I am using spring batch
in the spring-boot application. The Spring Boot version is 2.3.3.RELEASE
.
I have a complex XML that contains header
(file information) and body
(List of the transaction). I used XJC tool
to get java classes from the XSD
.
To reproduce this error i have added the code on Github spring-batch-xml-to-xml
What I need to do: I have to read(unmarshal
) the XML, do some business logic on each transaction element
in the body, and then finally write(marshal
) the XML file with the same header and updated body.
When I try to marshal a child element object, I am getting IllegalStateException: Marshaller must support the class of the marshalled object.
Sample XML file
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<reportFile xmlns="http://deutsche-boerse.com/DBRegHub" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://deutsche-boerse.com/DBRegHub regulatoryHubUpload_MiFIR_001.60.xsd">
<fileInformation>
<sender>11003220</sender>
<timestamp>2020-12-23T09:05:34Z</timestamp>
<environment>LOCAL</environment>
<version>1.0</version>
</fileInformation>
<record>
<transaction>
</transaction>
<transaction>
</transaction>
<transaction>
</transaction>
</record>
</reportFile>
Spring Batch Configuration
package com.trax.europeangateway.config;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.JobScope;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.launch.support.SimpleJobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemStreamReader;
import org.springframework.batch.item.support.CompositeItemProcessor;
import org.springframework.batch.item.support.CompositeItemWriter;
import org.springframework.batch.item.xml.StaxEventItemWriter;
import org.springframework.batch.item.xml.builder.StaxEventItemReaderBuilder;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.task.TaskExecutor;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.springframework.oxm.xstream.XStreamMarshaller;
import org.springframework.util.ClassUtils;
import org.springframework.web.client.RestTemplate;
import com.trax.europeangateway.itemprocessor.omegaxml.PIExtractorItemProcessor;
import com.trax.europeangateway.itemprocessor.omegaxml.PIRemoverItemProcessor;
import com.trax.europeangateway.itemwriter.omegaxml.EdsClientItemWriter;
import com.trax.europeangateway.itemwriter.omegaxml.ExtendedStaxEventItemWriter;
import com.trax.europeangateway.itemwriter.omegaxml.OmegaXmlFileWriter;
import com.trax.europeangateway.itemwriter.omegaxml.OmegaXmlFooterCallBack;
import com.trax.europeangateway.itemwriter.omegaxml.OmegaXmlHeaderCallBack;
import com.trax.europeangateway.listener.JobResultListener;
import com.trax.europeangateway.listener.StepResultListener;
import com.trax.europeangateway.model.dto.FileInformationHeaderDto;
import com.trax.europeangateway.model.dto.ProcessorWriterDto;
import com.trax.europeangateway.model.dto.xsd.omega.TransactionPositionReport;
import com.trax.europeangateway.service.ExtractHeaderOmegaXml;
import com.trax.europeangateway.util.FileUtils;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Configuration
@EnableBatchProcessing
public class BatchConfiguration {
@Autowired
PIExtractorItemProcessor pIExtractorItemProcessor;
@Autowired
StepResultListener stepResultListener;
@Autowired
JobBuilderFactory jobBuilderFactory;
@Autowired
StepBuilderFactory stepBuilderFactory;
@Autowired
private FileUtils fileUtils;
@Qualifier("europeanGatewayJobExecutor")
@Autowired
private TaskExecutor taskExecutor;
@Value( "${eugateway.batch.chunk.size}" )
private int chunkSize;
@Qualifier("euGatewayJobLauncher")
@Bean
public JobLauncher euGatewayJobLauncher(JobRepository jobRepository) throws Exception {
SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
jobLauncher.setJobRepository(jobRepository);
jobLauncher.setTaskExecutor(taskExecutor);
jobLauncher.afterPropertiesSet();
return jobLauncher;
}
@JobScope
@Bean (name = "extractHeaderStep")
public Step extractHeaderStep(StepBuilderFactory steps , @Value("#{jobParameters['file.path']}") String path) {
return steps.get("extractHeaderStep")
.tasklet((contribution, chunkContext) -> {
FileInformationHeaderDto fileInformation = new ExtractHeaderOmegaXml().readHeader(path);
ExecutionContext jobExecutionContext = chunkContext.getStepContext().getStepExecution().getJobExecution().getExecutionContext();
jobExecutionContext.put("file.information", fileInformation);
return RepeatStatus.FINISHED;
}).build();
}
@JobScope
@Bean (name = "extractAndReplacePersonalDataStep")
public Step jobStep(ItemStreamReader<TransactionPositionReport> reader,
CompositeItemProcessor<TransactionPositionReport,
ProcessorWriterDto> processor,
CompositeItemWriter<ProcessorWriterDto> writer,
StepBuilderFactory stepBuilderFactory) {
return stepBuilderFactory.get("extractAndReplacePersonalDataStep")
.<TransactionPositionReport, ProcessorWriterDto>chunk(chunkSize)
.reader(reader)
.processor(processor)
.writer(writer)
.listener(stepResultListener)
.build();
}
@Qualifier("omegaXmlJob")
@Bean
public Job extractPersonalDataJob(Step extractHeaderStep, Step extractAndReplacePersonalDataStep, JobResultListener jobListener,
JobBuilderFactory jobBuilderFactory) {
return jobBuilderFactory.get("extractAndReplacePersonalDataJob")
.incrementer(new RunIdIncrementer())
.start(extractHeaderStep)
.next(extractAndReplacePersonalDataStep)
.listener(jobListener)
.build();
}
@Bean
@StepScope
public ItemStreamReader<TransactionPositionReport> itemReader(@Value("#{jobParameters['file.path']}") String path) {
Jaxb2Marshaller transactionMarshaller = new Jaxb2Marshaller();
transactionMarshaller.setMappedClass(TransactionPositionReport.class);
transactionMarshaller.setPackagesToScan(ClassUtils.getPackageName(TransactionPositionReport.class));
transactionMarshaller.setSupportJaxbElementClass(true);
transactionMarshaller.setSupportDtd(true);
log.debug("Generating StaxEventItemReader");
return new StaxEventItemReaderBuilder<TransactionPositionReport>()
.name("transactionPositionReport")
.resource(new FileSystemResource(path))
.addFragmentRootElements("transaction")
.unmarshaller(transactionMarshaller)
.build();
}
@Bean
@JobScope
OmegaXmlHeaderCallBack getOmegaXmlHeaderCallBack(@Value("#{jobExecutionContext['file.information']}") FileInformationHeaderDto fileInformation){
return new OmegaXmlHeaderCallBack(fileInformation);
}
@Bean
OmegaXmlFooterCallBack getOmegaXmlFooterCallBack(){
return new OmegaXmlFooterCallBack();
}
@StepScope
@Bean(name = "staxTransactionWriter")
public StaxEventItemWriter<TransactionPositionReport> staxTransactionItemWriter(OmegaXmlHeaderCallBack omegaXmlHeaderCallBack,
@Value("#{jobParameters['file.path']}") String path, @Value("#{jobParameters['submission.account']}") String submissionAccount) {
Resource exportFileResource = new FileSystemResource(fileUtils.getOutputFilePath(path, submissionAccount));
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setSupportJaxbElementClass(true);
marshaller.setClassesToBeBound(TransactionPositionReport.class);
HashMap<String, String> rootElementAttribs = new HashMap<String, String>();
rootElementAttribs.put("xmlns", "http://deutsche-boerse.com/DBRegHub");
rootElementAttribs.put("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
rootElementAttribs.put("xsi:schemaLocation", "http://deutsche-boerse.com/DBRegHub regulatoryHubUpload_MiFIR_001.60.xsd");
ExtendedStaxEventItemWriter<TransactionPositionReport> writer = new ExtendedStaxEventItemWriter<TransactionPositionReport>();
writer.setName("transactionWriter");
writer.setVersion("1.0");
writer.setResource(exportFileResource);
writer.setMarshaller(marshaller);
writer.setRootTagName("reportFile");
writer.setRootElementAttributes(rootElementAttribs);
writer.setHeaderCallback(omegaXmlHeaderCallBack);
writer.setFooterCallback(getOmegaXmlFooterCallBack());
writer.setShouldDeleteIfEmpty(true);
writer.setIndenting(true);
return writer;
}
@StepScope
@Bean
public PIExtractorItemProcessor extractItemProcessor() {
log.debug("Generating PIExtractorItemProcessor");
return new PIExtractorItemProcessor();
}
@Bean
public PIRemoverItemProcessor removeItemProcessor() {
log.debug("Generating PIRemoverItemProcessor");
return new PIRemoverItemProcessor();
}
@Bean
@StepScope
CompositeItemProcessor<TransactionPositionReport, ProcessorWriterDto> extractAndRemoveItemProcessor() {
log.debug("Generating CompositeItemProcessor");
CompositeItemProcessor<TransactionPositionReport, ProcessorWriterDto> itemProcessor = new CompositeItemProcessor<>();
itemProcessor.setDelegates((List<? extends ItemProcessor<?, ?>>) Arrays.asList(extractItemProcessor(), removeItemProcessor()));
return itemProcessor;
}
@Bean
public EdsClientItemWriter<ProcessorWriterDto> edsClientItemWriter() {
log.debug("Generating EdsClientItemWriter");
return new EdsClientItemWriter<>();
}
@Bean
@StepScope
public OmegaXmlFileWriter<ProcessorWriterDto> omegaXmlFileWriter(OmegaXmlHeaderCallBack omegaXmlHeaderCallBack, @Value("#{jobParameters['file.path']}") String path, @Value("#{jobParameters['submission.account']}") String submissionAccount) {
log.debug("Generating OmegaXmlFileWriter");
return new OmegaXmlFileWriter(staxTransactionItemWriter(omegaXmlHeaderCallBack, path, submissionAccount));
}
@Bean
@StepScope
public CompositeItemWriter<ProcessorWriterDto> compositeItemWriter(OmegaXmlHeaderCallBack omegaXmlHeaderCallBack, @Value("#{jobParameters['file.path']}") String path, @Value("#{jobParameters['submission.account']}") String submissionAccount) {
log.debug("Generating CompositeItemWriter");
CompositeItemWriter<ProcessorWriterDto> compositeItemWriter = new CompositeItemWriter<>();
compositeItemWriter.setDelegates(Arrays.asList(edsClientItemWriter(), omegaXmlFileWriter(omegaXmlHeaderCallBack, path, submissionAccount)));
return compositeItemWriter;
}
@Bean
public RestTemplate restTemplate() {
log.debug("Generating RestTemplate");
return new RestTemplate();
}
}
Transaction, Report and ReportFile java classes generated from XJC tool
//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.8-b130911.1802
// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a>
// Any modifications to this file will be lost upon recompilation of the source schema.
// Generated on: 2021.05.18 at 02:21:55 PM BST
//
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/**
* <p>Java class for transactionPositionReport complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* <complexType name="transactionPositionReport">
* <complexContent>
* <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* <sequence>
* <element name="processingDetails" type="{http://deutsche-boerse.com/DBRegHub}processingDetails"/>
* <element name="configurableFields" type="{http://deutsche-boerse.com/DBRegHub}configurableFields" minOccurs="0"/>
* <element name="mifir" type="{http://deutsche-boerse.com/DBRegHub}mifirDetails" minOccurs="0"/>
* </sequence>
* </restriction>
* </complexContent>
* </complexType>
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "transactionPositionReport", propOrder = {
"processingDetails",
"configurableFields",
"mifir"
})
public class TransactionPositionReport {
@XmlElement(required = true)
protected ProcessingDetails processingDetails;
protected ConfigurableFields configurableFields;
protected MifirDetails mifir;
/**
* Gets the value of the processingDetails property.
*
* @return
* possible object is
* {@link ProcessingDetails }
*
*/
public ProcessingDetails getProcessingDetails() {
return processingDetails;
}
/**
* Sets the value of the processingDetails property.
*
* @param value
* allowed object is
* {@link ProcessingDetails }
*
*/
public void setProcessingDetails(ProcessingDetails value) {
this.processingDetails = value;
}
/**
* Gets the value of the configurableFields property.
*
* @return
* possible object is
* {@link ConfigurableFields }
*
*/
public ConfigurableFields getConfigurableFields() {
return configurableFields;
}
/**
* Sets the value of the configurableFields property.
*
* @param value
* allowed object is
* {@link ConfigurableFields }
*
*/
public void setConfigurableFields(ConfigurableFields value) {
this.configurableFields = value;
}
/**
* Gets the value of the mifir property.
*
* @return
* possible object is
* {@link MifirDetails }
*
*/
public MifirDetails getMifir() {
return mifir;
}
/**
* Sets the value of the mifir property.
*
* @param value
* allowed object is
* {@link MifirDetails }
*
*/
public void setMifir(MifirDetails value) {
this.mifir = value;
}
}
//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.8-b130911.1802
// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a>
// Any modifications to this file will be lost upon recompilation of the source schema.
// Generated on: 2021.05.18 at 02:21:55 PM BST
//
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlType;
/**
* <p>Java class for record complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* <complexType name="record">
* <complexContent>
* <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* <choice>
* <element name="transaction" type="{http://deutsche-boerse.com/DBRegHub}transactionPositionReport" maxOccurs="unbounded" minOccurs="0"/>
* <element name="referencePartyDetails" type="{http://deutsche-boerse.com/DBRegHub}referencePartyDetails" maxOccurs="unbounded" minOccurs="0"/>
* </choice>
* </restriction>
* </complexContent>
* </complexType>
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "record", propOrder = {
"transaction",
"referencePartyDetails"
})
public class Record {
protected List<TransactionPositionReport> transaction;
protected List<ReferencePartyDetails> referencePartyDetails;
/**
* Gets the value of the transaction property.
*
* <p>
* This accessor method returns a reference to the live list,
* not a snapshot. Therefore any modification you make to the
* returned list will be present inside the JAXB object.
* This is why there is not a <CODE>set</CODE> method for the transaction property.
*
* <p>
* For example, to add a new item, do as follows:
* <pre>
* getTransaction().add(newItem);
* </pre>
*
*
* <p>
* Objects of the following type(s) are allowed in the list
* {@link TransactionPositionReport }
*
*
*/
public List<TransactionPositionReport> getTransaction() {
if (transaction == null) {
transaction = new ArrayList<TransactionPositionReport>();
}
return this.transaction;
}
public List<ReferencePartyDetails> getReferencePartyDetails() {
if (referencePartyDetails == null) {
referencePartyDetails = new ArrayList<ReferencePartyDetails>();
}
return this.referencePartyDetails;
}
}
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/**
* <p>Java class for anonymous complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* <complexType>
* <complexContent>
* <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* <sequence>
* <element name="fileInformation" type="{http://deutsche-boerse.com/DBRegHub}fileInformation"/>
* <element name="record" type="{http://deutsche-boerse.com/DBRegHub}record"/>
* </sequence>
* </restriction>
* </complexContent>
* </complexType>
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"fileInformation",
"record"
})
@XmlRootElement(name = "reportFile")
public class ReportFile {
@XmlElement(required = true)
protected FileInformation fileInformation;
@XmlElement(required = true)
protected Record record;
/**
* Gets the value of the fileInformation property.
*
* @return
* possible object is
* {@link FileInformation }
*
*/
public FileInformation getFileInformation() {
return fileInformation;
}
/**
* Sets the value of the fileInformation property.
*
* @param value
* allowed object is
* {@link FileInformation }
*
*/
public void setFileInformation(FileInformation value) {
this.fileInformation = value;
}
/**
* Gets the value of the record property.
*
* @return
* possible object is
* {@link Record }
*
*/
public Record getRecord() {
return record;
}
/**
* Sets the value of the record property.
*
* @param value
* allowed object is
* {@link Record }
*
*/
public void setRecord(Record value) {
this.record = value;
}
}
Stacktrace of exception that I get when trying to marshal
java.lang.IllegalStateException: Marshaller must support the class of the marshalled object
at org.springframework.util.Assert.state(Assert.java:76) ~[spring-core-5.2.8.RELEASE.jar:5.2.8.RELEASE]
at org.springframework.batch.item.xml.StaxEventItemWriter.write(StaxEventItemWriter.java:767) ~[spring-batch-infrastructure-4.2.4.RELEASE.jar:4.2.4.RELEASE]
at org.springframework.batch.item.xml.StaxEventItemWriter$$FastClassBySpringCGLIB$$d105dd1.invoke(<generated>) ~[spring-batch-infrastructure-4.2.4.RELEASE.jar:4.2.4.RELEASE]
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.2.8.RELEASE.jar:5.2.8.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:771) ~[spring-aop-5.2.8.RELEASE.jar:5.2.8.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.2.8.RELEASE.jar:5.2.8.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749) ~[spring-aop-5.2.8.RELEASE.jar:5.2.8.RELEASE]
at org.springframework.aop.support.DelegatingIntroductionInterceptor.doProceed(DelegatingIntroductionInterceptor.java:136) ~[spring-aop-5.2.8.RELEASE.jar:5.2.8.RELEASE]
at org.springframework.aop.support.DelegatingIntroductionInterceptor.invoke(DelegatingIntroductionInterceptor.java:124) ~[spring-aop-5.2.8.RELEASE.jar:5.2.8.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.2.8.RELEASE.jar:5.2.8.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749) ~[spring-aop-5.2.8.RELEASE.jar:5.2.8.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:691) ~[spring-aop-5.2.8.RELEASE.jar:5.2.8.RELEASE]
at org.springframework.batch.item.xml.StaxEventItemWriter$$EnhancerBySpringCGLIB$$25cb8145.write(<generated>) ~[spring-batch-infrastructure-4.2.4.RELEASE.jar:4.2.4.RELEASE]
at com.trax.europeangateway.itemwriter.omegaxml.OmegaXmlFileWriter.write(OmegaXmlFileWriter.java:37) ~[classes/:?]
at com.trax.europeangateway.itemwriter.omegaxml.OmegaXmlFileWriter$$FastClassBySpringCGLIB$$48e7f3da.invoke(<generated>) ~[classes/:?]
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.2.8.RELEASE.jar:5.2.8.RELEASE]
FYI: In the generated java classes only ReportFile
class is annotated with @XmlRootElement
. But if I annotate the transaction
class with @XmlRootElement
, I am able to get generate a XML file but then all the transaction tag in the output file have namespace info with them.
<?xml version="1.0" encoding="UTF-8"?>
<reportFile xsi:schemaLocation="http://deutsche-boerse.com/DBRegHub regulatoryHubUpload_MiFIR_001.60.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<fileInformation>
<sender>11003220</sender>
<timestamp>2020-12-23T09:05:34Z</timestamp>
<environment>LOCAL</environment>
<version>1.0</version>
</fileInformation>
<record>
<transaction xmlns="http://deutsche-boerse.com/DBRegHub">
</transaction>
<transaction xmlns="http://deutsche-boerse.com/DBRegHub">
</transaction>
<transaction xmlns="http://deutsche-boerse.com/DBRegHub">
</transaction>
</record>
</reportFile>
marshaller.setSupportJaxbElementClass(true); marshaller.setClassesToBeBound(TransactionPositionReport.class);
on the marshaller, so I don't understand (by just looking at what you shared) why you are getting this error. This kind of errors is not easy to investigate without being able to reproduce them, so please provide a repo with a minimal complete example to be able to help you. – Counselorjava: java.lang.ExceptionInInitializerError Unable to make field private com.sun.tools.javac.processing.JavacProcessingEnvironment$DiscoveredProcessors com.sun.tools.javac.processing.JavacProcessingEnvironment.discoveredProcs accessible: module jdk.compiler does not "opens com.sun.tools.javac.processing" to unnamed module @272e20ee
. – Counselortransaction
s tags: github.com/trueKiller07/spring-batch-xml-to-xml/blob/main/src/… , but in your description you said that you don't want those namespace attributes on thetransaction
tags:But if I annotate the transaction class with @XmlRootElement, I am able to get generate a XML file but then all the transaction tag in the output file have namespace info with them.
. Sorry, I'm trying to help but I'm lost. – Counselor