/*
This file is part of Cyclos (www.cyclos.org).
A project of the Social Trade Organisation (www.socialtrade.org).
Cyclos is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
Cyclos is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Cyclos; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package nl.strohalm.cyclos.services.elements;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import nl.strohalm.cyclos.dao.members.imports.ImportedMemberDAO;
import nl.strohalm.cyclos.dao.members.imports.ImportedMemberRecordDAO;
import nl.strohalm.cyclos.dao.members.imports.MemberImportDAO;
import nl.strohalm.cyclos.entities.Relationship;
import nl.strohalm.cyclos.entities.access.MemberUser;
import nl.strohalm.cyclos.entities.access.User;
import nl.strohalm.cyclos.entities.accounts.MemberAccountType;
import nl.strohalm.cyclos.entities.accounts.MemberGroupAccountSettings;
import nl.strohalm.cyclos.entities.accounts.SystemAccountOwner;
import nl.strohalm.cyclos.entities.customization.fields.CustomField;
import nl.strohalm.cyclos.entities.customization.fields.CustomFieldValue;
import nl.strohalm.cyclos.entities.customization.fields.MemberCustomField;
import nl.strohalm.cyclos.entities.customization.fields.MemberCustomFieldValue;
import nl.strohalm.cyclos.entities.customization.fields.MemberRecordCustomField;
import nl.strohalm.cyclos.entities.customization.fields.MemberRecordCustomFieldValue;
import nl.strohalm.cyclos.entities.exceptions.EntityNotFoundException;
import nl.strohalm.cyclos.entities.groups.Group;
import nl.strohalm.cyclos.entities.groups.MemberGroup;
import nl.strohalm.cyclos.entities.members.Administrator;
import nl.strohalm.cyclos.entities.members.Member;
import nl.strohalm.cyclos.entities.members.imports.ImportedMember;
import nl.strohalm.cyclos.entities.members.imports.ImportedMemberQuery;
import nl.strohalm.cyclos.entities.members.imports.ImportedMemberRecord;
import nl.strohalm.cyclos.entities.members.imports.ImportedMemberRecordCustomFieldValue;
import nl.strohalm.cyclos.entities.members.imports.MemberImport;
import nl.strohalm.cyclos.entities.members.imports.MemberImportResult;
import nl.strohalm.cyclos.entities.members.records.MemberRecord;
import nl.strohalm.cyclos.entities.members.records.MemberRecordType;
import nl.strohalm.cyclos.entities.members.records.MemberRecordTypeQuery;
import nl.strohalm.cyclos.entities.settings.AccessSettings;
import nl.strohalm.cyclos.entities.settings.AccessSettings.UsernameGeneration;
import nl.strohalm.cyclos.entities.settings.LocalSettings;
import nl.strohalm.cyclos.services.accounts.AccountServiceLocal;
import nl.strohalm.cyclos.services.accounts.CreditLimitDTO;
import nl.strohalm.cyclos.services.customization.MemberCustomFieldServiceLocal;
import nl.strohalm.cyclos.services.customization.MemberRecordCustomFieldServiceLocal;
import nl.strohalm.cyclos.services.fetch.FetchServiceLocal;
import nl.strohalm.cyclos.services.groups.GroupServiceLocal;
import nl.strohalm.cyclos.services.settings.SettingsServiceLocal;
import nl.strohalm.cyclos.services.transactions.PaymentServiceLocal;
import nl.strohalm.cyclos.services.transactions.TransactionContext;
import nl.strohalm.cyclos.services.transactions.TransferDTO;
import nl.strohalm.cyclos.utils.CacheCleaner;
import nl.strohalm.cyclos.utils.CustomFieldHelper;
import nl.strohalm.cyclos.utils.DateHelper;
import nl.strohalm.cyclos.utils.HashHandler;
import nl.strohalm.cyclos.utils.TimePeriod;
import nl.strohalm.cyclos.utils.access.LoggedUser;
import nl.strohalm.cyclos.utils.conversion.CalendarConverter;
import nl.strohalm.cyclos.utils.conversion.CoercionHelper;
import nl.strohalm.cyclos.utils.csv.CSVReader;
import nl.strohalm.cyclos.utils.csv.UnknownColumnException;
import nl.strohalm.cyclos.utils.query.PageHelper;
import nl.strohalm.cyclos.utils.query.QueryParameters.ResultType;
import nl.strohalm.cyclos.utils.validation.EmailValidation;
import nl.strohalm.cyclos.utils.validation.LengthValidation;
import nl.strohalm.cyclos.utils.validation.MaxLengthError;
import nl.strohalm.cyclos.utils.validation.MinLengthError;
import nl.strohalm.cyclos.utils.validation.RegexValidation;
import nl.strohalm.cyclos.utils.validation.RequiredError;
import nl.strohalm.cyclos.utils.validation.UniqueError;
import nl.strohalm.cyclos.utils.validation.ValidationError;
import nl.strohalm.cyclos.utils.validation.ValidationException;
import nl.strohalm.cyclos.utils.validation.Validator;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
/**
* Handles importing members
*
* @author luis
*/
public class MemberImportServiceImpl implements MemberImportServiceLocal {
private FetchServiceLocal fetchService;
private ElementServiceLocal elementService;
private MemberRecordServiceLocal memberRecordService;
private MemberCustomFieldServiceLocal memberCustomFieldService;
private MemberRecordCustomFieldServiceLocal memberRecordCustomFieldService;
private AccountServiceLocal accountService;
private PaymentServiceLocal paymentService;
private SettingsServiceLocal settingsService;
private GroupServiceLocal groupService;
private MemberRecordTypeServiceLocal memberRecordTypeService;
private HashHandler hashHandler;
private MemberImportDAO memberImportDao;
private ImportedMemberDAO importedMemberDao;
private ImportedMemberRecordDAO importedMemberRecordDao;
private CustomFieldHelper customFieldHelper;
@Override
public MemberImportResult getSummary(final MemberImport memberImport) {
final MemberImportResult result = new MemberImportResult();
final ImportedMemberQuery query = new ImportedMemberQuery();
query.setPageForCount();
query.setMemberImport(memberImport);
// Get the total number of members
query.setStatus(ImportedMemberQuery.Status.ALL);
result.setTotal(PageHelper.getTotalCount(importedMemberDao.search(query)));
// Get the number of members with error
query.setStatus(ImportedMemberQuery.Status.ERROR);
result.setErrors(PageHelper.getTotalCount(importedMemberDao.search(query)));
// Get the total of transactions
result.setCredits(importedMemberDao.getTransactions(memberImport, true));
result.setDebits(importedMemberDao.getTransactions(memberImport, false));
return result;
}
@Override
public MemberImport importMembers(MemberImport memberImport, final InputStream data) {
final LocalSettings localSettings = settingsService.getLocalSettings();
final AccessSettings accessSettings = settingsService.getAccessSettings();
// Validate and save the import
getValidator().validate(memberImport);
final MemberGroup group = fetchService.fetch(memberImport.getGroup());
memberImport.setGroup(group);
memberImport.setBy(LoggedUser.<Administrator> element());
memberImport.setDate(Calendar.getInstance());
memberImport = memberImportDao.insert(memberImport);
// Get the account settings
MemberGroupAccountSettings accountSettings = null;
final MemberAccountType accountType = memberImport.getAccountType();
if (accountType != null) {
accountSettings = groupService.loadAccountSettings(group.getId(), accountType.getId());
}
// Find out the custom fields;
List<MemberCustomField> customFields = memberCustomFieldService.list();
customFields = customFieldHelper.onlyForGroup(customFields, group);
final Map<String, CustomField> customFieldMap = new HashMap<String, CustomField>(customFields.size());
for (final MemberCustomField customField : customFields) {
customFieldMap.put(customField.getInternalName().toLowerCase(), fetchService.fetch(customField, CustomField.Relationships.POSSIBLE_VALUES));
}
// Find the record types
final MemberRecordTypeQuery mrtQuery = new MemberRecordTypeQuery();
mrtQuery.fetch(MemberRecordType.Relationships.FIELDS);
Collection<Group> mrtGroups = new ArrayList<Group>();
mrtGroups.add(group);
mrtQuery.setGroups(mrtGroups);
final List<MemberRecordType> recordTypes = memberRecordTypeService.search(mrtQuery);
final Map<String, MemberRecordType> recordTypeMap = new HashMap<String, MemberRecordType>(recordTypes.size());
final Map<MemberRecordType, Map<String, CustomField>> recordTypeFieldsMap = new HashMap<MemberRecordType, Map<String, CustomField>>(recordTypes.size());
for (final MemberRecordType recordType : recordTypes) {
final String lowercaseName = recordType.getName().toLowerCase();
recordTypeMap.put(lowercaseName, recordType);
// Get the custom fields for this record type
final Map<String, CustomField> fields = new HashMap<String, CustomField>();
for (final MemberRecordCustomField customField : recordType.getFields()) {
fields.put(customField.getInternalName().toLowerCase(), fetchService.fetch(customField, CustomField.Relationships.POSSIBLE_VALUES));
}
recordTypeFieldsMap.put(recordType, fields);
}
// We need to read the first line in order to discover which columns exist
final char stringQuote = CoercionHelper.coerce(Character.TYPE, localSettings.getCsvStringQuote().getValue());
final char valueSeparator = CoercionHelper.coerce(Character.TYPE, localSettings.getCsvValueSeparator().getValue());
BufferedReader in = null;
List<String> headers;
try {
in = new BufferedReader(new InputStreamReader(data, localSettings.getCharset()));
headers = CSVReader.readLine(in, stringQuote, valueSeparator);
} catch (final Exception e) {
throw new ValidationException("memberImport.invalidFormat");
}
final Set<String> usedUsernames = new HashSet<String>();
// Import each member
try {
final CacheCleaner cacheCleaner = new CacheCleaner(fetchService);
int lineNumber = 2; // The first line is the header
List<String> values;
while ((values = CSVReader.readLine(in, stringQuote, valueSeparator)) != null) {
if (values.isEmpty()) {
continue;
}
importMember(memberImport, accountSettings, lineNumber, customFieldMap, recordTypeMap, recordTypeFieldsMap, localSettings, accessSettings, headers, values, usedUsernames);
lineNumber++;
cacheCleaner.clearCache();
}
} catch (final IOException e) {
throw new ValidationException("memberImport.errorReading");
} finally {
IOUtils.closeQuietly(in);
}
return memberImport;
}
@Override
public MemberImport load(final Long id, final Relationship... fetch) throws EntityNotFoundException {
return memberImportDao.load(id, fetch);
}
@Override
public void processImport(MemberImport memberImport, final boolean sendActivationMail) {
memberImport = fetchService.fetch(memberImport, MemberImport.Relationships.GROUP, MemberImport.Relationships.ACCOUNT_TYPE, MemberImport.Relationships.INITIAL_CREDIT_TRANSFER_TYPE, MemberImport.Relationships.INITIAL_DEBIT_TRANSFER_TYPE);
// Iterate through each member
final ImportedMemberQuery memberQuery = new ImportedMemberQuery();
memberQuery.setResultType(ResultType.ITERATOR);
memberQuery.setMemberImport(memberImport);
memberQuery.setStatus(ImportedMemberQuery.Status.SUCCESS);
int count = 0;
for (final ImportedMember importedMember : importedMemberDao.search(memberQuery)) {
processMember(memberImport, importedMember, sendActivationMail);
if (count % 20 == 0) {
// Every few records, clear the cache to avoid too many objects in memory
fetchService.clearCache();
}
count++;
}
// Delete the import after processing it
memberImportDao.delete(memberImport.getId());
}
@Override
public void purgeOld(Calendar time) {
// Only purge after 1 day of idleness
time = new TimePeriod(1, TimePeriod.Field.DAYS).remove(time);
for (final MemberImport memberImport : memberImportDao.listBefore(time)) {
memberImportDao.delete(memberImport.getId());
}
}
@Override
public List<ImportedMember> searchImportedMembers(final ImportedMemberQuery params) {
return importedMemberDao.search(params);
}
public void setAccountServiceLocal(final AccountServiceLocal accountService) {
this.accountService = accountService;
}
public void setCustomFieldHelper(final CustomFieldHelper customFieldHelper) {
this.customFieldHelper = customFieldHelper;
}
public void setElementServiceLocal(final ElementServiceLocal elementService) {
this.elementService = elementService;
}
public void setFetchServiceLocal(final FetchServiceLocal fetchService) {
this.fetchService = fetchService;
}
public void setGroupServiceLocal(final GroupServiceLocal groupService) {
this.groupService = groupService;
}
public void setHashHandler(final HashHandler hashHandler) {
this.hashHandler = hashHandler;
}
public void setImportedMemberDao(final ImportedMemberDAO importedMemberDao) {
this.importedMemberDao = importedMemberDao;
}
public void setImportedMemberRecordDao(final ImportedMemberRecordDAO importedMemberRecordDao) {
this.importedMemberRecordDao = importedMemberRecordDao;
}
public void setMemberCustomFieldServiceLocal(final MemberCustomFieldServiceLocal memberCustomFieldService) {
this.memberCustomFieldService = memberCustomFieldService;
}
public void setMemberImportDao(final MemberImportDAO memberImportDao) {
this.memberImportDao = memberImportDao;
}
public void setMemberRecordCustomFieldServiceLocal(final MemberRecordCustomFieldServiceLocal memberRecordCustomFieldService) {
this.memberRecordCustomFieldService = memberRecordCustomFieldService;
}
public void setMemberRecordServiceLocal(final MemberRecordServiceLocal memberRecordService) {
this.memberRecordService = memberRecordService;
}
public void setMemberRecordTypeServiceLocal(final MemberRecordTypeServiceLocal memberRecordTypeService) {
this.memberRecordTypeService = memberRecordTypeService;
}
public void setPaymentServiceLocal(final PaymentServiceLocal paymentService) {
this.paymentService = paymentService;
}
public void setSettingsServiceLocal(final SettingsServiceLocal settingsService) {
this.settingsService = settingsService;
}
@Override
public void validate(final MemberImport memberImport) throws ValidationException {
getValidator().validate(memberImport);
}
private Validator getValidator() {
final Validator validator = new Validator("memberImport");
validator.property("group").required();
return validator;
}
private void importMember(final MemberImport memberImport, final MemberGroupAccountSettings accountSettings, final int lineNumber, final Map<String, CustomField> customFieldMap, final Map<String, MemberRecordType> recordTypeMap, final Map<MemberRecordType, Map<String, CustomField>> recordTypeFieldsMap, final LocalSettings localSettings, final AccessSettings accessSettings, final List<String> headers, final List<String> values, final Set<String> importedUsernames) {
final Map<MemberRecordType, ImportedMemberRecord> records = new HashMap<MemberRecordType, ImportedMemberRecord>();
final Map<String, String> customFieldValues = new HashMap<String, String>();
final Map<MemberRecordType, Map<String, String>> recordFieldValues = new HashMap<MemberRecordType, Map<String, String>>();
// Insert the member
ImportedMember member = new ImportedMember();
member.setSalt(hashHandler.newSalt());
member.setLineNumber(lineNumber);
member.setImport(memberImport);
member.setStatus(ImportedMember.Status.SUCCESS);
member = importedMemberDao.insert(member);
final Calendar today = DateHelper.truncate(Calendar.getInstance());
try {
member.setCustomValues(new ArrayList<MemberCustomFieldValue>());
final CalendarConverter dateConverter = localSettings.getRawDateConverter();
// Process each field. Field names are lowercased to ignore case
for (int i = 0; i < headers.size() && i < values.size(); i++) {
final String field = StringUtils.trimToEmpty(headers.get(i)).toLowerCase();
final String value = StringUtils.trimToNull(values.get(i));
if ("name".equals(field)) {
member.setName(value);
} else if ("username".equals(field)) {
member.setUsername(value);
} else if ("password".equals(field)) {
member.setPassword(hashHandler.hash(member.getSalt(), value));
} else if ("email".equals(field)) {
member.setEmail(value);
} else if ("creationdate".equals(field)) {
try {
final Calendar creationDate = dateConverter.valueOf(value);
if (creationDate != null) {
if (creationDate.after(today) || creationDate.get(Calendar.YEAR) < 1950) {
throw new Exception();
}
member.setCreationDate(creationDate);
}
} catch (final Exception e) {
member.setStatus(ImportedMember.Status.INVALID_CREATION_DATE);
member.setErrorArgument1(value);
break;
}
} else if ("balance".equals(field)) {
try {
member.setInitialBalance(localSettings.getNumberConverter().valueOf(value));
} catch (final Exception e) {
member.setStatus(ImportedMember.Status.INVALID_BALANCE);
member.setErrorArgument1(value);
break;
}
} else if ("creditlimit".equals(field)) {
try {
BigDecimal limit = localSettings.getNumberConverter().valueOf(value);
// Ensure the limit is positive
if (limit != null) {
limit = limit.abs();
}
member.setCreditLimit(limit);
} catch (final Exception e) {
member.setStatus(ImportedMember.Status.INVALID_CREDIT_LIMIT);
member.setErrorArgument1(value);
break;
}
} else if ("uppercreditlimit".equals(field)) {
try {
member.setUpperCreditLimit(localSettings.getNumberConverter().valueOf(value));
} catch (final Exception e) {
member.setStatus(ImportedMember.Status.INVALID_UPPER_CREDIT_LIMIT);
member.setErrorArgument1(value);
break;
}
} else if (customFieldMap.containsKey(field)) {
// Create a custom field value
CustomField customField = customFieldMap.get(field);
final MemberCustomFieldValue fieldValue = new MemberCustomFieldValue();
fieldValue.setField(customField);
fieldValue.setValue(preprocessCustomFieldValue(customField, value));
member.getCustomValues().add(fieldValue);
customFieldValues.put(field, value);
} else if (field.contains(".")) {
// A record type value
final String[] parts = field.split("\\.");
// Find the record type
final String recordTypeName = parts[0];
final MemberRecordType recordType = recordTypeMap.get(recordTypeName);
if (recordType == null) {
member.setStatus(ImportedMember.Status.INVALID_RECORD_TYPE);
member.setErrorArgument1(recordTypeName);
break;
}
// Find the custom field
final String recordTypeField = parts[1];
final Map<String, CustomField> fieldsMap = recordTypeFieldsMap.get(recordType);
final CustomField customField = fieldsMap.get(recordTypeField);
if (customField == null) {
member.setStatus(ImportedMember.Status.INVALID_RECORD_TYPE_FIELD);
member.setErrorArgument1(recordTypeName);
member.setErrorArgument2(recordTypeField);
break;
}
// Find the imported member record
ImportedMemberRecord record = records.get(recordType);
if (record == null) {
// None yet - create a new one
record = new ImportedMemberRecord();
record.setMember(member);
record.setType(recordType);
record = importedMemberRecordDao.insert(record);
record.setCustomValues(new ArrayList<ImportedMemberRecordCustomFieldValue>());
records.put(recordType, record);
}
// Set the custom field
final ImportedMemberRecordCustomFieldValue fieldValue = new ImportedMemberRecordCustomFieldValue();
fieldValue.setField(customField);
fieldValue.setValue(preprocessCustomFieldValue(customField, value));
record.getCustomValues().add(fieldValue);
// Store the field value in a map
Map<String, String> fieldValues = recordFieldValues.get(recordType);
if (fieldValues == null) {
fieldValues = new HashMap<String, String>();
recordFieldValues.put(recordType, fieldValues);
}
fieldValues.put(recordTypeField, value);
} else {
throw new UnknownColumnException(field);
}
}
// When there was an error, stop processing
if (member.getStatus() != ImportedMember.Status.SUCCESS) {
return;
}
// Validate some data
if (member.getName() == null) {
// Name is always required
member.setStatus(ImportedMember.Status.MISSING_NAME);
return;
}
final String username = member.getUsername();
if (accessSettings.getUsernameGeneration() == UsernameGeneration.NONE && username == null) {
// Username is required when it's not generated
member.setStatus(ImportedMember.Status.MISSING_USERNAME);
return;
}
// Validate the username
if (username != null) {
// Check the username format
ValidationError error = new RegexValidation(accessSettings.getUsernameRegex()).validate(null, null, username);
if (error == null) {
// Check the username length
error = new LengthValidation(accessSettings.getUsernameLength()).validate(null, null, username);
}
if (error != null) {
member.setStatus(ImportedMember.Status.INVALID_USERNAME);
member.setErrorArgument1(username);
return;
}
// Check if username is duplicated in this import
if (!importedUsernames.add(username)) {
member.setStatus(ImportedMember.Status.USERNAME_ALREADY_IN_USE);
member.setErrorArgument1(username);
return;
}
// Check if username is already used by another member in cyclos
try {
elementService.loadUser(username);
// If an user could be loaded, it means that the username is already in use
member.setStatus(ImportedMember.Status.USERNAME_ALREADY_IN_USE);
member.setErrorArgument1(username);
return;
} catch (final EntityNotFoundException e) {
// Ok - not used yet
}
}
if (member.getEmail() == null && localSettings.isEmailRequired()) {
// Mail is required
member.setStatus(ImportedMember.Status.MISSING_EMAIL);
return;
}
if (EmailValidation.instance().validate(null, null, member.getEmail()) != null) {
// Mail format is invalid
member.setStatus(ImportedMember.Status.INVALID_EMAIL);
member.setErrorArgument1(member.getEmail());
return;
}
if (memberImport.getAccountType() == null) {
// Nothing related to accounts will be imported
member.setInitialBalance(null);
member.setCreditLimit(null);
member.setUpperCreditLimit(null);
} else {
if (member.getCreditLimit() == null) {
// Get the default group credit limit
member.setCreditLimit(accountSettings.getDefaultCreditLimit());
}
if (member.getUpperCreditLimit() == null) {
// Get the default group upper credit limit
member.setUpperCreditLimit(accountSettings.getDefaultUpperCreditLimit());
}
final BigDecimal balance = member.getInitialBalance();
if (balance != null) {
double balanceValue = balance.doubleValue();
if (balanceValue > 0 && memberImport.getInitialCreditTransferType() == null) {
// There was an initial credit, but no TT for it: ignore
member.setInitialBalance(null);
balanceValue = 0;
} else if (balanceValue < 0 && memberImport.getInitialDebitTransferType() == null) {
// There was an initial debit, but no TT for it: ignore
member.setInitialBalance(null);
balanceValue = 0;
}
final BigDecimal creditLimit = member.getCreditLimit();
if (creditLimit != null && balanceValue < 0 && balance.compareTo(creditLimit.negate()) < 0) {
// When the initial balance is negative, ensure the credit limit accommodates it
member.setStatus(ImportedMember.Status.BALANCE_LOWER_THAN_CREDIT_LIMIT);
return;
}
final BigDecimal upperCreditLimit = member.getUpperCreditLimit();
if (upperCreditLimit != null && balanceValue > 0 && balance.compareTo(upperCreditLimit) > 0) {
// When the initial balance is positive, ensure the credit limit accommodates it
member.setStatus(ImportedMember.Status.BALANCE_UPPER_THAN_CREDIT_LIMIT);
return;
}
}
}
// Save the custom field values
try {
memberCustomFieldService.saveValues(member);
} catch (final Exception e) {
member.setStatus(ImportedMember.Status.INVALID_CUSTOM_FIELD);
if (e instanceof ValidationException) {
final ValidationException vex = (ValidationException) e;
final Map<String, Collection<ValidationError>> errorsByProperty = vex.getErrorsByProperty();
if (MapUtils.isNotEmpty(errorsByProperty)) {
final String fieldName = errorsByProperty.keySet().iterator().next();
final String displayName = vex.getDisplayNameByProperty().get(fieldName);
member.setErrorArgument1(StringUtils.isEmpty(displayName) ? fieldName : displayName);
final String fieldValue = StringUtils.trimToNull(customFieldValues.get(fieldName.toLowerCase()));
if (CollectionUtils.isNotEmpty(errorsByProperty.get(fieldName))) {
ValidationError ve = errorsByProperty.get(fieldName).iterator().next();
if (ve instanceof UniqueError) {
member.setStatus(ImportedMember.Status.INVALID_CUSTOM_FIELD_VALUE_UNIQUE);
member.setErrorArgument2(ve.getArguments().iterator().next().toString());
} else if (ve instanceof RequiredError) {
member.setStatus(ImportedMember.Status.MISSING_CUSTOM_FIELD);
} else if (ve instanceof MaxLengthError) {
member.setStatus(ImportedMember.Status.INVALID_CUSTOM_FIELD_VALUE_MAX_LENGTH);
member.setErrorArgument2(ve.getArguments().iterator().next().toString());
} else if (ve instanceof MinLengthError) {
member.setStatus(ImportedMember.Status.INVALID_CUSTOM_FIELD_VALUE_MIN_LENGTH);
member.setErrorArgument2(ve.getArguments().iterator().next().toString());
}
}
if (StringUtils.isEmpty(member.getErrorArgument2()) && fieldValue != null) {
member.setErrorArgument2(fieldValue);
}
}
}
return;
}
// Save each record field values
for (final ImportedMemberRecord record : records.values()) {
final MemberRecordType recordType = record.getType();
final Map<String, String> fieldValues = recordFieldValues.get(recordType);
// Check if the record is not empty
boolean empty = true;
for (final String value : fieldValues.values()) {
if (StringUtils.isNotEmpty(value)) {
empty = false;
break;
}
}
if (empty) {
// There are no fields for this record: remove the record itself
importedMemberRecordDao.delete(record.getId());
continue;
}
try {
memberRecordCustomFieldService.saveValues(record);
} catch (final Exception e) {
member.setStatus(ImportedMember.Status.INVALID_RECORD_FIELD);
if (e instanceof ValidationException) {
final ValidationException vex = (ValidationException) e;
final Map<String, Collection<ValidationError>> errorsByProperty = vex.getErrorsByProperty();
if (MapUtils.isNotEmpty(errorsByProperty)) {
final String fieldName = errorsByProperty.keySet().iterator().next();
member.setErrorArgument1(recordType.getName() + "." + fieldName);
final String fieldValue = StringUtils.trimToNull(fieldValues.get(fieldName));
if (fieldValue == null) {
// When validation failed and the field is null, it's actually missing
member.setStatus(ImportedMember.Status.MISSING_RECORD_FIELD);
} else {
member.setErrorArgument2(fieldValue);
}
}
}
return;
}
}
} catch (final UnknownColumnException e) {
throw e;
} catch (final Exception e) {
member.setStatus(ImportedMember.Status.UNKNOWN_ERROR);
member.setErrorArgument1(e.toString());
} finally {
importedMemberDao.update(member);
}
}
private String preprocessCustomFieldValue(final CustomField field, String value) {
if (field.getType() == CustomField.Type.MEMBER) {
// Attempt to load by username
try {
User user = elementService.loadUser(value);
if (user instanceof MemberUser) {
value = user.getId().toString();
}
} catch (Exception e) {
// Ok, leave as is - will fail later
}
}
return value;
}
private void processMember(final MemberImport memberImport, ImportedMember importedMember, final boolean sendActivationMail) {
importedMember = fetchService.fetch(importedMember, ImportedMember.Relationships.CUSTOM_VALUES, ImportedMember.Relationships.RECORDS);
// Fill the member
Member member = new Member();
final MemberUser user = new MemberUser();
member.setUser(user);
member.setCreationDate(importedMember.getCreationDate());
member.setGroup(memberImport.getGroup());
member.setName(importedMember.getName());
user.setSalt(importedMember.getSalt());
user.setUsername(importedMember.getUsername());
user.setPassword(importedMember.getPassword());
member.setEmail(importedMember.getEmail());
// Set the custom values
fetchService.fetch(importedMember.getCustomValues(), CustomFieldValue.Relationships.FIELD, CustomFieldValue.Relationships.POSSIBLE_VALUE);
customFieldHelper.cloneFieldValues(importedMember, member);
// Insert the member
member = elementService.insertMember(member, !sendActivationMail, false);
// If the member is active and there was an imported creation date, set the activation date = imported creation date
if (member.getActivationDate() != null && importedMember.getCreationDate() != null) {
member.setActivationDate(importedMember.getCreationDate());
}
// Insert the records
final Collection<ImportedMemberRecord> records = importedMember.getRecords();
if (records != null) {
for (ImportedMemberRecord importedRecord : records) {
importedRecord = fetchService.fetch(importedRecord, ImportedMemberRecord.Relationships.CUSTOM_VALUES);
final MemberRecord record = new MemberRecord();
record.setElement(member);
record.setType(importedRecord.getType());
record.setCustomValues(new ArrayList<MemberRecordCustomFieldValue>());
for (final ImportedMemberRecordCustomFieldValue importedValue : importedRecord.getCustomValues()) {
final CustomField field = importedValue.getField();
final MemberRecordCustomFieldValue recordValue = new MemberRecordCustomFieldValue();
recordValue.setField(field);
if (field.getType() == CustomField.Type.ENUMERATED) {
recordValue.setPossibleValue(importedValue.getPossibleValue());
} else if (field.getType() == CustomField.Type.MEMBER) {
recordValue.setMemberValue(importedValue.getMemberValue());
}
record.getCustomValues().add(recordValue);
}
memberRecordService.insert(record);
}
}
// Handle the account
final MemberAccountType accountType = memberImport.getAccountType();
if (accountType != null) {
// Set the credit limit
final CreditLimitDTO limit = new CreditLimitDTO();
limit.setLimitPerType(Collections.singletonMap(accountType, importedMember.getCreditLimit()));
limit.setUpperLimitPerType(Collections.singletonMap(accountType, importedMember.getUpperCreditLimit()));
accountService.setCreditLimit(member, limit);
// Create the initial balance transaction
final BigDecimal initialBalance = importedMember.getInitialBalance();
if (initialBalance != null) {
final double balance = initialBalance.doubleValue();
TransferDTO transfer = null;
if (balance < 0 && memberImport.getInitialDebitTransferType() != null) {
// Is a negative balance: use the debit TT
transfer = new TransferDTO();
transfer.setFromOwner(member);
transfer.setToOwner(SystemAccountOwner.instance());
transfer.setTransferType(memberImport.getInitialDebitTransferType());
} else if (balance > 0 && memberImport.getInitialCreditTransferType() != null) {
// Is a positive balance: use the credit TT
transfer = new TransferDTO();
transfer.setFromOwner(SystemAccountOwner.instance());
transfer.setToOwner(member);
transfer.setTransferType(memberImport.getInitialCreditTransferType());
}
if (transfer != null) {
// If there was a transfer set, it will be used
transfer.setAutomatic(true);
transfer.setContext(TransactionContext.AUTOMATIC);
transfer.setAmount(initialBalance.abs());
transfer.setDescription(transfer.getTransferType().getDescription());
paymentService.insertWithoutNotification(transfer);
}
}
}
}
}