BeanIO InvalidRecordGroupException giving wrong line number

2k views Asked by At

I am using BeanIO to parse a fixed width text file. For example, the file coming into my project looks like the following fixed width text:

 CD DummyValue3
 EF DummyValue4 DummyValue5 DummyValue6
 AB DummyValue1 DummyValue2
 ...

In my mappings files, I have a recorddeclared for each recordID (i.e. AB, CD, EF)

<record name="dummyRecord" template="AB"
class="com.company.project.DummyRecordClass" minOccurs="0"
maxOccurs="1" />

I then have a template for each record:

<template name="AB">
    <field name="recordID" length="3" rid="true" literal="AB"
           lenientPadding="true" minOccurs="0" />
    <field name="value1" length="12" lenientPadding="true"
           minOccurs="1" required="true"/>
    <field name="value2" length="12" lenientPadding="true"
           minOccurs="1" required="true"/>
</template>

Because value1 and value2 have a minOccurs = 1 as well as a required="true" (Yes I know this is redundant, but it was already in the code and we have thousands of these fields) they must exist if I have an AB segment.

So if I was to pass the following file into my program:

 CD DummyValue3
 EF DummyValue4 DummyValue5 DummyValue6
 AB DummyValue1

I receive the following InvalidRecordGroupException:

org.beanio.InvalidRecordGroupException: Invalid 'ediMessage' record group at line 1

However, since the missing field is actually on line 3, this can be a complete pain to debug when you have 500-600 lines of data coming into the application.

Is there any way to have beanIO output the correct line number, or even the template and field value when a mandatory field is missing?

1

There are 1 answers

0
nicoschl On BEST ANSWER

I use a custom implemenation of org.beanio.BeanReaderErrorHandler by extending org.beanio.BeanReaderErrorHandlerSupport to create a LoggingErrorHandler.

import org.beanio.BeanReaderErrorHandlerSupport;
import org.beanio.BeanReaderException;
import org.beanio.InvalidRecordException;
import org.beanio.MalformedRecordException;
import org.beanio.RecordContext;
import org.beanio.UnexpectedRecordException;
import org.beanio.UnidentifiedRecordException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A BeanIO {@code BeanReaderErrorHandler} that logs the error messages.
 *
 * @author Nico Schlebusch
 */
public class LoggingBeanReaderErrorHandler extends BeanReaderErrorHandlerSupport {

  private static final Logger LOGGER = LoggerFactory.getLogger(LoggingBeanReaderErrorHandler.class);

  /**
   * Creates a new instance of {@code LoggingBeanReaderErrorHandler}.
   */
  public LoggingBeanReaderErrorHandler() {
  }

  /**
   * {@inheritDoc}
   *
   * @see org.beanio.BeanReaderErrorHandlerSupport#invalidRecord(org.beanio.InvalidRecordException)
   */
  @Override
  public void invalidRecord(final InvalidRecordException ex) throws BeanReaderException {

    LOGGER.error("{}", createErrorMessage(ex));
  }

  /**
   * {@inheritDoc}
   *
   * @see org.beanio.BeanReaderErrorHandlerSupport#unexpectedRecord(org.beanio.UnexpectedRecordException)
   */
  @Override
  public void unexpectedRecord(final UnexpectedRecordException ex) throws BeanReaderException {

    LOGGER.error("{}", createErrorMessage(ex));
  }

  /**
   * {@inheritDoc}
   *
   * @see org.beanio.BeanReaderErrorHandlerSupport#unidentifiedRecord(org.beanio.UnidentifiedRecordException)
   */
  @Override
  public void unidentifiedRecord(final UnidentifiedRecordException ex) throws BeanReaderException {

    LOGGER.error("{}", createErrorMessage(ex));
  }

  /**
   * {@inheritDoc}
   *
   * @see org.beanio.BeanReaderErrorHandlerSupport#malformedRecord(org.beanio.MalformedRecordException)
   */
  @Override
  public void malformedRecord(final MalformedRecordException ex) throws BeanReaderException {

    LOGGER.error("{}", createErrorMessage(ex));
  }

  /**
   * {@inheritDoc}
   *
   * @see org.beanio.BeanReaderErrorHandlerSupport#fatalError(org.beanio.BeanReaderException)
   */
  @Override
  public void fatalError(final BeanReaderException ex) throws BeanReaderException {

    LOGGER.error("{}", createErrorMessage(ex));
  }

  /**
   * Creates an error message using the exception to get the RecordContext from which a meaningful error
   * message can be constructed.
   *
   * @param ex the exception containing the error information.
   *
   * @return a string describing the error(s).
   */
  protected String createErrorMessage(final BeanReaderException ex) {

    final String message = ex.getMessage();
    final StringBuilder errorMessage = new StringBuilder(message.length() * 5);

    // if a bean object is mapped to a record group,
    // the exception may contain more than one record
    for (int i = 0, j = ex.getRecordCount(); i < j; i++) {
      final RecordContext recordContext = ex.getRecordContext(i);
      final String recordName = recordContext.getRecordName();
      final String text = recordContext.getRecordText();
      errorMessage.append(String.format("%s: %s%n", message, text));

      if (recordContext.hasRecordErrors()) {
        for (final String error : recordContext.getRecordErrors()) {
          errorMessage.append(String.format("Record '%s' - %s%n", recordName, error));
        }
      }
      if (recordContext.hasFieldErrors()) {
        for (final String field : recordContext.getFieldErrors().keySet()) {
          for (final String error : recordContext.getFieldErrors(field)) {
            errorMessage.append(String.format("Field '%s' - %s%n", field, error));
          }
        }
      }
    }

    return errorMessage.toString();
  }

}

You can modify the createErrorMessage() method to only retain the information you are looking for.

Of course you can do a lot of other things with the information, like writing it to a file, email it etc. See the BeanReaderErrorHandler section of the documentation for alternatives.

Then you have to register the error handler with your BeanReader instance:

BeanReader beanReader = ....
beanReader.setErrorHandler(errorHandler);

This should then output more information about where the errors are located.