I’m implementing a job that will load data from a database and write the result to xml files. The xml files will have a header that is based on some attributes that are stored in the execution context. Therefore, I want to acces the execution context. I’ve done this in other beans
in this job, but I can’t figure out how to access the execution context from within my StaxWriterCallback.
In the job my writer is currently implemented as:
@Bean(name = "writer") @StepScope public StaxEventItemWriter<Data> writer(@Value("#{jobParameters['path']}") String path) { StaxEventItemWriter<Data> itemWriter = new StaxEventItemWriter<Data>(); itemWriter.setVersion("1.0"); itemWriter.setEncoding("UTF-8"); itemWriter.setStandalone(false); itemWriter.setHeaderCallback(headerCallback()); itemWriter.setFooterCallback(new CustomXMLFooterCallback()); itemWriter.setMarshaller(new XStreamMarshaller()); FileSystemResource file = new FileSystemResource(path + "test.xml"); itemWriter.setResource(file); return itemWriter; }
The header callback is implemented as:
@Component public class CustomXMLHeaderCallback implements StaxWriterCallback { private StepExecution stepExecution; @BeforeStep public void beforeStep(StepExecution stepExecution) { this.stepExecution = stepExecution; } @Override public void write(XMLEventWriter writer) throws IOException { String currentFile = stepExecution.getExecutionContext().getString("currentFile"); try { XMLEventFactory eventFactory = XMLEventFactory.newInstance(); XMLEvent end = eventFactory.createDTD("n"); XMLEvent tab = eventFactory.createDTD("t"); writer.add(eventFactory.createStartElement("", "", "file")); writer.add(eventFactory.createCharacters(currentFile.substring(19, 31))); writer.add(eventFactory.createEndElement("", "", "file")); writer.add(end); } catch (Exception e) { e.printStackTrace(); } } }
The header callback is added to the batch job with:
@Bean @StepScope public CustomXMLHeaderCallback headerCallback() { return new CustomXMLHeaderCallback(); }
Unfortunately I get the following error:
java.lang.NullPointerException: Cannot invoke "org.springframework.batch.core.StepExecution.getExecutionContext()" because "this.stepExecution" is null at com.test.xml.callback.CustomXMLHeaderCallback.write(CustomXMLHeaderCallback.java:39) ~[classes/:na] at com.test.xml.callback.CustomXMLHeaderCallback$$FastClassBySpringCGLIB$$e0795ecb.invoke(<generated>) ~[classes/:na] at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.3.3.jar:5.3.3] at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:779) ~[spring-aop-5.3.3.jar:5.3.3] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.3.3.jar:5.3.3] at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) ~[spring-aop-5.3.3.jar:5.3.3] at org.springframework.aop.support.DelegatingIntroductionInterceptor.doProceed(DelegatingIntroductionInterceptor.java:137) ~[spring-aop-5.3.3.jar:5.3.3] at org.springframework.aop.support.DelegatingIntroductionInterceptor.invoke(DelegatingIntroductionInterceptor.java:124) ~[spring-aop-5.3.3.jar:5.3.3] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.3.jar:5.3.3] at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) ~[spring-aop-5.3.3.jar:5.3.3] at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692) ~[spring-aop-5.3.3.jar:5.3.3] at com.test.xml.callback.CustomXMLHeaderCallback$$EnhancerBySpringCGLIB$$e9083311.write(<generated>) ~[classes/:na] at org.springframework.batch.item.xml.StaxEventItemWriter.open(StaxEventItemWriter.java:447) ~[spring-batch-infrastructure-4.3.1.jar:4.3.1] at org.springframework.batch.item.xml.StaxEventItemWriter$$FastClassBySpringCGLIB$$d105dd1.invoke(<generated>) ~[spring-batch-infrastructure-4.3.1.jar:4.3.1] at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.3.3.jar:5.3.3] at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:779) ~[spring-aop-5.3.3.jar:5.3.3] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.3.3.jar:5.3.3] at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) ~[spring-aop-5.3.3.jar:5.3.3] at org.springframework.aop.support.DelegatingIntroductionInterceptor.doProceed(DelegatingIntroductionInterceptor.java:137) ~[spring-aop-5.3.3.jar:5.3.3] at org.springframework.aop.support.DelegatingIntroductionInterceptor.invoke(DelegatingIntroductionInterceptor.java:124) ~[spring-aop-5.3.3.jar:5.3.3] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.3.jar:5.3.3] at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) ~[spring-aop-5.3.3.jar:5.3.3] at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692) ~[spring-aop-5.3.3.jar:5.3.3] at org.springframework.batch.item.xml.StaxEventItemWriter$$EnhancerBySpringCGLIB$$599f2f97.open(<generated>) ~[spring-batch-infrastructure-4.3.1.jar:4.3.1] at org.springframework.batch.item.support.CompositeItemStream.open(CompositeItemStream.java:104) ~[spring-batch-infrastructure-4.3.1.jar:4.3.1] at org.springframework.batch.core.step.tasklet.TaskletStep.open(TaskletStep.java:311) ~[spring-batch-core-4.3.1.jar:4.3.1] at org.springframework.batch.core.step.AbstractStep.execute(AbstractStep.java:205) ~[spring-batch-core-4.3.1.jar:4.3.1] at org.springframework.batch.core.job.SimpleStepHandler.handleStep(SimpleStepHandler.java:148) ~[spring-batch-core-4.3.1.jar:4.3.1] at org.springframework.batch.core.job.AbstractJob.handleStep(AbstractJob.java:411) ~[spring-batch-core-4.3.1.jar:4.3.1] at org.springframework.batch.core.job.SimpleJob.doExecute(SimpleJob.java:136) ~[spring-batch-core-4.3.1.jar:4.3.1] at org.springframework.batch.core.job.AbstractJob.execute(AbstractJob.java:320) ~[spring-batch-core-4.3.1.jar:4.3.1] at org.springframework.batch.core.launch.support.SimpleJobLauncher$1.run(SimpleJobLauncher.java:147) ~[spring-batch-core-4.3.1.jar:4.3.1] at org.springframework.core.task.SyncTaskExecutor.execute(SyncTaskExecutor.java:50) ~[spring-core-5.3.3.jar:5.3.3] at org.springframework.batch.core.launch.support.SimpleJobLauncher.run(SimpleJobLauncher.java:140) ~[spring-batch-core-4.3.1.jar:4.3.1] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na] at java.base/java.lang.reflect.Method.invoke(Method.java:564) ~[na:na] at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344) ~[spring-aop-5.3.3.jar:5.3.3] at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198) ~[spring-aop-5.3.3.jar:5.3.3] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.3.3.jar:5.3.3] at org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration$PassthruAdvice.invoke(SimpleBatchConfiguration.java:128) ~[spring-batch-core-4.3.1.jar:4.3.1] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.3.jar:5.3.3] at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215) ~[spring-aop-5.3.3.jar:5.3.3] at com.sun.proxy.$Proxy62.run(Unknown Source) ~[na:na] at org.springframework.boot.autoconfigure.batch.JobLauncherApplicationRunner.execute(JobLauncherApplicationRunner.java:199) ~[spring-boot-autoconfigure-2.4.2.jar:2.4.2] at org.springframework.boot.autoconfigure.batch.JobLauncherApplicationRunner.executeLocalJobs(JobLauncherApplicationRunner.java:173) ~[spring-boot-autoconfigure-2.4.2.jar:2.4.2] at org.springframework.boot.autoconfigure.batch.JobLauncherApplicationRunner.launchJobFromProperties(JobLauncherApplicationRunner.java:160) ~[spring-boot-autoconfigure-2.4.2.jar:2.4.2] at org.springframework.boot.autoconfigure.batch.JobLauncherApplicationRunner.run(JobLauncherApplicationRunner.java:155) ~[spring-boot-autoconfigure-2.4.2.jar:2.4.2] at org.springframework.boot.autoconfigure.batch.JobLauncherApplicationRunner.run(JobLauncherApplicationRunner.java:150) ~[spring-boot-autoconfigure-2.4.2.jar:2.4.2] at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:795) ~[spring-boot-2.4.2.jar:2.4.2] at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:785) ~[spring-boot-2.4.2.jar:2.4.2] at org.springframework.boot.SpringApplication.run(SpringApplication.java:333) ~[spring-boot-2.4.2.jar:2.4.2] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1311) ~[spring-boot-2.4.2.jar:2.4.2] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1300) ~[spring-boot-2.4.2.jar:2.4.2] at com.test.main(MainOutboundDatins.java:18) ~[classes/:na]
Advertisement
Answer
If you want the beforeStep
method to be called before the step in your CustomXMLHeaderCallback
, you need to register that component as a listener in your step with one of the step builder’s listener
methods. This is explained in the docs here: Intercepting Step Execution.
Otherwise, you can inject the step execution with @Value
:
@Value("#{StepExecution}") private StepExecution stepExecution;
A third option is to inject the required attribute from the step execution context:
// define your header as follows: public class CustomXMLHeaderCallback implements StaxWriterCallback { private String currentFile; public CustomXMLHeaderCallback(String currentFile) { this.currentFile = currentFile; } @Override public void write(XMLEventWriter writer) throws IOException { // use this.currentFile as needed here } } // decalre your bean as follows: @Bean @StepScope public CustomXMLHeaderCallback headerCallback(@Value("#{stepExecutionContext['currentFile']}") String currentFile) { return new CustomXMLHeaderCallback(currentFile); }
I recommend the third option as it injects only the required attribute and not the whole step execution object.