package ca.intelliware.ihtsdo.mlds.service.affiliatesimport;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.joda.time.Instant;
import org.joda.time.LocalDate;
import org.springframework.stereotype.Service;
import ca.intelliware.ihtsdo.mlds.domain.Affiliate;
import ca.intelliware.ihtsdo.mlds.domain.AffiliateDetails;
import ca.intelliware.ihtsdo.mlds.domain.AffiliateType;
import ca.intelliware.ihtsdo.mlds.domain.Application;
import ca.intelliware.ihtsdo.mlds.domain.Application.ApplicationType;
import ca.intelliware.ihtsdo.mlds.domain.ApprovalState;
import ca.intelliware.ihtsdo.mlds.domain.CommercialUsage;
import ca.intelliware.ihtsdo.mlds.domain.CommercialUsagePeriod;
import ca.intelliware.ihtsdo.mlds.domain.PrimaryApplication;
import ca.intelliware.ihtsdo.mlds.domain.UsageContext;
import ca.intelliware.ihtsdo.mlds.domain.UsageReportState;
import ca.intelliware.ihtsdo.mlds.repository.AffiliateDetailsRepository;
import ca.intelliware.ihtsdo.mlds.repository.AffiliateRepository;
import ca.intelliware.ihtsdo.mlds.repository.ApplicationRepository;
import ca.intelliware.ihtsdo.mlds.repository.CommercialUsageRepository;
import ca.intelliware.ihtsdo.mlds.service.AffiliateAuditEvents;
@Service
@Transactional
public class AffiliatesImporterService {
@Resource ApplicationRepository applicationRepository;
@Resource AffiliateRepository affiliateRepository;
@Resource AffiliateDetailsRepository affiliateDetailsRepository;
@Resource CommercialUsageRepository commercialUsageRepository;
@Resource AffiliateAuditEvents affiliateAuditEvents;
@Resource AffiliatesMapper affiliatesMapper;
@PersistenceContext
private EntityManager entityManager;
public ImportResult importFromCSV(String contents) throws IOException {
ImportResult result = new ImportResult();
long start = System.currentTimeMillis();
try {
importContents(contents, result);
} catch (Exception e) {
result.addException(e);
}
result.durationMillis = System.currentTimeMillis() - start;
return result;
}
private void importContents(String contents, ImportResult result) throws IOException {
List<LineRecord> lines = parseFile(contents);
result.readRows = lines.size();
validateLines(lines, result);
if (result.success) {
processAffiliateRecords(lines, result);
}
}
private void processAffiliateRecords(List<LineRecord> lines, ImportResult result) {
result.importedRecords = 0;
result.setNewRecords(0);
result.updatedRecords = 0;
for (int i = 0; i < lines.size(); i++) {
LineRecord lineRecord = lines.get(i);
if (!lineRecord.header && !lineRecord.isBlank) {
try {
processLineRecord(lineRecord, result);
result.importedRecords += 1;
} catch (Exception e) {
result.addError(lineRecord, "Failed to populate record: "+e);
}
}
}
}
void processLineRecord(LineRecord record, ImportResult result) throws IllegalArgumentException, IllegalAccessException, InstantiationException {
Affiliate affiliate = findExistingAffiliateForUpdate(record);
Application application;
if (affiliate == null) {
application = createApprovedAffiliate(record);
result.setNewRecords(result.getNewRecords() + 1);
} else {
application = updateAffiliate(affiliate,record);
result.updatedRecords += 1;
}
result.setSourceMemberKey(application.getMember().getKey());
clearSession();
}
/** For bulk performance clear pending changes from hibernate session */
private void clearSession() {
entityManager.flush();
entityManager.clear();
}
private Affiliate findExistingAffiliateForUpdate(LineRecord record) throws IllegalAccessException, InstantiationException {
Affiliate tmpAffiliate = new Affiliate();
PrimaryApplication tmpApplication = new PrimaryApplication();
populateWithAll(tmpAffiliate, record, Affiliate.class);
populateWithAll(tmpApplication, record, Application.class);
Affiliate affiliate = affiliateRepository.findByImportKeyAndHomeMember(tmpAffiliate.getImportKey(),tmpApplication.getMember());
return affiliate;
}
private Application updateAffiliate(Affiliate affiliate, LineRecord record) throws IllegalAccessException, InstantiationException {
populateWithAll(affiliate, record, Affiliate.class);
populateWithAll(affiliate.getAffiliateDetails(), record, AffiliateDetails.class);
PrimaryApplication importApplication = createApprovedPrimaryApplication(record, ApplicationType.IMPORT);
applicationRepository.save(importApplication);
affiliate.addApplication(importApplication);
affiliateAuditEvents.logUpdateByImport(affiliate);
return importApplication;
}
private Application createApprovedAffiliate(LineRecord record) throws IllegalArgumentException, IllegalAccessException, InstantiationException {
//FIXME use services for much of this where possible... with a create and approve steps
PrimaryApplication application = createApprovedPrimaryApplication(record, ApplicationType.PRIMARY);
application = applicationRepository.save(application);
CommercialUsage commercialUsage = createCommercialUsage(record, application.getType());
commercialUsage = commercialUsageRepository.save(commercialUsage);
application.setCommercialUsage(commercialUsage);
application = applicationRepository.save(application);
AffiliateDetails affiliateDetails = createPopulateAffiliateDetails(record);
affiliateDetails = affiliateDetailsRepository.save(affiliateDetails);
Affiliate affiliate = createAffiliate(record, application, affiliateDetails);
affiliate = affiliateRepository.save(affiliate);
affiliateAuditEvents.logCreationByImport(affiliate);
return application;
}
private Affiliate createAffiliate(LineRecord record, PrimaryApplication application, AffiliateDetails affiliateDetails) throws IllegalAccessException, InstantiationException {
Affiliate affiliate = new Affiliate();
affiliate.setAffiliateDetails(affiliateDetails);
populateWithAll(affiliate, record, Affiliate.class);
affiliate.addApplication(application);
affiliate.setApplication(application);
affiliate.setHomeMember(application.getMember());
affiliate.setType(application.getType());
affiliate.addCommercialUsage(application.getCommercialUsage());
return affiliate;
}
private CommercialUsage createCommercialUsage(LineRecord record, AffiliateType affiliateType) throws IllegalAccessException, InstantiationException {
CommercialUsage commercialUsage = new CommercialUsage();
CommercialUsagePeriod usagePeriod = createCurrentCommercialUsagePeriod();
commercialUsage.setStartDate(usagePeriod.getStartDate());
commercialUsage.setEndDate(usagePeriod.getEndDate());
UsageContext usageContext = new UsageContext();
populateWithAll(usageContext, record, UsageContext.class);
commercialUsage.setContext(usageContext);
commercialUsage.setState(UsageReportState.NOT_SUBMITTED);
commercialUsage.setType(affiliateType);
return commercialUsage;
}
private AffiliateDetails createPopulateAffiliateDetails(LineRecord record) throws IllegalAccessException, InstantiationException {
AffiliateDetails affiliateDetails = new AffiliateDetails();
populateWithAll(affiliateDetails, record, AffiliateDetails.class);
return affiliateDetails;
}
/**
* Create an application from the record
* @param record
* @param applicationType assumed to be one of Primary or Import (since Import extends Primary)
* @return
* @throws IllegalArgumentException
* @throws IllegalAccessException
* @throws InstantiationException
*/
private PrimaryApplication createApprovedPrimaryApplication(LineRecord record, ApplicationType applicationType) throws IllegalArgumentException, IllegalAccessException, InstantiationException {
PrimaryApplication application = (PrimaryApplication) Application.create(applicationType);
populateWithAll(application, record, Application.class);
application.setCompletedAt(Instant.now());
application.setSubmittedAt(Instant.now());
application.setApprovalState(ApprovalState.APPROVED);
populateWithAll(application, record, PrimaryApplication.class);
application.setAffiliateDetails(createPopulateAffiliateDetails(record));
return application;
}
private void populateWithAll(Object rootObject, LineRecord record, Class<?> matchingClazz) throws IllegalArgumentException, IllegalAccessException, InstantiationException {
record.setValuesOfMatchingClass(rootObject, matchingClazz, affiliatesMapper);
}
private void validateLines(List<LineRecord> lines, ImportResult result) {
for (LineRecord lineRecord : lines) {
lineRecord.validate(result, affiliatesMapper);
}
}
private List<LineRecord> parseFile(String contents) throws IOException {
List<LineRecord> records = new ArrayList<LineRecord>();
List<String> lines = IOUtils.readLines(new StringReader(contents));
boolean headerFound = false;
for (String line : lines) {
List<String> fields = parseLine(line);
LineRecord record = new LineRecord(records.size() + 1, fields, StringUtils.isBlank(line));
if (!headerFound && !record.isBlank) {
record.header = true;
headerFound = true;
}
records.add(record);
}
return records;
}
private List<String> parseLine(String line) {
String[] elements = line.split(AffiliateFileFormat.COLUMN_SEPARATOR_REGEX, -1);
for (int i = 0; i < elements.length; i++) {
elements[i] = StringUtils.trimToEmpty(elements[i]);
}
return Arrays.asList(elements);
}
//FIXME move this code to a more appropriate service...
private CommercialUsagePeriod createCurrentCommercialUsagePeriod() {
CommercialUsagePeriod period = new CommercialUsagePeriod();
if (LocalDate.now().getMonthOfYear() <= 6) {
period.setStartDate(LocalDate.now().dayOfYear().withMinimumValue());
period.setEndDate(LocalDate.now().monthOfYear().setCopy(6).dayOfMonth().withMaximumValue());
} else {
period.setStartDate(LocalDate.now().monthOfYear().setCopy(7).dayOfMonth().withMinimumValue());
period.setEndDate(LocalDate.now().monthOfYear().setCopy(12).dayOfMonth().withMaximumValue());
}
return period;
}
}