code: @Override
    public void processEvent(QueryBatch batch) {
      try {
        PlanBuilder.Plan exportPlan = exportFunction.apply(batch);
        for (RowRecord record : rowManager.resultRows(exportPlan)) {
          for (Consumer<RowRecord> listener : opticExportListeners) {
            try {
              listener.accept(record);
            } catch (Throwable t) {
              logger.error("Exception thrown by an onRowRecordReady listener ", t);
            }
          }
        }
      } catch (Throwable t) {
        for (BatchFailureListener<Batch<String>> listener : opticFailureListeners) {
          try {
            listener.processFailure(batch, t);
          } catch (Throwable t2) {
            logger.error("Exception thrown by an onBatchFailure listener", t2);
          }
        }
      }
    }
docs: This is the method QueryBatcher calls for OpticExportListener to do its
  thing. You should not need to call it.
  
  @param batch the batch of uris and some metadata about the current status
  of the job