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}