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}