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 log = 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 // assumes single bean or Cascade.PERSIST will save any 128 // related beans (e.g. customer -> customer.billingAddress 129 server.save(bean, transaction); 130 if (logInfoFrequency > 0 && (row % logInfoFrequency == 0)) { 131 log.debug("processed {} rows", row); 132 } 133 } 134 135 /** 136 * Commit the transaction if one was created. 137 */ 138 @Override 139 public void end(int row) { 140 commitTransactionIfCreated(); 141 exeTime = System.currentTimeMillis() - startTime; 142 log.info("Csv finished, rows[{}] exeMillis[{}]", row, exeTime); 143 } 144 145 /** 146 * Rollback the transaction if one was created. 147 */ 148 @Override 149 public void endWithError(int row, Exception e) { 150 rollbackTransactionIfCreated(e); 151 } 152 153 /** 154 * Create a transaction if one is not already active and set its batch mode 155 * and batch size. 156 */ 157 protected void initTransactionIfRequired() { 158 transaction = server.currentTransaction(); 159 if (transaction == null || !transaction.isActive()) { 160 transaction = server.beginTransaction(); 161 createdTransaction = true; 162 if (persistBatchSize > 1) { 163 log.debug("Creating transaction, batchSize[{}]", persistBatchSize); 164 transaction.setBatchMode(true); 165 transaction.setBatchSize(persistBatchSize); 166 transaction.setGetGeneratedKeys(false); 167 } else { 168 // explicitly turn off JDBC batching in case 169 // is has been turned on globally 170 transaction.setBatchMode(false); 171 log.debug("Creating transaction with no JDBC batching"); 172 } 173 } 174 } 175 176 /** 177 * If we created a transaction commit it. We have successfully processed all 178 * the rows. 179 */ 180 protected void commitTransactionIfCreated() { 181 if (createdTransaction) { 182 transaction.commit(); 183 log.debug("Committed transaction"); 184 } 185 } 186 187 /** 188 * Rollback the transaction if we where not successful in processing all the 189 * rows. 190 */ 191 protected void rollbackTransactionIfCreated(Throwable e) { 192 if (createdTransaction) { 193 transaction.rollback(e); 194 log.debug("Rolled back transaction"); 195 } 196 } 197 198}