001package com.avaje.ebean.text.csv;
002
003import com.avaje.ebean.EbeanServer;
004import com.avaje.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 * @author rob
020 * 
021 * @param <T>
022 */
023public class DefaultCsvCallback<T> implements CsvCallback<T> {
024
025  private static final Logger logger = LoggerFactory.getLogger(DefaultCsvCallback.class);
026
027  /**
028   * The transaction to use (if not using CsvCallback).
029   */
030  protected Transaction transaction;
031
032  /**
033   * Flag set when we created the transaction.
034   */
035  protected boolean createdTransaction;
036
037  /**
038   * The EbeanServer used to save the beans.
039   */
040  protected EbeanServer server;
041
042  /**
043   * Used to log a message to indicate progress through large files.
044   */
045  protected final int logInfoFrequency;
046
047  /**
048   * The batch size used when saving the beans.
049   */
050  protected final int persistBatchSize;
051
052  /**
053   * The time the process started.
054   */
055  protected long startTime;
056
057  /**
058   * The execution time of the process.
059   */
060  protected long exeTime;
061
062  /**
063   * Construct with a default batch size of 30 and logging info messages every
064   * 1000 rows.
065   */
066  public DefaultCsvCallback() {
067    this(30, 1000);
068  }
069
070  /**
071   * Construct with explicit batch size and logging info frequency.
072   */
073  public DefaultCsvCallback(int persistBatchSize, int logInfoFrequency) {
074
075    this.persistBatchSize = persistBatchSize;
076    this.logInfoFrequency = logInfoFrequency;
077  }
078
079  /**
080   * Create a transaction if required.
081   */
082  public void begin(EbeanServer server) {
083    this.server = server;
084    this.startTime = System.currentTimeMillis();
085
086    initTransactionIfRequired();
087  }
088
089  /**
090   * Override to read the heading line.
091   * <p>
092   * This is only called if {@link CsvReader#setHasHeader(boolean,boolean)} is
093   * set to true.
094   * </p>
095   * <p>
096   * By default this does nothing (effectively ignoring the heading).
097   * </p>
098   */
099  public void readHeader(String[] line) {
100
101  }
102
103  /**
104   * Validate that the content is valid and return false if the row should be
105   * ignored.
106   * <p>
107   * By default this just returns true.
108   * </p>
109   * <p>
110   * Override this to add custom validation logic returning false if you want
111   * the row to be ignored. For example, if all the content is empty return
112   * false to ignore the row (rather than having the processing fail with some
113   * error).
114   * </p>
115   */
116  public boolean processLine(int row, String[] line) {
117    return true;
118  }
119
120  /**
121   * Will save the bean.
122   * <p>
123   * Override this method to customise the bean (set additional properties etc)
124   * or to control the saving of other related beans (when you can't/don't want
125   * to use Cascade.PERSIST etc).
126   * </p>
127   */
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  public void end(int row) {
143
144    commitTransactionIfCreated();
145
146    exeTime = System.currentTimeMillis() - startTime;
147    logger.info("Csv finished, rows[" + row + "] exeMillis[" + exeTime + "]");
148  }
149
150  /**
151   * Rollback the transaction if one was created.
152   */
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.setBatchGetGeneratedKeys(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}