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 logger = 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
128    // assumes single bean or Cascade.PERSIST will save any
129    // related beans (e.g. customer -> customer.billingAddress
130    server.save(bean, transaction);
131
132    if (logInfoFrequency > 0 && (row % logInfoFrequency == 0)) {
133      logger.info("processed " + row + " rows");
134    }
135  }
136
137  /**
138   * Commit the transaction if one was created.
139   */
140  @Override
141  public void end(int row) {
142
143    commitTransactionIfCreated();
144
145    exeTime = System.currentTimeMillis() - startTime;
146    logger.info("Csv finished, rows[" + row + "] exeMillis[" + exeTime + "]");
147  }
148
149  /**
150   * Rollback the transaction if one was created.
151   */
152  @Override
153  public void endWithError(int row, Exception e) {
154    rollbackTransactionIfCreated(e);
155  }
156
157  /**
158   * Create a transaction if one is not already active and set its batch mode
159   * and batch size.
160   */
161  protected void initTransactionIfRequired() {
162
163    transaction = server.currentTransaction();
164    if (transaction == null || !transaction.isActive()) {
165
166      transaction = server.beginTransaction();
167      createdTransaction = true;
168      if (persistBatchSize > 1) {
169        logger.info("Creating transaction, batchSize[" + persistBatchSize + "]");
170        transaction.setBatchMode(true);
171        transaction.setBatchSize(persistBatchSize);
172        transaction.setGetGeneratedKeys(false);
173
174      } else {
175        // explicitly turn off JDBC batching in case
176        // is has been turned on globally
177        transaction.setBatchMode(false);
178        logger.info("Creating transaction with no JDBC batching");
179      }
180    }
181  }
182
183  /**
184   * If we created a transaction commit it. We have successfully processed all
185   * the rows.
186   */
187  protected void commitTransactionIfCreated() {
188    if (createdTransaction) {
189      transaction.commit();
190      logger.info("Committed transaction");
191    }
192  }
193
194  /**
195   * Rollback the transaction if we where not successful in processing all the
196   * rows.
197   */
198  protected void rollbackTransactionIfCreated(Throwable e) {
199    if (createdTransaction) {
200      transaction.rollback(e);
201      logger.info("Rolled back transaction");
202    }
203  }
204
205}