001package io.ebean.text.csv;
002
003import io.ebean.Database;
004import io.ebean.Transaction;
005import org.slf4j.Logger;
006import org.slf4j.LoggerFactory;
007
008/**
009 * Provides the default implementation of CsvCallback.
010 * <p>
011 * This handles transaction creation (if no current transaction existed) and
012 * transaction commit or rollback on error.
013 * </p>
014 * <p>
015 * For customising the processing you can extend this object and override the
016 * appropriate methods.
017 * </p>
018 *
019 * @param <T>
020 */
021public class DefaultCsvCallback<T> implements CsvCallback<T> {
022
023  private static final Logger log = LoggerFactory.getLogger(DefaultCsvCallback.class);
024
025  /**
026   * The transaction to use (if not using CsvCallback).
027   */
028  protected Transaction transaction;
029
030  /**
031   * Flag set when we created the transaction.
032   */
033  protected boolean createdTransaction;
034
035  /**
036   * The EbeanServer used to save the beans.
037   */
038  protected Database server;
039
040  /**
041   * Used to log a message to indicate progress through large files.
042   */
043  protected final int logInfoFrequency;
044
045  /**
046   * The batch size used when saving the beans.
047   */
048  protected final int persistBatchSize;
049
050  /**
051   * The time the process started.
052   */
053  protected long startTime;
054
055  /**
056   * The execution time of the process.
057   */
058  protected long exeTime;
059
060  /**
061   * Construct with a default batch size of 30 and logging info messages every
062   * 1000 rows.
063   */
064  public DefaultCsvCallback() {
065    this(30, 1000);
066  }
067
068  /**
069   * Construct with explicit batch size and logging info frequency.
070   */
071  public DefaultCsvCallback(int persistBatchSize, int logInfoFrequency) {
072    this.persistBatchSize = persistBatchSize;
073    this.logInfoFrequency = logInfoFrequency;
074  }
075
076  /**
077   * Create a transaction if required.
078   */
079  @Override
080  public void begin(Database server) {
081    this.server = server;
082    this.startTime = System.currentTimeMillis();
083    initTransactionIfRequired();
084  }
085
086  /**
087   * Override to read the heading line.
088   * <p>
089   * This is only called if {@link CsvReader#setHasHeader(boolean, boolean)} is
090   * set to true.
091   * <p>
092   * By default this does nothing (effectively ignoring the heading).
093   */
094  @Override
095  public void readHeader(String[] line) {
096
097  }
098
099  /**
100   * Validate that the content is valid and return false if the row should be
101   * ignored.
102   * <p>
103   * By default this just returns true.
104   * </p>
105   * <p>
106   * Override this to add custom validation logic returning false if you want
107   * the row to be ignored. For example, if all the content is empty return
108   * false to ignore the row (rather than having the processing fail with some
109   * error).
110   * </p>
111   */
112  @Override
113  public boolean processLine(int row, String[] line) {
114    return true;
115  }
116
117  /**
118   * Will save the bean.
119   * <p>
120   * Override this method to customise the bean (set additional properties etc)
121   * or to control the saving of other related beans (when you can't/don't want
122   * to use Cascade.PERSIST etc).
123   * </p>
124   */
125  @Override
126  public void processBean(int row, String[] line, T bean) {
127    // assumes single bean or Cascade.PERSIST will save any
128    // related beans (e.g. customer -> customer.billingAddress
129    server.save(bean, transaction);
130    if (logInfoFrequency > 0 && (row % logInfoFrequency == 0)) {
131      log.debug("processed {} rows", row);
132    }
133  }
134
135  /**
136   * Commit the transaction if one was created.
137   */
138  @Override
139  public void end(int row) {
140    commitTransactionIfCreated();
141    exeTime = System.currentTimeMillis() - startTime;
142    log.info("Csv finished, rows[{}] exeMillis[{}]", row, exeTime);
143  }
144
145  /**
146   * Rollback the transaction if one was created.
147   */
148  @Override
149  public void endWithError(int row, Exception e) {
150    rollbackTransactionIfCreated(e);
151  }
152
153  /**
154   * Create a transaction if one is not already active and set its batch mode
155   * and batch size.
156   */
157  protected void initTransactionIfRequired() {
158    transaction = server.currentTransaction();
159    if (transaction == null || !transaction.isActive()) {
160      transaction = server.beginTransaction();
161      createdTransaction = true;
162      if (persistBatchSize > 1) {
163        log.debug("Creating transaction, batchSize[{}]", persistBatchSize);
164        transaction.setBatchMode(true);
165        transaction.setBatchSize(persistBatchSize);
166        transaction.setGetGeneratedKeys(false);
167      } else {
168        // explicitly turn off JDBC batching in case
169        // is has been turned on globally
170        transaction.setBatchMode(false);
171        log.debug("Creating transaction with no JDBC batching");
172      }
173    }
174  }
175
176  /**
177   * If we created a transaction commit it. We have successfully processed all
178   * the rows.
179   */
180  protected void commitTransactionIfCreated() {
181    if (createdTransaction) {
182      transaction.commit();
183      log.debug("Committed transaction");
184    }
185  }
186
187  /**
188   * Rollback the transaction if we where not successful in processing all the
189   * rows.
190   */
191  protected void rollbackTransactionIfCreated(Throwable e) {
192    if (createdTransaction) {
193      transaction.rollback(e);
194      log.debug("Rolled back transaction");
195    }
196  }
197
198}