nl.strohalm.cyclos.services.groups.GroupServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for nl.strohalm.cyclos.services.groups.GroupServiceImpl.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.groups;

import java.math.BigDecimal;
import java.util.ArrayList;
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.access.Permission;
import nl.strohalm.cyclos.dao.accounts.AccountDAO;
import nl.strohalm.cyclos.dao.accounts.AccountLimitLogDAO;
import nl.strohalm.cyclos.dao.accounts.MemberGroupAccountSettingsDAO;
import nl.strohalm.cyclos.dao.accounts.fee.account.AccountFeeDAO;
import nl.strohalm.cyclos.dao.accounts.fee.transaction.TransactionFeeDAO;
import nl.strohalm.cyclos.dao.accounts.transactions.PaymentFilterDAO;
import nl.strohalm.cyclos.dao.customizations.CustomFieldDAO;
import nl.strohalm.cyclos.dao.customizations.CustomizedFileDAO;
import nl.strohalm.cyclos.dao.groups.GroupDAO;
import nl.strohalm.cyclos.dao.members.ElementDAO;
import nl.strohalm.cyclos.dao.members.MemberRecordTypeDAO;
import nl.strohalm.cyclos.entities.Relationship;
import nl.strohalm.cyclos.entities.access.Channel;
import nl.strohalm.cyclos.entities.access.Channel.Credentials;
import nl.strohalm.cyclos.entities.accounts.AccountType;
import nl.strohalm.cyclos.entities.accounts.MemberAccountType;
import nl.strohalm.cyclos.entities.accounts.MemberGroupAccountSettings;
import nl.strohalm.cyclos.entities.accounts.SystemAccountType;
import nl.strohalm.cyclos.entities.accounts.cards.CardType;
import nl.strohalm.cyclos.entities.accounts.fees.account.AccountFee;
import nl.strohalm.cyclos.entities.accounts.fees.transaction.TransactionFee;
import nl.strohalm.cyclos.entities.accounts.guarantees.GuaranteeType;
import nl.strohalm.cyclos.entities.accounts.transactions.PaymentFilter;
import nl.strohalm.cyclos.entities.accounts.transactions.TransferType;
import nl.strohalm.cyclos.entities.customization.documents.Document;
import nl.strohalm.cyclos.entities.customization.fields.AdminCustomField;
import nl.strohalm.cyclos.entities.customization.fields.CustomField;
import nl.strohalm.cyclos.entities.customization.files.CustomizedFile;
import nl.strohalm.cyclos.entities.exceptions.EntityNotFoundException;
import nl.strohalm.cyclos.entities.exceptions.UnexpectedEntityException;
import nl.strohalm.cyclos.entities.groups.AdminGroup;
import nl.strohalm.cyclos.entities.groups.BasicGroupSettings;
import nl.strohalm.cyclos.entities.groups.BasicGroupSettings.PasswordPolicy;
import nl.strohalm.cyclos.entities.groups.BrokerGroup;
import nl.strohalm.cyclos.entities.groups.Group;
import nl.strohalm.cyclos.entities.groups.GroupFilter;
import nl.strohalm.cyclos.entities.groups.GroupQuery;
import nl.strohalm.cyclos.entities.groups.MemberGroup;
import nl.strohalm.cyclos.entities.groups.MemberGroupSettings;
import nl.strohalm.cyclos.entities.groups.OperatorGroup;
import nl.strohalm.cyclos.entities.groups.SystemGroup;
import nl.strohalm.cyclos.entities.members.Element;
import nl.strohalm.cyclos.entities.members.RegistrationAgreement;
import nl.strohalm.cyclos.entities.members.messages.Message;
import nl.strohalm.cyclos.entities.members.messages.Message.Type;
import nl.strohalm.cyclos.entities.members.messages.MessageCategory;
import nl.strohalm.cyclos.entities.members.records.MemberRecordType;
import nl.strohalm.cyclos.entities.settings.AccessSettings;
import nl.strohalm.cyclos.exceptions.PermissionDeniedException;
import nl.strohalm.cyclos.scheduling.polling.AccountActivationPollingTask;
import nl.strohalm.cyclos.services.access.ChannelServiceLocal;
import nl.strohalm.cyclos.services.accounts.AccountServiceLocal;
import nl.strohalm.cyclos.services.accounts.AccountTypeServiceLocal;
import nl.strohalm.cyclos.services.accounts.BulkUpdateAccountDTO;
import nl.strohalm.cyclos.services.accounts.MemberAccountHandler;
import nl.strohalm.cyclos.services.application.ApplicationServiceLocal;
import nl.strohalm.cyclos.services.customization.AdminCustomFieldServiceLocal;
import nl.strohalm.cyclos.services.customization.MemberCustomFieldServiceLocal;
import nl.strohalm.cyclos.services.customization.OperatorCustomFieldServiceLocal;
import nl.strohalm.cyclos.services.elements.ElementServiceLocal;
import nl.strohalm.cyclos.services.fetch.FetchServiceLocal;
import nl.strohalm.cyclos.services.permissions.PermissionServiceLocal;
import nl.strohalm.cyclos.services.settings.SettingsServiceLocal;
import nl.strohalm.cyclos.services.sms.ISmsContext;
import nl.strohalm.cyclos.utils.PropertyHelper;
import nl.strohalm.cyclos.utils.RelationshipHelper;
import nl.strohalm.cyclos.utils.TimePeriod;
import nl.strohalm.cyclos.utils.access.LoggedUser;
import nl.strohalm.cyclos.utils.validation.GeneralValidation;
import nl.strohalm.cyclos.utils.validation.InvalidError;
import nl.strohalm.cyclos.utils.validation.PropertyValidation;
import nl.strohalm.cyclos.utils.validation.RequiredError;
import nl.strohalm.cyclos.utils.validation.RequiredValidation;
import nl.strohalm.cyclos.utils.validation.ValidationError;
import nl.strohalm.cyclos.utils.validation.Validator;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;

/**
 * Implementation for group service.
 * @author rafael
 * @author luis
 * @author Jefferson Magno
 */
public class GroupServiceImpl implements GroupServiceLocal {

    /**
     * After group creation its nature never change.
     * @author ameyer
     */
    private final class ChangeNatureValidation implements PropertyValidation {
        private static final long serialVersionUID = 1L;

        @Override
        public ValidationError validate(final Object object, final Object property, final Object value) {
            Group group = (Group) object;
            if (group.isPersistent()) {
                Group current = load(group.getId());
                Group.Nature nature = (Group.Nature) value;
                if (nature != current.getNature()) {
                    return new ValidationError("group.invalidNature");
                }
            }
            return null;
        }
    }

    /**
     * A custom validation that process the given validation only when the member group is set to expire members after a given time period
     * @author luis
     */
    private final class ExpirationValidation implements PropertyValidation {

        private static final long serialVersionUID = 7237205242667756245L;
        private final PropertyValidation validation;

        private ExpirationValidation(final PropertyValidation validation) {
            this.validation = validation;
        }

        @Override
        public ValidationError validate(final Object object, final Object property, final Object value) {
            final MemberGroup group = (MemberGroup) object;
            final TimePeriod expireMembersAfter = group.getMemberSettings().getExpireMembersAfter();
            if (expireMembersAfter != null && expireMembersAfter.getNumber() > 0) {
                return validation.validate(object, property, value);
            }
            return null;
        }
    }

    /**
     * Ensures that a password policy is consistent with the access settings
     * @author luis
     */
    private final class PasswordPolicyValidation implements PropertyValidation {

        private static final long serialVersionUID = 5513735809903251255L;

        @Override
        public ValidationError validate(final Object object, final Object property, final Object value) {
            final PasswordPolicy policy = (PasswordPolicy) value;
            if (policy == null) {
                return null;
            }
            final Group group = (Group) object;
            final AccessSettings accessSettings = settingsService.getAccessSettings();
            final boolean virtualKeyboard = accessSettings.isVirtualKeyboard();
            final boolean numericPassword = accessSettings.isNumericPassword();

            if (policy == PasswordPolicy.AVOID_OBVIOUS_LETTERS_NUMBERS_SPECIAL && virtualKeyboard) {
                // Special chars cannot be typed with the virtual keyboard
                return new ValidationError("group.error.passwordPolicySpecialVirtualKeyboard");
            } else if (group.getNature() != Group.Nature.ADMIN && policy.isForceCharacters() && numericPassword) {
                // Admin groups don't use this, but ensure for other groups that if numeric password, there are no characters enforcements
                return new ValidationError("group.error.passwordPolicyNumeric");
            }
            return null;
        }

    }

    private final class PasswordTrialsValidation implements PropertyValidation {
        private static final long serialVersionUID = -5445950747956604765L;

        @Override
        public ValidationError validate(final Object object, final Object property, final Object value) {
            final Group group = (Group) object;

            if (((Integer) value == 0) && (group.getBasicSettings().getMaxPasswordWrongTries() > 0)) {
                return new InvalidError();
            }
            return null;
        }
    }

    private final class PINBlockTimeValidation implements PropertyValidation {

        private static final long serialVersionUID = 7237205242667756245L;
        private final PropertyValidation validation;

        private PINBlockTimeValidation(final PropertyValidation validation) {
            this.validation = validation;
        }

        @Override
        public ValidationError validate(final Object object, final Object property, final Object value) {
            final MemberGroup group = (MemberGroup) object;
            final TimePeriod expireMembersAfter = group.getMemberSettings().getPinBlockTimeAfterMaxTries();
            if (expireMembersAfter != null && expireMembersAfter.getNumber() > 0) {
                return validation.validate(object, property, value);
            }
            return null;
        }
    }

    private final class PinTrialsValidation implements PropertyValidation {
        private static final long serialVersionUID = -5445950747956604765L;

        @Override
        public ValidationError validate(final Object object, final Object property, final Object value) {
            final MemberGroup group = (MemberGroup) object;

            if ((value == null || (Integer) value == 0) && (group.getMemberSettings().getMaxPinWrongTries() > 0)) {
                return new InvalidError();
            }
            return null;
        }
    }

    private static final Relationship[] FETCH_TO_KEEP_DATA = { Group.Relationships.PERMISSIONS,
            Group.Relationships.TRANSFER_TYPES, Group.Relationships.CONVERSION_SIMULATION_TTS,
            BrokerGroup.Relationships.BROKER_CONVERSION_SIMULATION_TTS, SystemGroup.Relationships.DOCUMENTS,
            SystemGroup.Relationships.MESSAGE_CATEGORIES, BrokerGroup.Relationships.BROKER_DOCUMENTS,
            BrokerGroup.Relationships.BROKER_MEMBER_RECORD_TYPES, AdminGroup.Relationships.MANAGES_GROUPS,
            SystemGroup.Relationships.CHARGEBACK_TRANSFER_TYPES, AdminGroup.Relationships.TRANSFER_TYPES_AS_MEMBER,
            AdminGroup.Relationships.VIEW_INFORMATION_OF, AdminGroup.Relationships.VIEW_CONNECTED_ADMINS_OF,
            AdminGroup.Relationships.VIEW_ADMIN_RECORD_TYPES, MemberGroup.Relationships.CAN_VIEW_ADS_OF_GROUPS,
            MemberGroup.Relationships.CAN_VIEW_PROFILE_OF_GROUPS, MemberGroup.Relationships.MANAGED_BY_GROUPS,
            Group.Relationships.GUARANTEE_TYPES };
    private static final Relationship[] FETCH_TO_REMOVE = { Group.Relationships.PAYMENT_FILTERS,
            AdminGroup.Relationships.CONNECTED_ADMINS_VIEWED_BY, AdminGroup.Relationships.ADMIN_CUSTOM_FIELDS,
            MemberGroup.Relationships.ACCOUNT_FEES, MemberGroup.Relationships.MANAGED_BY_GROUPS,
            MemberGroup.Relationships.CUSTOM_FIELDS, MemberGroup.Relationships.FROM_TRANSACTION_FEES,
            MemberGroup.Relationships.TO_TRANSACTION_FEES, MemberGroup.Relationships.MEMBER_RECORD_TYPES };

    private AccountFeeDAO accountFeeDao;
    private AccountTypeServiceLocal accountTypeService;
    private AccountServiceLocal accountService;
    private CustomFieldDAO customFieldDao;
    private CustomizedFileDAO customizedFileDao;
    private GroupDAO groupDao;
    private ElementDAO elementDao;
    private ElementServiceLocal elementService;
    private MemberGroupAccountSettingsDAO memberGroupAccountSettingsDao;
    private MemberRecordTypeDAO memberRecordTypeDao;
    private PaymentFilterDAO paymentFilterDao;
    private TransactionFeeDAO transactionFeeDao;
    private MemberAccountHandler memberAccountHandler;
    private PermissionServiceLocal permissionService;
    private FetchServiceLocal fetchService;
    private AdminCustomFieldServiceLocal adminCustomFieldService;
    private MemberCustomFieldServiceLocal memberCustomFieldService;
    private OperatorCustomFieldServiceLocal operatorCustomFieldService;
    private SettingsServiceLocal settingsService;
    private ChannelServiceLocal channelService;
    private ApplicationServiceLocal applicationService;
    private AccountDAO accountDao;
    private AccountLimitLogDAO accountLimitLogDao;

    @Override
    public int countPendingAccounts(final MemberGroup group, final MemberAccountType accountType) {
        return accountService.countPendingActivation(group, accountType);
    }

    @Override
    public SystemGroup findByLoginPageName(final String loginPageName) {
        return groupDao.findByLoginPageName(loginPageName);
    }

    public CustomFieldDAO getCustomFieldDao() {
        return customFieldDao;
    }

    public CustomizedFileDAO getCustomizedFileDao() {
        return customizedFileDao;
    }

    public GroupDAO getGroupDao() {
        return groupDao;
    }

    public MemberAccountHandler getMemberAccountHandler() {
        return memberAccountHandler;
    }

    public MemberGroupAccountSettingsDAO getMemberGroupAccountSettingsDao() {
        return memberGroupAccountSettingsDao;
    }

    public MemberRecordTypeDAO getMemberRecordTypeDao() {
        return memberRecordTypeDao;
    }

    public PaymentFilterDAO getPaymentFilterDao() {
        return paymentFilterDao;
    }

    @Override
    public List<MemberGroup> getPossibleInitialGroups(GroupFilter groupFilter) {
        if (LoggedUser.hasUser() && LoggedUser.isBroker()) {
            BrokerGroup brokerGroup = LoggedUser.group();
            brokerGroup = fetchService.fetch(brokerGroup, BrokerGroup.Relationships.POSSIBLE_INITIAL_GROUPS);
            return (List<MemberGroup>) brokerGroup.getPossibleInitialGroups();
        }
        groupFilter = fetchService.fetch(groupFilter, GroupFilter.Relationships.GROUPS);
        final GroupQuery query = new GroupQuery();
        query.setNatures(Group.Nature.BROKER, Group.Nature.MEMBER);
        final List<MemberGroup> initialGroups = new ArrayList<MemberGroup>();
        for (Group group : search(query)) {
            final MemberGroup memberGroup = (MemberGroup) fetchService.fetch(group);
            if (memberGroup.isInitialGroup()) {
                initialGroups.add(memberGroup);
            }
        }
        if (groupFilter != null) {
            // If group filter is used, only return groups in that group filter
            initialGroups.retainAll(groupFilter.getGroups());
        }
        return initialGroups;
    }

    @Override
    public boolean hasGroupsWhichRequiresSpecialOnPassword() {
        final GroupQuery query = new GroupQuery();
        query.setStatus(Group.Status.NORMAL);
        for (final Group group : search(query)) {
            if (group.getBasicSettings()
                    .getPasswordPolicy() == PasswordPolicy.AVOID_OBVIOUS_LETTERS_NUMBERS_SPECIAL) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean hasMemberGroupsWhichEnforcesCharactersOnPassword() {
        final GroupQuery query = new GroupQuery();
        query.setStatus(Group.Status.NORMAL);
        query.setNatures(Group.Nature.MEMBER, Group.Nature.BROKER);
        for (final Group group : search(query)) {
            if (group.getBasicSettings().getPasswordPolicy().isForceCharacters()) {
                return true;
            }
        }
        return false;
    }

    @Override
    public <G extends Group> G insert(G group, final G baseGroup) {
        if (baseGroup != null) {
            // Copy settings from base group
            group = copyBaseGroupSettings(group, baseGroup);
        } else {
            group.setBasicSettings(new BasicGroupSettings());
            if (group instanceof MemberGroup) {
                MemberGroup memberGroup = (MemberGroup) group;
                memberGroup.setMemberSettings(new MemberGroupSettings());
            }
        }

        // Validate and save
        validate(group);
        group = save(group);

        // Copy inverse collections from base group
        if (baseGroup != null) {
            copyInverseCollections(group, baseGroup);
        }

        if (group instanceof MemberGroup) {
            MemberGroup memberGroup = (MemberGroup) group;

            // Add permission to admin group over the member group
            AdminGroup adminGroup = LoggedUser.group();
            adminGroup = fetchService.fetch(adminGroup, AdminGroup.Relationships.MANAGES_GROUPS);
            final Collection<MemberGroup> managesGroups = adminGroup.getManagesGroups();
            managesGroups.add(memberGroup);
            groupDao.update(adminGroup);
        }

        clearRelatedCaches(group);
        return group;
    }

    @Override
    public MemberGroupAccountSettings insertAccountSettings(final MemberGroupAccountSettings settings) {
        // Check if the admin group has the permission to manage the member group
        final MemberGroup memberGroup = fetchService.fetch(settings.getGroup());
        AdminGroup adminGroup = LoggedUser.group();
        adminGroup = fetchService.fetch(adminGroup, AdminGroup.Relationships.MANAGES_GROUPS);
        final Collection<MemberGroup> managesGroups = adminGroup.getManagesGroups();
        if (!managesGroups.contains(memberGroup)) {
            throw new PermissionDeniedException();
        }

        validate(settings);
        // Check if the account type isn't already related to the group
        try {
            memberGroupAccountSettingsDao.load(settings.getGroup().getId(), settings.getAccountType().getId());
            throw new UnexpectedEntityException();
        } catch (final EntityNotFoundException e) {
            // Ok, the account is not related to the group, so we can go on
        }
        final MemberAccountType currentDefault = accountTypeService.getDefault(memberGroup);
        if (currentDefault == null) {
            // When there's no current default, set this one as default
            settings.setDefault(true);
        } else if (settings.isDefault()) {
            // When there was a default already and this one is marked as default, unmark the previous one
            final MemberGroupAccountSettings defaultSettings = memberGroupAccountSettingsDao
                    .load(memberGroup.getId(), currentDefault.getId());
            defaultSettings.setDefault(false);
            memberGroupAccountSettingsDao.update(defaultSettings);
        }
        final MemberGroupAccountSettings saved = memberGroupAccountSettingsDao.insert(settings);

        // When inserting an account to an inactive group, activate it
        if (!memberGroup.isActive()) {
            memberGroup.setActive(true);
            groupDao.update(memberGroup);
            elementDao.activateMembersOfGroup(memberGroup);
        }

        // create and mark the member accounts for activation so MemberAccountHandler.ActivateAccountThread can pick them up
        BulkUpdateAccountDTO dto = new BulkUpdateAccountDTO();
        dto.setGroup(saved.getGroup());
        dto.setType(saved.getAccountType());
        dto.setCreditLimit(saved.getDefaultCreditLimit());
        dto.setUpperCreditLimit(saved.getDefaultUpperCreditLimit());
        accountDao.markForActivation(dto);

        applicationService.awakePollingTaskOnTransactionCommit(AccountActivationPollingTask.class);

        return saved;
    }

    @Override
    public List<OperatorGroup> iterateOperatorGroups(final MemberGroup memberGroup) {
        return groupDao.iterateOperatorGroups(memberGroup);
    }

    @Override
    public <T extends Group> Collection<T> load(final Collection<Long> ids, final Relationship... fetch) {
        return groupDao.<T>load(ids, fetch);
    }

    @Override
    public <T extends Group> T load(final Long id, final Relationship... fetch) {
        return groupDao.<T>load(id, fetch);
    }

    @Override
    public MemberGroupAccountSettings loadAccountSettings(final long groupId, final long accountTypeId,
            final Relationship... fetch) {
        return memberGroupAccountSettingsDao.load(groupId, accountTypeId, fetch);
    }

    @Override
    public <T extends Group> T reload(final Long id, final Relationship... fetch) {
        return groupDao.<T>reload(id, fetch);
    }

    @Override
    public void remove(final Long id) throws EntityNotFoundException {
        final Group group = load(id, FETCH_TO_REMOVE);
        if (!(group instanceof OperatorGroup)) {
            removeFromInverseCollections(group);
        }
        // Ensure the permissions cache for this group is evicted
        permissionService.evictCache(group);
        groupDao.delete(id);
    }

    @Override
    public void removeAccountTypeRelationship(final MemberGroup group, final MemberAccountType type) {
        // Remove the account settings
        memberGroupAccountSettingsDao.delete(group.getId(), type.getId());
        // mark all accounts for deactivation
        accountDao.markForDeactivation(type, group);

        applicationService.awakePollingTaskOnTransactionCommit(AccountActivationPollingTask.class);
    }

    @Override
    public List<? extends Group> search(final GroupQuery query) {
        if (!query.isIgnoreManagedBy() && LoggedUser.hasUser() && LoggedUser.isAdministrator()) {
            final AdminGroup adminGroup = LoggedUser.group();
            query.setManagedBy(adminGroup);
        }
        return groupDao.search(query);
    }

    public void setAccountDao(final AccountDAO accountDao) {
        this.accountDao = accountDao;
    }

    public void setAccountFeeDao(final AccountFeeDAO accountFeeDao) {
        this.accountFeeDao = accountFeeDao;
    }

    public void setAccountLimitLogDao(final AccountLimitLogDAO accountLimitLogDao) {
        this.accountLimitLogDao = accountLimitLogDao;
    }

    public void setAccountServiceLocal(final AccountServiceLocal accountService) {
        this.accountService = accountService;
    }

    public void setAccountTypeServiceLocal(final AccountTypeServiceLocal accountTypeService) {
        this.accountTypeService = accountTypeService;
    }

    public void setAdminCustomFieldServiceLocal(final AdminCustomFieldServiceLocal adminCustomFieldService) {
        this.adminCustomFieldService = adminCustomFieldService;
    }

    public void setApplicationServiceLocal(final ApplicationServiceLocal applicationService) {
        this.applicationService = applicationService;
    }

    public void setChannelServiceLocal(final ChannelServiceLocal channelService) {
        this.channelService = channelService;
    }

    public void setCustomFieldDao(final CustomFieldDAO customFieldDao) {
        this.customFieldDao = customFieldDao;
    }

    public void setCustomizedFileDao(final CustomizedFileDAO customizedFileDao) {
        this.customizedFileDao = customizedFileDao;
    }

    public void setElementDao(final ElementDAO elementDao) {
        this.elementDao = elementDao;
    }

    public void setElementServiceLocal(final ElementServiceLocal elementService) {
        this.elementService = elementService;
    }

    public void setFetchServiceLocal(final FetchServiceLocal fetchService) {
        this.fetchService = fetchService;
    }

    public void setGroupDao(final GroupDAO groupDao) {
        this.groupDao = groupDao;
    }

    public void setMemberAccountHandler(final MemberAccountHandler memberAccountHandler) {
        this.memberAccountHandler = memberAccountHandler;
    }

    public void setMemberCustomFieldServiceLocal(final MemberCustomFieldServiceLocal memberCustomFieldService) {
        this.memberCustomFieldService = memberCustomFieldService;
    }

    public void setMemberGroupAccountSettingsDao(
            final MemberGroupAccountSettingsDAO memberGroupAccountSettingsDao) {
        this.memberGroupAccountSettingsDao = memberGroupAccountSettingsDao;
    }

    public void setMemberRecordTypeDao(final MemberRecordTypeDAO memberRecordTypeDao) {
        this.memberRecordTypeDao = memberRecordTypeDao;
    }

    public void setOperatorCustomFieldServiceLocal(
            final OperatorCustomFieldServiceLocal operatorCustomFieldService) {
        this.operatorCustomFieldService = operatorCustomFieldService;
    }

    public void setPaymentFilterDao(final PaymentFilterDAO paymentFilterDao) {
        this.paymentFilterDao = paymentFilterDao;
    }

    @Override
    public <G extends Group> G setPermissions(final GroupPermissionsDTO<G> dto) {
        G group = fetchService.fetch(dto.getGroup());
        dto.update(group);
        group = groupDao.update(group);
        permissionService.evictCache(group);
        return group;
    }

    public void setPermissionServiceLocal(final PermissionServiceLocal permissionService) {
        this.permissionService = permissionService;
    }

    public void setSettingsServiceLocal(final SettingsServiceLocal settingsService) {
        this.settingsService = settingsService;
    }

    public void setTransactionFeeDao(final TransactionFeeDAO transactionFeeDao) {
        this.transactionFeeDao = transactionFeeDao;
    }

    @Override
    public <G extends Group> G update(G group, final boolean forceMembersToAcceptAgreement) {
        validate(group);

        final Group currentGroup = load(group.getId());
        if (group.isRemoved()) { // only take into account name and description
            currentGroup.setName(group.getName());
            currentGroup.setDescription(group.getDescription());

            return groupDao.update(group);
        }

        // Those variables are only used for member groups
        boolean wasTPUsed = false;
        boolean wasActive = false;
        boolean isTPUsed = false;
        RegistrationAgreement oldAgreement = null;

        if (group instanceof MemberGroup) {
            // Member groups have special handling
            MemberGroup memberGroup = (MemberGroup) group;
            final MemberGroup currentMemberGroup = (MemberGroup) load(memberGroup.getId());
            oldAgreement = fetchService.fetch(currentMemberGroup.getRegistrationAgreement());
            try {
                wasTPUsed = currentMemberGroup.getBasicSettings().getTransactionPassword().isUsed();
            } catch (final Exception e) {
                wasTPUsed = false;
            }
            try {
                isTPUsed = memberGroup.getBasicSettings().getTransactionPassword().isUsed();
            } catch (final Exception e) {
                isTPUsed = false;
            }
            wasActive = currentMemberGroup.isActive();
        }

        group = save(group);

        if (group instanceof MemberGroup) {
            // Member groups have special handling
            MemberGroup memberGroup = (MemberGroup) group;
            // When the transaction password was not used and now is, or was used and now is not, update the accounts
            if ((wasTPUsed && !isTPUsed) || (!wasTPUsed && isTPUsed)) {
                memberGroup = fetchService.reload(memberGroup, MemberGroup.Relationships.ACCOUNT_SETTINGS);
                for (final MemberGroupAccountSettings mgas : memberGroup.getAccountSettings()) {
                    mgas.setTransactionPasswordRequired(isTPUsed);
                    memberGroupAccountSettingsDao.update(mgas);
                }
            }

            // When the group is becoming active, activate all members on it
            if (!wasActive && memberGroup.isActive()) {
                elementDao.activateMembersOfGroup(memberGroup);
            }

            // Check if the registration agreement has changed
            final RegistrationAgreement registrationAgreement = fetchService
                    .fetch(memberGroup.getRegistrationAgreement());
            if (registrationAgreement != null && !registrationAgreement.equals(oldAgreement)) {
                // It did change. When not forcing members to accept it again, we should create all accepting logs
                if (!forceMembersToAcceptAgreement) {
                    elementService.createAgreementForAllMembers(registrationAgreement, memberGroup);
                }
            }
        }

        return group;
    }

    @Override
    public MemberGroupAccountSettings updateAccountSettings(final MemberGroupAccountSettings settings,
            final boolean updateAccountLimits) {

        // Check if the admin group has the permission to manage the member group
        final MemberGroup memberGroup = settings.getGroup();
        AdminGroup adminGroup = LoggedUser.group();
        adminGroup = fetchService.fetch(adminGroup, AdminGroup.Relationships.MANAGES_GROUPS);
        final Collection<MemberGroup> managesGroups = adminGroup.getManagesGroups();
        if (!managesGroups.contains(memberGroup)) {
            throw new PermissionDeniedException();
        }

        validate(settings);

        final MemberAccountType currentDefault = accountTypeService.getDefault(memberGroup);
        if (currentDefault == null || currentDefault.equals(settings.getAccountType())) {
            // When there's no current default, or is this one, set as default
            settings.setDefault(true);
        } else if (settings.isDefault()) {
            // When there was a default already and this one is marked as default, unmark the previous one
            final MemberGroupAccountSettings defaultSettings = memberGroupAccountSettingsDao
                    .load(memberGroup.getId(), currentDefault.getId());
            defaultSettings.setDefault(false);
            memberGroupAccountSettingsDao.update(defaultSettings);
        }

        final MemberGroupAccountSettings saved = memberGroupAccountSettingsDao.update(settings);

        if (updateAccountLimits) {
            BulkUpdateAccountDTO dto = new BulkUpdateAccountDTO();
            dto.setType(saved.getAccountType());
            dto.setGroup(settings.getGroup());
            dto.setCreditLimit(saved.getDefaultCreditLimit());
            dto.setUpperCreditLimit(saved.getDefaultUpperCreditLimit());
            accountDao.bulkUpdateCreditLimites(dto);
            accountLimitLogDao.insertAfterCreditLimitBulkUpdate(saved.getAccountType(), settings.getGroup());
        }
        return saved;
    }

    @Override
    public boolean usesPin(MemberGroup group) {
        group = fetchService.fetch(group, MemberGroup.Relationships.CHANNELS);
        final Collection<Channel> channels = group.getChannels();
        for (final Channel channel : channels) {
            if (channel.getCredentials() == Credentials.PIN) {
                return true;
            }
        }
        return false;
    }

    @Override
    public void validate(final Group group) {
        if (group.isTransient()) {
            getInsertValidator().validate(group);
        } else if (Group.Status.REMOVED.equals(group.getStatus())) {
            getRemovedValidator().validate(group);
        } else if (group instanceof AdminGroup) {
            getAdminValidator().validate(group);
        } else if (group instanceof BrokerGroup) {
            getBrokerValidator().validate(group);
        } else if (group instanceof MemberGroup) {
            getMemberValidator().validate(group);
        } else if (group instanceof OperatorGroup) {
            getOperatorValidator().validate(group);
        }
    }

    @Override
    public void validate(final MemberGroupAccountSettings settings) {
        getAccountSettingsValidator().validate(settings);
    }

    private void clearRelatedCaches(final Group group) {
        if (group instanceof AdminGroup) {
            adminCustomFieldService.clearCache();
        } else if (group instanceof MemberGroup) {
            memberCustomFieldService.clearCache();
        } else if (group instanceof OperatorGroup) {
            operatorCustomFieldService.clearCache();
        }
        accountTypeService.clearCache();
    }

    private <G extends Group> G copyBaseGroupSettings(final G group, G baseGroup) {
        baseGroup = fetchService.fetch(baseGroup, Group.Relationships.PAYMENT_FILTERS,
                Group.Relationships.PERMISSIONS, Group.Relationships.TRANSFER_TYPES,
                SystemGroup.Relationships.DOCUMENTS, Group.Relationships.CUSTOMIZED_FILES,
                SystemGroup.Relationships.MESSAGE_CATEGORIES, AdminGroup.Relationships.TRANSFER_TYPES_AS_MEMBER,
                AdminGroup.Relationships.MANAGES_GROUPS, AdminGroup.Relationships.VIEW_INFORMATION_OF,
                AdminGroup.Relationships.VIEW_CONNECTED_ADMINS_OF,
                AdminGroup.Relationships.CONNECTED_ADMINS_VIEWED_BY, AdminGroup.Relationships.ADMIN_CUSTOM_FIELDS,
                MemberGroup.Relationships.ACCOUNT_SETTINGS, MemberGroup.Relationships.CAN_VIEW_PROFILE_OF_GROUPS,
                MemberGroup.Relationships.CAN_VIEW_ADS_OF_GROUPS, MemberGroup.Relationships.CAN_VIEW_INFORMATION_OF,
                MemberGroup.Relationships.ACCOUNT_FEES, MemberGroup.Relationships.MANAGED_BY_GROUPS,
                MemberGroup.Relationships.CUSTOM_FIELDS, MemberGroup.Relationships.FROM_TRANSACTION_FEES,
                MemberGroup.Relationships.TO_TRANSACTION_FEES, BrokerGroup.Relationships.TRANSFER_TYPES_AS_MEMBER,
                BrokerGroup.Relationships.BROKER_DOCUMENTS,
                BrokerGroup.Relationships.BROKER_CAN_VIEW_INFORMATION_OF, OperatorGroup.Relationships.MEMBER,
                OperatorGroup.Relationships.MAX_AMOUNT_PER_DAY_BY_TRANSFER_TYPE,
                OperatorGroup.Relationships.CAN_VIEW_INFORMATION_OF, Group.Relationships.GUARANTEE_TYPES,
                MemberGroup.Relationships.CAN_ISSUE_CERTIFICATION_TO_GROUPS,
                MemberGroup.Relationships.CAN_BUY_WITH_PAYMENT_OBLIGATIONS_FROM_GROUPS,
                MemberGroup.Relationships.CARD_TYPE);

        // Copy permissions
        final Set<Permission> permissions = new HashSet<Permission>(baseGroup.getPermissions());
        group.setPermissions(permissions);

        // Status
        group.setStatus(baseGroup.getStatus());

        // Transfer types
        final List<TransferType> transferTypes = new ArrayList<TransferType>(baseGroup.getTransferTypes());
        group.setTransferTypes(transferTypes);

        // Conversion Simulation TTs
        final List<TransferType> conversionSimulationTTs = new ArrayList<TransferType>(
                baseGroup.getConversionSimulationTTs());
        group.setConversionSimulationTTs(conversionSimulationTTs);

        // Basic settings
        if (!(group instanceof OperatorGroup)) {
            group.setBasicSettings((BasicGroupSettings) baseGroup.getBasicSettings().clone());
        }

        // Guarantee types
        final List<GuaranteeType> guaranteeTypes = new ArrayList<GuaranteeType>(baseGroup.getGuaranteeTypes());
        group.setGuaranteeTypes(guaranteeTypes);

        if (group instanceof SystemGroup) {
            final SystemGroup systemGroup = (SystemGroup) group;
            final SystemGroup baseSystemGroup = (SystemGroup) baseGroup;

            // Documents
            final List<Document> documents = new ArrayList<Document>(baseSystemGroup.getDocuments());
            systemGroup.setDocuments(documents);

            // Message categories
            final List<MessageCategory> messageCategories = new ArrayList<MessageCategory>(
                    baseSystemGroup.getMessageCategories());
            systemGroup.setMessageCategories(messageCategories);

            // Chargeback transfer types
            final List<TransferType> chargebackTransferTypes = new ArrayList<TransferType>(
                    baseSystemGroup.getChargebackTransferTypes());
            systemGroup.setChargebackTransferTypes(chargebackTransferTypes);
        }

        if (group instanceof AdminGroup) {
            final AdminGroup adminGroup = (AdminGroup) group;
            final AdminGroup baseAdminGroup = (AdminGroup) baseGroup;

            // Transfer types as member
            final List<TransferType> transferTypesAsMember = new ArrayList<TransferType>(
                    baseAdminGroup.getTransferTypesAsMember());
            adminGroup.setTransferTypesAsMember(transferTypesAsMember);

            // Manages groups
            final List<MemberGroup> managesGroups = new ArrayList<MemberGroup>(baseAdminGroup.getManagesGroups());
            adminGroup.setManagesGroups(managesGroups);

            // View information of
            final List<SystemAccountType> viewInformationOf = new ArrayList<SystemAccountType>(
                    baseAdminGroup.getViewInformationOf());
            adminGroup.setViewInformationOf(viewInformationOf);

            // View connected admins of
            final List<AdminGroup> viewConnectedAdminsOf = new ArrayList<AdminGroup>(
                    baseAdminGroup.getViewConnectedAdminsOf());
            adminGroup.setViewConnectedAdminsOf(viewConnectedAdminsOf);

            // View member record types
            final List<MemberRecordType> viewMemberRecordTypes = new ArrayList<MemberRecordType>(
                    baseAdminGroup.getViewMemberRecordTypes());
            adminGroup.setViewMemberRecordTypes(viewMemberRecordTypes);

            // Create member record types
            final List<MemberRecordType> createMemberRecordTypes = new ArrayList<MemberRecordType>(
                    baseAdminGroup.getCreateMemberRecordTypes());
            adminGroup.setCreateMemberRecordTypes(createMemberRecordTypes);

            // Modify member record types
            final List<MemberRecordType> modifyMemberRecordTypes = new ArrayList<MemberRecordType>(
                    baseAdminGroup.getModifyMemberRecordTypes());
            adminGroup.setModifyMemberRecordTypes(modifyMemberRecordTypes);

            // Delete member record types
            final List<MemberRecordType> deleteMemberRecordTypes = new ArrayList<MemberRecordType>(
                    baseAdminGroup.getDeleteMemberRecordTypes());
            adminGroup.setDeleteMemberRecordTypes(deleteMemberRecordTypes);

            // View admin record types
            final List<MemberRecordType> viewAdminRecordTypes = new ArrayList<MemberRecordType>(
                    baseAdminGroup.getViewAdminRecordTypes());
            adminGroup.setViewAdminRecordTypes(viewAdminRecordTypes);

            // Create admin record types
            final List<MemberRecordType> createAdminRecordTypes = new ArrayList<MemberRecordType>(
                    baseAdminGroup.getCreateAdminRecordTypes());
            adminGroup.setCreateAdminRecordTypes(createAdminRecordTypes);

            // Modify admin record types
            final List<MemberRecordType> modifyAdminRecordTypes = new ArrayList<MemberRecordType>(
                    baseAdminGroup.getModifyAdminRecordTypes());
            adminGroup.setModifyAdminRecordTypes(modifyAdminRecordTypes);

            // Delete admin record types
            final List<MemberRecordType> deleteAdminRecordTypes = new ArrayList<MemberRecordType>(
                    baseAdminGroup.getDeleteAdminRecordTypes());
            adminGroup.setDeleteAdminRecordTypes(deleteAdminRecordTypes);
        }

        if (group instanceof MemberGroup) {
            final MemberGroup memberGroup = (MemberGroup) group;
            final MemberGroup baseMemberGroup = (MemberGroup) baseGroup;

            // Member group settings
            memberGroup.setMemberSettings((MemberGroupSettings) baseMemberGroup.getMemberSettings().clone());

            // Copy the e-mail validation
            memberGroup.getMemberSettings().setEmailValidation(new HashSet<MemberGroupSettings.EmailValidation>(
                    baseMemberGroup.getMemberSettings().getEmailValidation()));

            // Initial group & agreement
            memberGroup.setInitialGroup(baseMemberGroup.isInitialGroup());
            memberGroup.setRegistrationAgreement(baseMemberGroup.getRegistrationAgreement());

            // Active
            memberGroup.setActive(baseMemberGroup.isActive());

            // Can view profile of groups
            final List<MemberGroup> canViewProfileOfGroups = new ArrayList<MemberGroup>(
                    baseMemberGroup.getCanViewProfileOfGroups());
            canViewProfileOfGroups.add(baseMemberGroup);
            memberGroup.setCanViewProfileOfGroups(canViewProfileOfGroups);

            // Can view ads of groups
            final List<MemberGroup> canViewAdsOfGroups = new ArrayList<MemberGroup>(
                    baseMemberGroup.getCanViewAdsOfGroups());
            canViewAdsOfGroups.add(baseMemberGroup);
            memberGroup.setCanViewAdsOfGroups(canViewAdsOfGroups);

            // Can view information of
            final List<AccountType> canViewInformationOf = new ArrayList<AccountType>(
                    baseMemberGroup.getCanViewInformationOf());
            memberGroup.setCanViewInformationOf(canViewInformationOf);

            // Default mail messages
            final List<Message.Type> defaultMailMessages = new ArrayList<Message.Type>(
                    baseMemberGroup.getDefaultMailMessages());
            memberGroup.setDefaultMailMessages(defaultMailMessages);

            // SMS messages
            final List<Message.Type> smsMessages = new ArrayList<Message.Type>(baseMemberGroup.getSmsMessages());
            memberGroup.setSmsMessages(smsMessages);

            // Default SMS messages
            final List<Message.Type> defaultSmsMessages = new ArrayList<Message.Type>(
                    baseMemberGroup.getDefaultSmsMessages());
            memberGroup.setDefaultSmsMessages(defaultSmsMessages);

            // Channels
            final List<Channel> channels = new ArrayList<Channel>(baseMemberGroup.getChannels());
            memberGroup.setChannels(channels);

            // Default channels
            final List<Channel> defaultChannels = new ArrayList<Channel>(baseMemberGroup.getDefaultChannels());
            memberGroup.setDefaultChannels(defaultChannels);

            // Request payment by channels
            final List<Channel> requestPaymentByChannels = new ArrayList<Channel>(
                    baseMemberGroup.getRequestPaymentByChannels());
            memberGroup.setRequestPaymentByChannels(requestPaymentByChannels);

            // Can issue certification to groups
            final List<MemberGroup> canIssueCertificationToGroups = new ArrayList<MemberGroup>(
                    baseMemberGroup.getCanIssueCertificationToGroups());
            memberGroup.setCanIssueCertificationToGroups(canIssueCertificationToGroups);

            // Can buy with payment obligations from groups
            final List<MemberGroup> canBuyWithPaymentObligationsFromGroups = new ArrayList<MemberGroup>(
                    baseMemberGroup.getCanBuyWithPaymentObligationsFromGroups());
            memberGroup.setCanBuyWithPaymentObligationsFromGroups(canBuyWithPaymentObligationsFromGroups);

            // Card type
            final CardType cardType = baseMemberGroup.getCardType();
            memberGroup.setCardType(cardType);
        }

        if (group instanceof BrokerGroup) {
            final BrokerGroup brokerGroup = (BrokerGroup) group;
            final BrokerGroup baseBrokerGroup = (BrokerGroup) baseGroup;

            // Transfer types as member
            final List<TransferType> transferTypesAsMember = new ArrayList<TransferType>(
                    baseBrokerGroup.getTransferTypesAsMember());
            brokerGroup.setTransferTypesAsMember(transferTypesAsMember);

            final List<TransferType> brokerConversionSimulationTTs = new ArrayList<TransferType>(
                    baseBrokerGroup.getBrokerConversionSimulationTTs());
            brokerGroup.setBrokerConversionSimulationTTs(brokerConversionSimulationTTs);

            // Broker documents
            final List<Document> brokerDocuments = new ArrayList<Document>(baseBrokerGroup.getBrokerDocuments());
            brokerGroup.setBrokerDocuments(brokerDocuments);

            // Broker can view information of
            final List<AccountType> brokerCanViewInformationOf = new ArrayList<AccountType>(
                    baseBrokerGroup.getBrokerCanViewInformationOf());
            brokerGroup.setBrokerCanViewInformationOf(brokerCanViewInformationOf);

            // Broker view member record types
            final List<MemberRecordType> brokerMemberRecordTypes = new ArrayList<MemberRecordType>(
                    baseBrokerGroup.getBrokerMemberRecordTypes());
            brokerGroup.setBrokerMemberRecordTypes(brokerMemberRecordTypes);

            // Broker create member record types
            final List<MemberRecordType> brokerCreateMemberRecordTypes = new ArrayList<MemberRecordType>(
                    baseBrokerGroup.getBrokerCreateMemberRecordTypes());
            brokerGroup.setBrokerCreateMemberRecordTypes(brokerCreateMemberRecordTypes);

            // Broker modify member record types
            final List<MemberRecordType> brokerModifyMemberRecordTypes = new ArrayList<MemberRecordType>(
                    baseBrokerGroup.getBrokerModifyMemberRecordTypes());
            brokerGroup.setBrokerModifyMemberRecordTypes(brokerModifyMemberRecordTypes);

            // Broker delete member record types
            final List<MemberRecordType> brokerDeleteMemberRecordTypes = new ArrayList<MemberRecordType>(
                    baseBrokerGroup.getBrokerDeleteMemberRecordTypes());
            brokerGroup.setBrokerDeleteMemberRecordTypes(brokerDeleteMemberRecordTypes);

            // Possible initial groups
            final List<MemberGroup> possibleInitialGroups = new ArrayList<MemberGroup>(
                    baseBrokerGroup.getPossibleInitialGroups());
            brokerGroup.setPossibleInitialGroups(possibleInitialGroups);
        }

        if (group instanceof OperatorGroup) {
            final OperatorGroup operatorGroup = (OperatorGroup) group;
            final OperatorGroup baseOperatorGroup = (OperatorGroup) baseGroup;

            // Member
            operatorGroup.setMember(baseOperatorGroup.getMember());

            // Max amount per day by transfer type
            final Map<TransferType, BigDecimal> maxAmountPerDayByTransferType = new HashMap<TransferType, BigDecimal>(
                    baseOperatorGroup.getMaxAmountPerDayByTransferType());
            operatorGroup.setMaxAmountPerDayByTransferType(maxAmountPerDayByTransferType);

            // Can view information of
            final List<AccountType> canViewInformationOf = new ArrayList<AccountType>(
                    baseOperatorGroup.getCanViewInformationOf());
            operatorGroup.setCanViewInformationOf(canViewInformationOf);
        }

        return group;
    }

    @SuppressWarnings("unchecked")
    private Group copyInverseCollections(final Group group, final Group baseGroup) {

        // Payment filters
        final Collection<PaymentFilter> paymentFilters = baseGroup.getPaymentFilters();
        for (final PaymentFilter paymentFilter : paymentFilters) {
            final Collection<Group> groups = (Collection<Group>) paymentFilter.getGroups();
            groups.add(group);
            paymentFilterDao.update(paymentFilter);
        }

        // Customized files
        final Collection<CustomizedFile> customizedFiles = baseGroup.getCustomizedFiles();
        for (final CustomizedFile baseGroupCustomizedFile : customizedFiles) {
            final CustomizedFile customizedFile = (CustomizedFile) baseGroupCustomizedFile.clone();
            customizedFile.setId(null);
            customizedFile.setGroup(group);
            customizedFileDao.insert(customizedFile);
        }

        // Record types
        final Collection<MemberRecordType> recordTypes = baseGroup.getMemberRecordTypes();
        for (final MemberRecordType recordType : recordTypes) {
            recordType.getGroups().add(group);
            memberRecordTypeDao.update(recordType);
        }

        if (group instanceof AdminGroup) {
            final AdminGroup adminGroup = (AdminGroup) group;
            final AdminGroup baseAdminGroup = (AdminGroup) baseGroup;

            // Connected admins viewed by
            final Collection<AdminGroup> connectedAdminsViewedBy = baseAdminGroup.getConnectedAdminsViewedBy();
            for (final AdminGroup viewerAdminGroup : connectedAdminsViewedBy) {
                viewerAdminGroup.getViewConnectedAdminsOf().add(adminGroup);
                groupDao.update(viewerAdminGroup);
            }

            // Admin custom fields
            final Collection<AdminCustomField> adminCustomFields = baseAdminGroup.getAdminCustomFields();
            for (final AdminCustomField adminCustomField : adminCustomFields) {
                adminCustomField.getGroups().add(adminGroup);
                customFieldDao.update(adminCustomField);
            }
        } else if (group instanceof MemberGroup) {
            final MemberGroup memberGroup = (MemberGroup) group;
            final MemberGroup baseMemberGroup = (MemberGroup) baseGroup;

            // Managed by groups
            final Collection<AdminGroup> managedByGroups = baseMemberGroup.getManagedByGroups();
            for (final AdminGroup adminGroup : managedByGroups) {
                adminGroup.getManagesGroups().add(memberGroup);
                groupDao.update(adminGroup);
            }

            // Account settings
            final Collection<MemberGroupAccountSettings> baseMemberGroupAccountSettings = baseMemberGroup
                    .getAccountSettings();
            for (final MemberGroupAccountSettings baseAccountSettings : baseMemberGroupAccountSettings) {
                final MemberGroupAccountSettings accountSettings = (MemberGroupAccountSettings) baseAccountSettings
                        .clone();
                accountSettings.setId(null);
                accountSettings.setGroup(memberGroup);
                insertAccountSettings(accountSettings);
            }

            // Account fees
            final Collection<AccountFee> accountFees = baseMemberGroup.getAccountFees();
            for (final AccountFee accountFee : accountFees) {
                final Collection<MemberGroup> groups = accountFee.getGroups();
                groups.add(memberGroup);
                accountFeeDao.update(accountFee);
            }

            // Member custom fields and general remark custom fields)
            final Collection<CustomField> customFields = baseMemberGroup.getCustomFields();
            for (final CustomField customField : customFields) {
                // Get the groups using reflection
                final Collection<MemberGroup> groups = PropertyHelper.get(customField, "groups");
                groups.add(memberGroup);
                customFieldDao.update(customField);
            }

            // From transaction fees
            final Collection<TransactionFee> fromTransactionFees = baseMemberGroup.getFromTransactionFees();
            for (final TransactionFee transactionFee : fromTransactionFees) {
                transactionFee.getFromGroups().add(memberGroup);
                transactionFeeDao.update(transactionFee);
            }

            // To transaction fees
            final Collection<TransactionFee> toTransactionFees = baseMemberGroup.getToTransactionFees();
            for (final TransactionFee transactionFee : toTransactionFees) {
                transactionFee.getToGroups().add(memberGroup);
                transactionFeeDao.update(transactionFee);
            }

            // View profile of
            for (final MemberGroup other : memberGroup.getCanViewProfileOfGroups()) {
                other.getCanViewProfileOfGroups().add(memberGroup);
            }
            memberGroup.getCanViewProfileOfGroups().add(memberGroup);

            // View ads of
            for (final MemberGroup other : memberGroup.getCanViewAdsOfGroups()) {
                other.getCanViewAdsOfGroups().add(memberGroup);
            }
            memberGroup.getCanViewAdsOfGroups().add(memberGroup);

            // Group filters
            for (final GroupFilter groupFilter : baseMemberGroup.getGroupFilters()) {
                groupFilter.getGroups().add(memberGroup);
            }
            for (final GroupFilter groupFilter : baseMemberGroup.getCanViewGroupFilters()) {
                groupFilter.getViewableBy().add(memberGroup);
            }
        }

        return group;
    }

    private Validator getAccountSettingsValidator() {
        final Validator accountSettingsValidator = new Validator("account");
        accountSettingsValidator.property("group").displayName("group").required();
        accountSettingsValidator.property("accountType").displayName("account type").required();
        accountSettingsValidator.property("initialCredit").positive();
        accountSettingsValidator.general(new GeneralValidation() {
            private static final long serialVersionUID = 1L;

            @Override
            public ValidationError validate(final Object object) {

                final MemberGroupAccountSettings mgas = (MemberGroupAccountSettings) object;
                if (mgas.getInitialCreditTransferType() != null) {
                    TransferType tt = fetchService.fetch(mgas.getInitialCreditTransferType());
                    BigDecimal minAmount = tt.getMinAmount();
                    BigDecimal initialCredit = mgas.getInitialCredit();
                    boolean initialCreditIsPossitive = initialCredit != null
                            && initialCredit.compareTo(new BigDecimal(0)) > 0;

                    if (initialCreditIsPossitive && minAmount != null && (initialCredit.compareTo(minAmount) < 0)) {
                        return new ValidationError("group.account.error.minInitialCredit", initialCredit,
                                minAmount);
                    }
                }
                return null;
            }
        });
        accountSettingsValidator.property("initialCreditTransferType").add(new PropertyValidation() {
            private static final long serialVersionUID = 8284432136349418154L;

            @Override
            public ValidationError validate(final Object object, final Object name, final Object value) {
                final MemberGroupAccountSettings mgas = (MemberGroupAccountSettings) object;
                final TransferType tt = fetchService.fetch((TransferType) value, TransferType.Relationships.FROM,
                        TransferType.Relationships.TO);

                final BigDecimal initialCredit = mgas.getInitialCredit();
                // When there is initial credit, there must be a transfer type
                if (initialCredit != null && (initialCredit.compareTo(BigDecimal.ZERO) == 1) && tt == null) {
                    return new RequiredError();
                }
                // Must be from system to member
                if (tt != null && !(tt.isFromSystem() && !tt.isToSystem())) {
                    return new InvalidError();
                }
                return null;
            }
        });
        accountSettingsValidator.property("defaultCreditLimit").required().positive();
        accountSettingsValidator.property("defaultUpperCreditLimit").positive();
        accountSettingsValidator.property("lowUnits").positive();
        accountSettingsValidator.property("lowUnitsMessage").add(new PropertyValidation() {
            private static final long serialVersionUID = -6086632981851357180L;

            @Override
            public ValidationError validate(final Object object, final Object name, final Object value) {
                final MemberGroupAccountSettings mgas = (MemberGroupAccountSettings) object;
                final BigDecimal lowUnits = mgas.getLowUnits();
                // When there are low units, the message is required
                if (lowUnits != null && (lowUnits.compareTo(BigDecimal.ZERO) == 1)
                        && StringUtils.isEmpty(mgas.getLowUnitsMessage())) {
                    return new RequiredError();
                }
                return null;
            }
        });
        return accountSettingsValidator;
    }

    private Validator getAdminValidator() {
        final Validator adminValidator = new Validator("group");
        initSystem(adminValidator, true);
        return adminValidator;
    }

    private Validator getBrokerValidator() {
        final Validator brokerValidator = new Validator("group");
        initSystem(brokerValidator, true);
        initMember(brokerValidator);
        return brokerValidator;
    }

    private Validator getInsertValidator() {
        final Validator insertValidator = new Validator("group");
        insertValidator.property("name").required().maxLength(100);
        insertValidator.property("description").maxLength(2000);
        insertValidator.property("nature").required();
        insertValidator.property("status").required();
        return insertValidator;
    }

    private Validator getMemberValidator() {
        final Validator memberValidator = new Validator("group");
        initSystem(memberValidator, true);
        initMember(memberValidator);
        return memberValidator;
    }

    private Validator getOperatorValidator() {
        final Validator operatorValidator = new Validator("group");
        initBasic(operatorValidator, false);
        return operatorValidator;
    }

    private Validator getRemovedValidator() {
        final Validator removedValidator = new Validator("group");
        removedValidator.property("name").required().maxLength(100);
        removedValidator.property("description").maxLength(2000);
        removedValidator.property("nature").add(new ChangeNatureValidation());

        return removedValidator;
    }

    private void initBasic(final Validator validator, final boolean addSettings) {
        validator.property("name").required().maxLength(100);
        validator.property("description").maxLength(2000);
        validator.property("nature").add(new ChangeNatureValidation());

        if (addSettings) {
            validator.property("basicSettings.passwordLength.min").key("group.settings.passwordLength.min")
                    .between(1, 32);
            validator.property("basicSettings.passwordLength.max").key("group.settings.passwordLength.max")
                    .between(1, 32);
            validator.property("basicSettings.maxPasswordWrongTries").key("group.settings.passwordTries.maximum")
                    .between(0, 99);
            validator.property("basicSettings.deactivationAfterMaxPasswordTries.number")
                    .key("group.settings.passwordTries.deactivationTime.number").between(0, 999);
            validator.property("basicSettings.deactivationAfterMaxPasswordTries.field")
                    .key("group.settings.passwordTries.deactivationTime.field").required();
            validator.property("basicSettings.passwordExpiresAfter.number")
                    .key("group.settings.passwordExpiresAfter.number").between(0, 999);
            validator.property("basicSettings.passwordExpiresAfter.field")
                    .key("group.settings.passwordExpiresAfter.field").required();
            validator.property("basicSettings.transactionPassword").key("group.settings.transactionPassword")
                    .required();
            validator.property("basicSettings.transactionPasswordLength")
                    .key("group.settings.transactionPassword.length").between(1, 32);
            validator.property("basicSettings.maxTransactionPasswordWrongTries")
                    .key("group.settings.maxTransactionPasswordWrongTries").between(0, 99);
            validator.property("basicSettings.deactivationAfterMaxPasswordTries.number")
                    .key("group.settings.passwordTries.deactivationTime.number")
                    .add(new PasswordTrialsValidation());
            validator.property("basicSettings.passwordPolicy").key("group.settings.passwordPolicy")
                    .add(new PasswordPolicyValidation());
        }
    }

    private void initMember(final Validator validator) {
        validator.property("memberSettings.defaultAdPublicationTime.number")
                .key("group.settings.defaultAdPublicationTime.number").between(1, 999);
        validator.property("memberSettings.defaultAdPublicationTime.field")
                .key("group.settings.defaultAdPublicationTime.field").required();
        validator.property("memberSettings.maxAdPublicationTime.number")
                .key("group.settings.maxAdPublicationTime.number").between(1, 999);
        validator.property("memberSettings.maxAdPublicationTime.field")
                .key("group.settings.maxAdPublicationTime.field").required();
        validator.property("memberSettings.maxAdDescriptionSize").key("group.settings.maxAdDescriptionSize")
                .required().between(16, 16000);
        validator.property("memberSettings.maxAdsPerMember").key("group.settings.maxAdsPerMember").between(0, 999);
        validator.property("memberSettings.maxAdImagesPerMember").key("group.settings.maxAdImagesPerMember")
                .between(0, 999);
        validator.property("memberSettings.maxImagesPerMember").key("group.settings.maxImagesPerMember").between(0,
                999);
        validator.property("memberSettings.expireMembersAfter.number").key("group.settings.expireMembersAfter")
                .between(0, 999);
        validator.property("memberSettings.expireMembersAfter.field").key("group.settings.expireMembersAfter")
                .add(new ExpirationValidation(RequiredValidation.instance()));
        validator.property("memberSettings.groupAfterExpiration").key("group.settings.groupAfterExpiration")
                .add(new ExpirationValidation(RequiredValidation.instance()));
        validator.property("memberSettings.pinBlockTimeAfterMaxTries.number")
                .key("group.settings.pinBlockTimeAfterMaxTries.number").between(0, 999);
        validator.property("memberSettings.pinBlockTimeAfterMaxTries.field")
                .key("group.settings.pinBlockTimeAfterMaxTries.field")
                .add(new PINBlockTimeValidation(RequiredValidation.instance()));
        validator.property("memberSettings.smsContextClassName").key("group.settings.smsContextClassName")
                .instanceOf(ISmsContext.class);
        validator.property("memberSettings.pinBlockTimeAfterMaxTries.number")
                .key("group.settings.pinBlockTimeAfterMaxTries.number").add(new PinTrialsValidation());
    }

    private void initSystem(final Validator validator, final boolean addSettings) {
        initBasic(validator, addSettings);
        validator.property("rootUrl").maxLength(100).url();
        validator.property("loginPageName").maxLength(20);
        validator.property("containerUrl").maxLength(100).url();
    }

    @SuppressWarnings("unchecked")
    private void removeFromInverseCollections(final Group group) {

        // Payment filters
        final Collection<PaymentFilter> paymentFilters = group.getPaymentFilters();
        for (final PaymentFilter paymentFilter : paymentFilters) {
            final Collection<Group> groups = (Collection<Group>) paymentFilter.getGroups();
            groups.remove(group);
            paymentFilterDao.update(paymentFilter);
        }

        // Record types
        final Collection<MemberRecordType> recordTypes = group.getMemberRecordTypes();
        for (final MemberRecordType recordType : recordTypes) {
            recordType.getGroups().remove(group);
            memberRecordTypeDao.update(recordType);
        }

        if (group instanceof AdminGroup) {
            final AdminGroup adminGroup = (AdminGroup) group;

            // Connected admins viewed by
            final Collection<AdminGroup> connectedAdminsViewedBy = adminGroup.getConnectedAdminsViewedBy();
            for (final AdminGroup viewerAdminGroup : connectedAdminsViewedBy) {
                viewerAdminGroup.getViewConnectedAdminsOf().remove(adminGroup);
                groupDao.update(viewerAdminGroup);
            }

            // Admin custom fields
            final Collection<AdminCustomField> adminCustomFields = adminGroup.getAdminCustomFields();
            for (final AdminCustomField adminCustomField : adminCustomFields) {
                adminCustomField.getGroups().remove(adminGroup);
                customFieldDao.update(adminCustomField);
            }
        }

        if (group instanceof MemberGroup) {
            final MemberGroup memberGroup = (MemberGroup) group;

            // Account fees
            final Collection<AccountFee> accountFees = memberGroup.getAccountFees();
            for (final AccountFee accountFee : accountFees) {
                final Collection<MemberGroup> groups = accountFee.getGroups();
                groups.remove(memberGroup);
                accountFeeDao.update(accountFee);
            }

            // Managed by groups
            final Collection<AdminGroup> managedByGroups = memberGroup.getManagedByGroups();
            for (final AdminGroup adminGroup : managedByGroups) {
                adminGroup.getManagesGroups().remove(memberGroup);
                groupDao.update(adminGroup);
            }

            // Member custom fields and general remark custom fields)
            final Collection<CustomField> customFields = memberGroup.getCustomFields();
            for (final CustomField customField : customFields) {
                // Get the groups using reflection
                final Collection<MemberGroup> groups = PropertyHelper.get(customField, "groups");
                groups.remove(memberGroup);
                customFieldDao.update(customField);
            }

            // From transaction fees
            final Collection<TransactionFee> fromTransactionFees = memberGroup.getFromTransactionFees();
            for (final TransactionFee transactionFee : fromTransactionFees) {
                transactionFee.getFromGroups().remove(memberGroup);
                transactionFeeDao.update(transactionFee);
            }

            // To transaction fees
            final Collection<TransactionFee> toTransactionFees = memberGroup.getToTransactionFees();
            for (final TransactionFee transactionFee : toTransactionFees) {
                transactionFee.getToGroups().remove(memberGroup);
                transactionFeeDao.update(transactionFee);
            }

        }

    }

    @SuppressWarnings("unchecked")
    private <G extends Group> G save(G group) {
        if (group.isTransient()) {
            group = groupDao.insert(group);
        } else {
            // We must keep the many-to-many relationships, or they would be cleared...
            final Group currentGroup = load(group.getId(), FETCH_TO_KEEP_DATA);

            group.setPermissions(new HashSet<Permission>(currentGroup.getPermissions()));
            group.setTransferTypes(new ArrayList<TransferType>(currentGroup.getTransferTypes()));
            group.setConversionSimulationTTs(
                    new ArrayList<TransferType>(currentGroup.getConversionSimulationTTs()));
            group.setGuaranteeTypes(new ArrayList<GuaranteeType>(currentGroup.getGuaranteeTypes()));

            if (group instanceof SystemGroup) {
                final SystemGroup systemGroup = (SystemGroup) group;
                final SystemGroup currentSystemGroup = ((SystemGroup) currentGroup);
                systemGroup.setDocuments(new ArrayList<Document>(currentSystemGroup.getDocuments()));
                systemGroup.setMessageCategories(
                        new ArrayList<MessageCategory>(currentSystemGroup.getMessageCategories()));
                systemGroup.setChargebackTransferTypes(
                        new ArrayList<TransferType>(currentSystemGroup.getChargebackTransferTypes()));
            }

            if (group instanceof AdminGroup) {
                final AdminGroup adminGroup = (AdminGroup) group;
                final AdminGroup currentAdminGroup = ((AdminGroup) currentGroup);
                adminGroup.setTransferTypesAsMember(
                        new ArrayList<TransferType>(currentAdminGroup.getTransferTypesAsMember()));
                adminGroup.setManagesGroups(new ArrayList<MemberGroup>(currentAdminGroup.getManagesGroups()));
                adminGroup.setViewConnectedAdminsOf(
                        new ArrayList<AdminGroup>(currentAdminGroup.getViewConnectedAdminsOf()));
                adminGroup.setViewInformationOf(
                        new ArrayList<SystemAccountType>(currentAdminGroup.getViewInformationOf()));
                adminGroup.setViewAdminRecordTypes(
                        new ArrayList<MemberRecordType>(currentAdminGroup.getViewAdminRecordTypes()));
                adminGroup.setCreateAdminRecordTypes(
                        new ArrayList<MemberRecordType>(currentAdminGroup.getCreateAdminRecordTypes()));
                adminGroup.setModifyAdminRecordTypes(
                        new ArrayList<MemberRecordType>(currentAdminGroup.getModifyAdminRecordTypes()));
                adminGroup.setDeleteAdminRecordTypes(
                        new ArrayList<MemberRecordType>(currentAdminGroup.getDeleteAdminRecordTypes()));
                adminGroup.setViewMemberRecordTypes(
                        new ArrayList<MemberRecordType>(currentAdminGroup.getViewMemberRecordTypes()));
                adminGroup.setCreateMemberRecordTypes(
                        new ArrayList<MemberRecordType>(currentAdminGroup.getCreateMemberRecordTypes()));
                adminGroup.setModifyMemberRecordTypes(
                        new ArrayList<MemberRecordType>(currentAdminGroup.getModifyMemberRecordTypes()));
                adminGroup.setDeleteMemberRecordTypes(
                        new ArrayList<MemberRecordType>(currentAdminGroup.getDeleteMemberRecordTypes()));
            }
            if (group instanceof BrokerGroup) {
                final BrokerGroup brokerGroup = (BrokerGroup) group;
                final BrokerGroup currentBrokerGroup = (BrokerGroup) currentGroup;

                final List<Document> brokerDocuments = new ArrayList<Document>();
                if (currentBrokerGroup.getBrokerDocuments() != null) {
                    brokerDocuments.addAll(currentBrokerGroup.getBrokerDocuments());
                }
                brokerGroup.setBrokerDocuments(brokerDocuments);

                final List<AccountType> brokerCanViewInformationOf = new ArrayList<AccountType>();
                if (brokerGroup.getBrokerCanViewInformationOf() != null) {
                    brokerCanViewInformationOf.addAll(brokerGroup.getBrokerCanViewInformationOf());
                }
                brokerGroup.setBrokerCanViewInformationOf(brokerCanViewInformationOf);

                brokerGroup.setTransferTypesAsMember(
                        new ArrayList<TransferType>(currentBrokerGroup.getTransferTypesAsMember()));
                brokerGroup.setBrokerConversionSimulationTTs(
                        new ArrayList<TransferType>(currentBrokerGroup.getBrokerConversionSimulationTTs()));
                brokerGroup.setBrokerMemberRecordTypes(
                        new ArrayList<MemberRecordType>(currentBrokerGroup.getBrokerMemberRecordTypes()));
                brokerGroup.setBrokerCreateMemberRecordTypes(
                        new ArrayList<MemberRecordType>(currentBrokerGroup.getBrokerCreateMemberRecordTypes()));
                brokerGroup.setBrokerModifyMemberRecordTypes(
                        new ArrayList<MemberRecordType>(currentBrokerGroup.getBrokerModifyMemberRecordTypes()));
                brokerGroup.setBrokerDeleteMemberRecordTypes(
                        new ArrayList<MemberRecordType>(currentBrokerGroup.getBrokerDeleteMemberRecordTypes()));

                // "possibleInitialGroups" is updated at edit group screen, so it doesn't need to be copied
            }
            if (group instanceof MemberGroup) {
                final MemberGroup memberGroup = (MemberGroup) group;
                final MemberGroup currentMemberGroup = (MemberGroup) currentGroup;
                memberGroup.setAccountSettings(currentMemberGroup.getAccountSettings());

                // Ensure that no channel will be set by default if it's not accessible
                memberGroup.getDefaultChannels().retainAll(memberGroup.getChannels());

                // Ensure the removedChannels collection contains the channels which were removed
                final Collection<Channel> removedChannels = new HashSet<Channel>();
                removedChannels.addAll(currentMemberGroup.getChannels());
                removedChannels.removeAll(memberGroup.getChannels());

                final List<MemberGroup> viewProfile = new ArrayList<MemberGroup>();
                if (currentMemberGroup.getCanViewProfileOfGroups() != null) {
                    viewProfile.addAll(currentMemberGroup.getCanViewProfileOfGroups());
                }
                memberGroup.setCanViewProfileOfGroups(viewProfile);

                final List<AccountType> canViewInformationOf = new ArrayList<AccountType>();
                if (currentMemberGroup.getCanViewInformationOf() != null) {
                    canViewInformationOf.addAll(currentMemberGroup.getCanViewInformationOf());
                }
                memberGroup.setCanViewInformationOf(canViewInformationOf);

                final List<MemberGroup> viewAds = new ArrayList<MemberGroup>();
                if (currentMemberGroup.getCanViewAdsOfGroups() != null) {
                    viewAds.addAll(currentMemberGroup.getCanViewAdsOfGroups());
                }
                memberGroup.setCanViewAdsOfGroups(viewAds);

                final List<AdminGroup> managedByGroups = new ArrayList<AdminGroup>();
                if (currentMemberGroup.getManagedByGroups() != null) {
                    managedByGroups.addAll(currentMemberGroup.getManagedByGroups());
                }
                memberGroup.setManagedByGroups(managedByGroups);

                // "defaultMailMessages" is updated at edit group screen, so it doesn't need to be copied
                // "smsMessages" is updated at edit group screen, so it doesn't need to be copied
                // "defaultSmsMessages" is updated at edit group screen, so it doesn't need to be copied
                // "channels" is updated at edit group screen, so it doesn't need to be copied
                // "defaultChannels" is updated at edit group screen, so it doesn't need to be copied

                // Add the main web access to default and accessible channels.
                final Channel webChannel = channelService.loadByInternalName(Channel.WEB);
                memberGroup.setChannels(
                        CollectionUtils.union(memberGroup.getChannels(), Collections.singleton(webChannel)));
                memberGroup.setDefaultChannels(
                        CollectionUtils.union(memberGroup.getDefaultChannels(), Collections.singleton(webChannel)));

                final List<Channel> requestPaymentByChannels = new ArrayList<Channel>();
                if (currentMemberGroup.getRequestPaymentByChannels() != null) {
                    requestPaymentByChannels.addAll(currentMemberGroup.getRequestPaymentByChannels());
                }
                memberGroup.setRequestPaymentByChannels(requestPaymentByChannels);

                final MemberGroupSettings memberSettings = memberGroup.getMemberSettings();
                memberSettings
                        .setGroupAfterExpiration(fetchService.fetch(memberSettings.getGroupAfterExpiration()));

                // Update the basic settings of operator groups for members in this group
                final GroupQuery operatorQuery = new GroupQuery();
                operatorQuery.setNature(Group.Nature.OPERATOR);
                operatorQuery.fetch(
                        RelationshipHelper.nested(OperatorGroup.Relationships.MEMBER, Element.Relationships.GROUP));
                final List<OperatorGroup> operatorGroups = (List<OperatorGroup>) groupDao.search(operatorQuery);
                for (final OperatorGroup operatorGroup : operatorGroups) {
                    if (operatorGroup.getMember().getGroup().equals(memberGroup)) {
                        groupDao.update(operatorGroup);
                    }
                }
                final List<MemberGroup> canIssueCertificationToGroups = new ArrayList<MemberGroup>();
                if (currentMemberGroup.getCanIssueCertificationToGroups() != null) {
                    canIssueCertificationToGroups.addAll(currentMemberGroup.getCanIssueCertificationToGroups());
                }
                memberGroup.setCanIssueCertificationToGroups(canIssueCertificationToGroups);

                final List<MemberGroup> canBuyWithPaymentObligationsFromGroups = new ArrayList<MemberGroup>();
                if (currentMemberGroup.getCanBuyWithPaymentObligationsFromGroups() != null) {
                    canBuyWithPaymentObligationsFromGroups
                            .addAll(currentMemberGroup.getCanBuyWithPaymentObligationsFromGroups());
                }
                memberGroup.setCanBuyWithPaymentObligationsFromGroups(canBuyWithPaymentObligationsFromGroups);

                // Ensure the message notification types are not present on the group for SMS
                final Collection<Message.Type> smsMessages = memberGroup.getSmsMessages();
                if (smsMessages != null) {
                    smsMessages.remove(Message.Type.FROM_MEMBER);
                    smsMessages.remove(Message.Type.FROM_ADMIN_TO_GROUP);
                    smsMessages.remove(Message.Type.FROM_ADMIN_TO_MEMBER);
                }
                final Collection<Type> defaultSmsMessages = memberGroup.getDefaultSmsMessages();
                if (defaultSmsMessages != null) {
                    defaultSmsMessages.remove(Message.Type.FROM_MEMBER);
                    defaultSmsMessages.remove(Message.Type.FROM_ADMIN_TO_GROUP);
                    defaultSmsMessages.remove(Message.Type.FROM_ADMIN_TO_MEMBER);
                }

                // Remove from all members channels which are no longer accessible
                elementDao.removeChannelsFromMembers(memberGroup, removedChannels);

                // ensure activation status
                if (memberGroup.isRemoved()) {
                    memberGroup.setActive(false);
                }
            }
            if (group instanceof OperatorGroup) {
                final OperatorGroup operatorGroup = (OperatorGroup) group;
                final OperatorGroup currentOperatorGroup = (OperatorGroup) currentGroup;

                // Check the account types
                final List<AccountType> canViewInformationOf = new ArrayList<AccountType>();
                if (currentOperatorGroup.getCanViewInformationOf() != null) {
                    canViewInformationOf.addAll(currentOperatorGroup.getCanViewInformationOf());
                }
                operatorGroup.setCanViewInformationOf(canViewInformationOf);
            }
            group = groupDao.update(group);
        }
        // Ensure the permissions cache for this group is evicted
        permissionService.evictCache(group);
        return group;
    }

}