001package io.ebean.text.csv;
002
003import io.ebean.EbeanServer;
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 EbeanServer 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
073    this.persistBatchSize = persistBatchSize;
074    this.logInfoFrequency = logInfoFrequency;
075  }
076
077  /**
078   * Create a transaction if required.
079   */
080  @Override
081  public void begin(EbeanServer server) {
082    this.server = server;
083    this.startTime = System.currentTimeMillis();
084
085    initTransactionIfRequired();
086  }
087
088  /**
089   * Override to read the heading line.
090   * <p>
091   * This is only called if {@link CsvReader#setHasHeader(boolean, boolean)} is
092   * set to true.
093   * <p>
094   * By default this does nothing (effectively ignoring the heading).
095   */
096  @Override
097  public void readHeader(String[] line) {
098
099  }
100
101  /**
102   * Validate that the content is valid and return false if the row should be
103   * ignored.
104   * <p>
105   * By default this just returns true.
106   * </p>
107   * <p>
108   * Override this to add custom validation logic returning false if you want
109   * the row to be ignored. For example, if all the content is empty return
110   * false to ignore the row (rather than having the processing fail with some
111   * error).
112   * </p>
113   */
114  @Override
115  public boolean processLine(int row, String[] line) {
116    return true;
117  }
118
119  /**
120   * Will save the bean.
121   * <p>
122   * Override this method to customise the bean (set additional properties etc)
123   * or to control the saving of other related beans (when you can't/don't want
124   * to use Cascade.PERSIST etc).
125   * </p>
126   */
127  @Override
128  public void processBean(int row, String[] line, T bean) {
129
130    // assumes single bean or Cascade.PERSIST will save any
131    // related beans (e.g. customer -> customer.billingAddress
132    server.save(bean, transaction);
133
134    if (logInfoFrequency > 0 && (row % logInfoFrequency == 0)) {
135      logger.info("processed " + row + " rows");
136    }
137  }
138
139  /**
140   * Commit the transaction if one was created.
141   */
142  @Override
143  public void end(int row) {
144
145    commitTransactionIfCreated();
146
147    exeTime = System.currentTimeMillis() - startTime;
148    logger.info("Csv finished, rows[" + row + "] exeMillis[" + exeTime + "]");
149  }
150
151  /**
152   * Rollback the transaction if one was created.
153   */
154  @Override
155  public void endWithError(int row, Exception e) {
156    rollbackTransactionIfCreated(e);
157  }
158
159  /**
160   * Create a transaction if one is not already active and set its batch mode
161   * and batch size.
162   */
163  protected void initTransactionIfRequired() {
164
165    transaction = server.currentTransaction();
166    if (transaction == null || !transaction.isActive()) {
167
168      transaction = server.beginTransaction();
169      createdTransaction = true;
170      if (persistBatchSize > 1) {
171        logger.info("Creating transaction, batchSize[" + persistBatchSize + "]");
172        transaction.setBatchMode(true);
173        transaction.setBatchSize(persistBatchSize);
174        transaction.setBatchGetGeneratedKeys(false);
175
176      } else {
177        // explicitly turn off JDBC batching in case
178        // is has been turned on globally
179        transaction.setBatchMode(false);
180        logger.info("Creating transaction with no JDBC batching");
181      }
182    }
183  }
184
185  /**
186   * If we created a transaction commit it. We have successfully processed all
187   * the rows.
188   */
189  protected void commitTransactionIfCreated() {
190    if (createdTransaction) {
191      transaction.commit();
192      logger.info("Committed transaction");
193    }
194  }
195
196  /**
197   * Rollback the transaction if we where not successful in processing all the
198   * rows.
199   */
200  protected void rollbackTransactionIfCreated(Throwable e) {
201    if (createdTransaction) {
202      transaction.rollback(e);
203      logger.info("Rolled back transaction");
204    }
205  }
206
207}