Issue retrieving a ExecutionContext from a SkipListener

Viewed 961

I am trying to retrieve a spring batch ExecutionContext from a SkipListener.

Here is what I have attempted (I rely on annotations instead of interfaces in order to implement my listeners):

import com.xxxx.domain.UserAccount;
import lombok.extern.slf4j.Slf4j;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.annotation.BeforeStep;
import org.springframework.batch.core.annotation.OnSkipInWrite;
import org.springframework.mail.MailSendException;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class MailSkipListener {

    private StepExecution stepExecution;

    @BeforeStep
    public void saveStepExecution(StepExecution stepExecution) {
        this.stepExecution = stepExecution;
    }

    @OnSkipInWrite
    public void logSkippedEmail(UserAccount userAccount, Throwable t) {
        if (t instanceof MailSendException) {
            MailSendException e = (MailSendException) t;
            log.warn("FailedMessages: " + e.getFailedMessages());
        }
    }
}

However, the logSkippedEmail method is never executed when a MailSendException is raised. When I remove the saveStepExecution method, the logSkippedEmail is again executed in case of a MailSendException.

I register my MailSkipListener as follows:

@Bean
public Step messagesDigestMailingStep(EntityManagerFactory entityManagerFactory) {
    return stepBuilderFactory
            .get("messagesDigestMailingStep")
            .<UserAccount, UserAccount>chunk(5)

            ...

            .writer(itemWriter)
            .listener(mailSkipListener)//Here
            .build();
}

What I am trying to achieve here is retrieving an ExecutionContext from my SkipListener. How can this be achieved? It seems there's no way to autowire theExecutionContext.

3 Answers

I know this is an old Question, but I had to deal with this myself, and have put together the following implementation, wherein I made the SkipListener also implement the StepExecutionListener, and adding the same class as both the SkipListener and StepExecutionListener.

@Component
public class PersonImportListener implements SkipListener<Person, Person>, StepExecutionListener {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    
    private StepExecution stepExecution;
    @Override
    public void beforeStep(StepExecution stepExecution) {
        this.stepExecution = stepExecution;
    }

    @Override
    public ExitStatus afterStep(StepExecution stepExecution) {
        return ExitStatus.COMPLETED;
    }

    @Override
    public void onSkipInRead(Throwable throwable) {
        logger.warn("Line skipped on read", throwable);
    }

    @Override
    public void onSkipInWrite(Person person, Throwable throwable) {
        logger.warn("Bean skipped on write", throwable);
        logger.warn("Execution Context" + stepExecution);
    }

    @Override
    public void onSkipInProcess(Person person, Throwable throwable) {
        logger.warn("Bean skipped on process", throwable);
    }
}

And use this class as a listener for StepExecutionListener and also SkipListener.

@Bean
public Step step1(JdbcBatchItemWriter<Person> writer) {
    PersonImportListener listener = new PersonImportListener();
    return stepBuilderFactory.get("step1")
            .<Person, Person> chunk(10)
            .reader(reader())
            .faultTolerant()
            .skipLimit(10)
            .skip(DataIntegrityViolationException.class)
            .listener((StepExecutionListener) listener)
            .listener((SkipListener) listener)
            .processor(processor())
            .writer(writer)
            .build();
}
Related