package net.techreadiness.batch;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import javax.inject.Inject;
import net.techreadiness.service.FileService;
import net.techreadiness.service.exception.ServiceException;
import net.techreadiness.service.exception.ValidationServiceException;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.core.annotation.OnReadError;
import org.springframework.batch.core.annotation.OnSkipInProcess;
import org.springframework.batch.core.annotation.OnSkipInWrite;
import org.springframework.batch.item.file.FlatFileParseException;
import org.springframework.batch.item.file.transform.IncorrectLineLengthException;
import org.springframework.batch.item.file.transform.IncorrectTokenCountException;
import org.springframework.context.MessageSource;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
public class CoreItemListener extends AbstractServiceContextProvider {
@Inject
FileService fileService;
@Inject
private MessageSource messageSource;
@Inject
private PlatformTransactionManager txManager;
private static String ERROR_DATA_FILE_SUFFIX = "-error.csv";
private static String ERROR_MESSAGE_FILE_SUFFIX = "-error-messages.txt";
private String fileName;
private String errorDataFileName = null;
private String errorMessageFileName = null;
protected final Logger log = LoggerFactory.getLogger(this.getClass());
@OnReadError
public void onReadError(Exception e) {
TransactionStatus transaction = null;
try {
transaction = txManager.getTransaction(new DefaultTransactionDefinition(
TransactionDefinition.PROPAGATION_REQUIRES_NEW));
FlatFileParseException ffpe = getThrowable(e, FlatFileParseException.class);
if (ffpe == null) {
// TODO Handle error
log.error("#### Encountered skipable Exception on read", e);
} else {
Map<String, List<String>> errorMap = Maps.newHashMap();
List<String> errorList = Lists.newArrayList();
errorMap.put("readError", errorList);
IncorrectTokenCountException itce = getThrowable(e, IncorrectTokenCountException.class);
IncorrectLineLengthException ille = getThrowable(e, IncorrectLineLengthException.class);
SpelEvaluationException see = getThrowable(e, SpelEvaluationException.class);
if (itce != null) {
String message = getMessage("file.incorrectTokenCount", Integer.valueOf(ffpe.getLineNumber()),
itce.getExpectedCount(), itce.getActualCount());
errorList.add(message);
} else if (ille != null) {
String message = getMessage("file.incorrectLineLength", Integer.valueOf(ffpe.getLineNumber()),
ille.getExpectedLength(), ille.getActualLength());
errorList.add(message);
} else if (see != null) {
String msg = getMessage("file.bindingError", see.getInserts());
errorList.add(msg);
} else {
String message = getMessage("file.genericParseError", Integer.valueOf(ffpe.getLineNumber()),
ffpe.getInput());
errorList.add(message);
}
createFileErrorEntries(ffpe.getLineNumber(), errorMap);
writeErrorMessagesToFile(ffpe.getLineNumber(), errorMap);
writeErrorLineDataToFile(ffpe.getInput());
}
} finally {
if (transaction != null) {
txManager.commit(transaction);
}
}
}
private static <T extends Throwable> T getThrowable(Throwable t, Class<T> type) {
int index = ExceptionUtils.indexOfThrowable(t, type);
if (index < 0) {
return null;
}
return (T) ExceptionUtils.getThrowableList(t).get(index);
}
protected String getMessage(String key, Object... params) {
String message = messageSource.getMessage(key, params, Locale.getDefault());
return message;
}
@OnSkipInProcess
public void onSkipInProcess(BaseData item, Throwable t) {
if (t instanceof ValidationServiceException) {
ValidationServiceException vse = (ValidationServiceException) t;
createFileErrorEntries(item.getLineNumber(), vse.getBatchErrorMap());
writeErrorMessagesToFile(item.getLineNumber(), vse.getBatchErrorMap());
writeErrorLineDataToFile(item.getRawData());
} else {
log.error("#### Encountered skipable Exception on read", t);
}
}
@OnSkipInWrite
public void onSkipInWrite(BaseData item, Throwable t) {
Map<String, List<String>> errors = Maps.newHashMap();
Integer lineNumber = Integer.valueOf(item.getLineNumber());
if (t instanceof ValidationServiceException) {
ValidationServiceException vse = (ValidationServiceException) t;
errors = vse.getBatchErrorMap();
} else if (t instanceof ServiceException && StringUtils.isNotBlank(t.getMessage())) {
List<String> error = Lists.newArrayList(t.getMessage());
errors.put("serviceError", error);
} else {
Integer numItems = Integer.valueOf(1);
Integer firstLine = Integer.valueOf(item.getLineNumber());
String msg = getMessage("file.writeFailed", new Object[] { numItems, firstLine });
List<String> error = Lists.newArrayList(msg);
errors.put("writeError", error);
}
createFileErrorEntries(lineNumber, errors);
writeErrorMessagesToFile(lineNumber, errors);
writeErrorLineDataToFile(item.getRawData());
}
private void writeErrorMessagesToFile(Integer lineNumber, Map<String, List<String>> errorMap) {
if (errorMessageFileName == null) {
// our original file looks something like: file:/tmp/files/ORG-Import-7-3453535345
errorMessageFileName = fileName.replaceFirst("file:", "") + ERROR_MESSAGE_FILE_SUFFIX;
File errorFile = new File(errorMessageFileName);
try {
errorFile.createNewFile();
} catch (IOException e) {
log.error("Unable to create message error file for file: " + fileName, e);
}
// update file with data error file name
net.techreadiness.service.object.File file = fileService.getById(getServiceContext(), getFileId());
file.setErrorMessageFilename(StringUtils.substringAfterLast(errorMessageFileName, File.separator));
fileService.addOrUpdate(getServiceContext(), file);
}
if (errorMap != null) {
try (BufferedWriter out = new BufferedWriter(new FileWriter(errorMessageFileName, true))){
for (Entry<String, List<String>> entry : errorMap.entrySet()) {
for (String s : entry.getValue()) {
out.write(lineNumber + " : " + s + "\n");
log.info(getFileName() + " : " + lineNumber + " : " + entry.getKey() + " - " + s);
}
}
} catch (IOException e) {
log.error("Unable to write to error message file for file: " + fileName, e);
}
}
}
private void writeErrorLineDataToFile(String rawData) {
if (errorDataFileName == null) {
// our original file looks something like: file:/tmp/files/ORG-Import-7-3453535345
errorDataFileName = fileName.replaceFirst("file:", "") + ERROR_DATA_FILE_SUFFIX;
File errorFile = new File(errorDataFileName);
try {
errorFile.createNewFile();
} catch (IOException e) {
log.error("Unable to create error file for file: " + fileName, e);
}
// update file with data error file name
net.techreadiness.service.object.File file = fileService.getById(getServiceContext(), getFileId());
file.setErrorDataFilename(StringUtils.substringAfterLast(errorDataFileName, File.separator));
fileService.addOrUpdate(getServiceContext(), file);
}
try (BufferedWriter out = new BufferedWriter(new FileWriter(errorDataFileName, true))) {
out.write(rawData + "\n");
out.close();
} catch (IOException e) {
log.error("Unable to write to error file for file: " + fileName, e);
}
}
private void createFileErrorEntries(Integer lineNumber, Map<String, List<String>> errors) {
fileService.addErrors(getServiceContext(), getFileId(), lineNumber, errors);
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
}