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}