Eria
Eria

Reputation: 3182

Spring Boot batch - MultiResourceItemReader : move to next file on error

In a batch service, I read multiple XML files using a MultiResourceItemReader, which delegate to a StaxEventItemReader.

If an error is raised reading a file (a parsing exception for example), I would like to specify to Spring to start reading the next matching file. Using @OnReadError annotation and/or a SkipPolicy for example.

Currently, when a reading exception is raised, the batch stops.

Does anyone have an idea how to do it ?

EDIT: I see MultiResourceItemReader has a method readNextItem(), but it's private -_-

Upvotes: 1

Views: 4716

Answers (2)

Eria
Eria

Reputation: 3182

Here are my final classes to read multiple XML files and jump to the next file when a read error occurs on one (thanks to Luca's idea).

My custom ItemReader, extended from MultiResourceItemReader :

public class MyItemReader extends MultiResourceItemReader<InputElement> {

    private SkippableResourceItemReader<InputElement> reader;

    public MyItemReader() throws IOException {
        super();

        // Resources
        PathMatchingResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
        this.setResources( resourceResolver.getResources( "classpath:input/inputFile*.xml" ) );

        // Delegate reader
        reader = new SkippableResourceItemReader<InputElement>();
        StaxEventItemReader<InputElement> delegateReader = new StaxEventItemReader<InputElement>();
        delegateReader.setFragmentRootElementName("inputElement");
        Jaxb2Marshaller unmarshaller = new Jaxb2Marshaller();
        unmarshaller.setClassesToBeBound( InputElement.class );
        delegateReader.setUnmarshaller( unmarshaller );
        reader.setDelegate( delegateReader );

        this.setDelegate( reader );
    }

    [...] 

    @OnReadError
    public void onReadError( Exception exception ){
        reader.setSkipResource( true );
    }
}

And the ItemReader-in-the-middle used to skip the current resource :

public class SkippableResourceItemReader<T> implements ResourceAwareItemReaderItemStream<T> {

    private ResourceAwareItemReaderItemStream<T> delegate;
    private boolean skipResource = false;

    @Override
    public void close() throws ItemStreamException {
        delegate.close();
    }

    @Override
    public T read() throws UnexpectedInputException, ParseException, NonTransientResourceException, Exception {
        if( skipResource ){
            skipResource = false;
            return null;
        }
        return delegate.read();
    }

    @Override
    public void setResource( Resource resource ) {
        skipResource = false;
        delegate.setResource( resource );
    }

    @Override
    public void open( ExecutionContext executionContext ) throws ItemStreamException {
        delegate.open( executionContext );
    }

    @Override
    public void update( ExecutionContext executionContext ) throws ItemStreamException {
        delegate.update( executionContext );
    }

    public void setDelegate(ResourceAwareItemReaderItemStream<T> delegate) {
        this.delegate = delegate;
    }

    public void setSkipResource( boolean skipResource ) {
        this.skipResource = skipResource;
    }
}

Upvotes: 1

Luca Basso Ricci
Luca Basso Ricci

Reputation: 18403

I'm not using SB for a while, but looking MultiResourceItemReader code I suppose you can write your own ResourceAwareItemReaderItemStream wrapper where you check for a flag setted to move to next file or to perform a standard read using a delegate.
This flag can be stored into execution-context or into your wrapper and should be cleared after a move next.

class MoveNextReader<T> implements ResourceAwareItemReaderItemStream<T> {
  private ResourceAwareItemReaderItemStream delegate;
  private boolean skipThisFile = false;

  public void setSkipThisFile(boolean value) {
    skipThisFile = value;
  }

  public void setResource(Resource resource) {
    skipThisFile = false;
    delegate.setResource(resource);
  }

  public T read() {
    if(skipThisFile) {
      skipThisFile = false;
      // This force MultiResourceItemReader to move to next resource
      return null;
    }
    return delegate.read();
  }
}

Use this class as delegate for MultiResourceItemReader and in @OnReadErrorinject MoveNextReader and set MoveNextReader.skipThisFile.

I can't test code from myself but I hope this can be a good starting point.

Upvotes: 2

Related Questions