nl.strohalm.cyclos.services.elements.MemberImportServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for nl.strohalm.cyclos.services.elements.MemberImportServiceImpl.java

Source

/*
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);
                }
            }
        }
    }
}