nl.strohalm.cyclos.controls.groups.EditGroupAction.java Source code

Java tutorial

Introduction

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

import java.io.File;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import nl.strohalm.cyclos.access.MemberPermission;
import nl.strohalm.cyclos.access.Permission;
import nl.strohalm.cyclos.annotations.Inject;
import nl.strohalm.cyclos.controls.ActionContext;
import nl.strohalm.cyclos.controls.BaseFormAction;
import nl.strohalm.cyclos.entities.access.Channel;
import nl.strohalm.cyclos.entities.access.TransactionPassword;
import nl.strohalm.cyclos.entities.accounts.AccountType;
import nl.strohalm.cyclos.entities.accounts.cards.CardType;
import nl.strohalm.cyclos.entities.accounts.transactions.TransferType;
import nl.strohalm.cyclos.entities.accounts.transactions.TransferTypeQuery;
import nl.strohalm.cyclos.entities.customization.files.CustomizedFile;
import nl.strohalm.cyclos.entities.customization.files.CustomizedFileQuery;
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.Group.Nature;
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.MemberGroupSettings.EmailValidation;
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.Member;
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.settings.LocalSettings;
import nl.strohalm.cyclos.services.access.ChannelService;
import nl.strohalm.cyclos.services.accounts.AccountTypeService;
import nl.strohalm.cyclos.services.accounts.MemberAccountTypeQuery;
import nl.strohalm.cyclos.services.accounts.cards.CardTypeService;
import nl.strohalm.cyclos.services.customization.CustomizedFileService;
import nl.strohalm.cyclos.services.elements.RegistrationAgreementService;
import nl.strohalm.cyclos.services.transactions.TransactionContext;
import nl.strohalm.cyclos.services.transfertypes.TransferTypeService;
import nl.strohalm.cyclos.utils.ActionHelper;
import nl.strohalm.cyclos.utils.CustomizationHelper;
import nl.strohalm.cyclos.utils.RelationshipHelper;
import nl.strohalm.cyclos.utils.RequestHelper;
import nl.strohalm.cyclos.utils.TimePeriod;
import nl.strohalm.cyclos.utils.TimePeriod.Field;
import nl.strohalm.cyclos.utils.binding.BeanBinder;
import nl.strohalm.cyclos.utils.binding.DataBinder;
import nl.strohalm.cyclos.utils.binding.DataBinderHelper;
import nl.strohalm.cyclos.utils.binding.MapBinder;
import nl.strohalm.cyclos.utils.binding.PropertyBinder;
import nl.strohalm.cyclos.utils.binding.SimpleCollectionBinder;
import nl.strohalm.cyclos.utils.conversion.IdConverter;
import nl.strohalm.cyclos.utils.query.PageHelper;
import nl.strohalm.cyclos.utils.validation.ValidationException;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.apache.commons.lang.StringUtils;
import org.apache.struts.action.ActionForward;

/**
 * Action used to edit a group
 * @author luis
 * @author Jefferson Magno
 */
public class EditGroupAction extends BaseFormAction {

    private static Group.Nature getGroupNature(final ActionContext context, final boolean acceptEmpty) {
        try {
            final EditGroupForm form = context.getForm();
            final String nature = (String) form.getGroup("nature");
            if (acceptEmpty && StringUtils.isBlank(nature)) {
                return null;
            } else {
                return Group.Nature.valueOf(nature);
            }
        } catch (final Exception e) {
            throw new ValidationException();
        }
    }

    private CustomizationHelper customizationHelper;
    private AccountTypeService accountTypeService;
    private RegistrationAgreementService registrationAgreementService;
    private CustomizedFileService customizedFileService;
    private TransferTypeService transferTypeService;
    private CardTypeService cardTypeService;
    private ChannelService channelService;
    private Map<Group.Nature, DataBinder<? extends Group>> dataBinders;

    public AccountTypeService getAccountTypeService() {
        return accountTypeService;
    }

    public CustomizedFileService getCustomizedFileService() {
        return customizedFileService;
    }

    public DataBinder<? extends Group> getDataBinder(final Group.Nature nature) {
        if (dataBinders == null) {
            dataBinders = new EnumMap<Group.Nature, DataBinder<? extends Group>>(Group.Nature.class);

            final BeanBinder<BasicGroupSettings> basicSettingsBinder = BeanBinder.instance(BasicGroupSettings.class,
                    "basicSettings");
            final BeanBinder<MemberGroupSettings> memberSettingsBinder = BeanBinder
                    .instance(MemberGroupSettings.class, "memberSettings");

            final BeanBinder<AdminGroup> adminGroupBinder = BeanBinder.instance(AdminGroup.class);
            initBasic(adminGroupBinder, basicSettingsBinder);
            initSystem(adminGroupBinder);
            dataBinders.put(Group.Nature.ADMIN, adminGroupBinder);

            final BeanBinder<MemberGroup> memberGroupBinder = BeanBinder.instance(MemberGroup.class);
            initBasic(memberGroupBinder, basicSettingsBinder);
            initSystem(memberGroupBinder);
            initMember(memberGroupBinder, memberSettingsBinder);
            dataBinders.put(Group.Nature.MEMBER, memberGroupBinder);

            final BeanBinder<OperatorGroup> operatorGroupBinder = BeanBinder.instance(OperatorGroup.class);
            initBasic(operatorGroupBinder, basicSettingsBinder);
            initOperator(operatorGroupBinder);
            dataBinders.put(Group.Nature.OPERATOR, operatorGroupBinder);

            final BeanBinder<BrokerGroup> brokerGroupBinder = BeanBinder.instance(BrokerGroup.class);
            initBasic(brokerGroupBinder, basicSettingsBinder);
            initSystem(brokerGroupBinder);
            initMember(brokerGroupBinder, memberSettingsBinder);
            initBroker(brokerGroupBinder);
            dataBinders.put(Group.Nature.BROKER, brokerGroupBinder);
        }
        return dataBinders.get(nature);
    }

    @Inject
    public void setAccountTypeService(final AccountTypeService accountTypeService) {
        this.accountTypeService = accountTypeService;
    }

    @Inject
    public void setCardTypeService(final CardTypeService cardTypeService) {
        this.cardTypeService = cardTypeService;
    }

    @Inject
    public void setChannelService(final ChannelService channelService) {
        this.channelService = channelService;
    }

    @Inject
    public void setCustomizationHelper(final CustomizationHelper customizationHelper) {
        this.customizationHelper = customizationHelper;
    }

    @Inject
    public void setCustomizedFileService(final CustomizedFileService customizedFileService) {
        this.customizedFileService = customizedFileService;
    }

    @Inject
    public void setRegistrationAgreementService(final RegistrationAgreementService registrationAgreementService) {
        this.registrationAgreementService = registrationAgreementService;
    }

    @Inject
    public void setTransferTypeService(final TransferTypeService transferTypeService) {
        this.transferTypeService = transferTypeService;
    }

    @Override
    protected ActionForward handleSubmit(final ActionContext context) throws Exception {
        final EditGroupForm form = context.getForm();
        Group group = readGroup(context);
        final Group baseGroup = readBaseGroup(context);
        final boolean isInsert = group.getId() == null;

        // Persist the group
        if (isInsert) {
            group = groupService.insert(group, baseGroup);
        } else {
            group = groupService.update(group, form.isForceAccept());
        }

        // Ensure the customized files collection is reloaded (for example, a group could be copied from other, and we need the collection to be
        // up-to-date)
        group = groupService.reload(group.getId(), Group.Relationships.CUSTOMIZED_FILES);

        // Physically update the files
        for (final CustomizedFile file : group.getCustomizedFiles()) {
            final File physicalFile = customizationHelper.customizedFileOf(file);
            customizationHelper.updateFile(physicalFile, file);
        }

        context.sendMessage(isInsert ? "group.inserted" : "group.modified");
        return ActionHelper.redirectWithParam(context.getRequest(), context.getSuccessForward(), "groupId",
                group.getId());
    }

    @Override
    @SuppressWarnings({ "unchecked", "rawtypes" })
    protected void prepareForm(final ActionContext context) throws Exception {
        final HttpServletRequest request = context.getRequest();
        final EditGroupForm form = context.getForm();
        final long id = form.getGroupId();
        boolean editable = false;
        boolean canManageFiles = false;

        final Map<Group.Nature, Permission> permissionByNature = ListGroupsAction
                .getManageGroupPermissionByNatureMap();

        final boolean isInsert = id <= 0L;
        if (isInsert) {
            // Prepare for insert
            List<Group.Nature> natures = new ArrayList<Group.Nature>();
            if (context.isAdmin()) {
                // Put in the request the name of permission used to manage a type of group
                request.setAttribute("permissionByNature", permissionByNature);

                for (final Group.Nature nature : permissionByNature.keySet()) {
                    final Permission permission = permissionByNature.get(nature);
                    if (permissionService.hasPermission(permission)) {
                        natures.add(nature);
                    }
                }
            } else {
                // It's a member inserting an operator group
                final GroupQuery groupQuery = new GroupQuery();
                groupQuery.setMember((Member) context.getElement());
                final List<OperatorGroup> baseGroups = (List<OperatorGroup>) groupService.search(groupQuery);
                request.setAttribute("baseGroups", baseGroups);
                request.setAttribute("isOperatorGroup", true);
                natures = Collections.singletonList(Group.Nature.OPERATOR);
            }
            request.setAttribute("natures", natures);
            RequestHelper.storeEnum(request, Group.Status.class, "status");
            editable = true;
        } else {
            // Prepare for modify
            Group group = groupService.load(id, Group.Relationships.CUSTOMIZED_FILES,
                    MemberGroup.Relationships.CHANNELS);
            final boolean isMemberGroup = MemberGroup.class.isAssignableFrom(group.getNature().getGroupClass());
            final boolean isBrokerGroup = BrokerGroup.class.isAssignableFrom(group.getNature().getGroupClass());
            final boolean isOperatorGroup = OperatorGroup.class.isAssignableFrom(group.getNature().getGroupClass());
            if (group.getStatus().isEnabled()) {
                request.setAttribute("deactivationTimePeriodFields",
                        Arrays.asList(Field.SECONDS, Field.MINUTES, Field.HOURS, Field.DAYS));
                request.setAttribute("passwordExpiresAfterFields",
                        Arrays.asList(Field.DAYS, Field.WEEKS, Field.MONTHS, Field.YEARS));
                RequestHelper.storeEnum(request, TransactionPassword.class, "transactionPasswords");
                if (isMemberGroup) {
                    final MemberGroup memberGroup = (MemberGroup) group;

                    // Check if the group has access to a channel that uses pin
                    final boolean usesPin = groupService.usesPin(memberGroup);
                    request.setAttribute("usesPin", usesPin);

                    // Retrieve the registration agreements
                    final List<RegistrationAgreement> registrationAgreements = registrationAgreementService
                            .listAll();
                    request.setAttribute("registrationAgreements", registrationAgreements);

                    // Retrieve the associated accounts
                    request.setAttribute("timePeriodFields",
                            Arrays.asList(Field.DAYS, Field.WEEKS, Field.MONTHS, Field.YEARS));
                    final MemberAccountTypeQuery atQuery = new MemberAccountTypeQuery();
                    atQuery.setRelatedToGroup(memberGroup);
                    request.setAttribute("accountTypes", accountTypeService.search(atQuery));

                    // Sort the message types using their messages
                    final List<Type> messageTypes = Arrays.asList(Message.Type.values());
                    final Comparator cmp = new Comparator() {
                        @Override
                        public int compare(final Object o1, final Object o2) {
                            final String msg1 = context.message("message.type." + o1);
                            final String msg2 = context.message("message.type." + o2);
                            return msg1.compareTo(msg2);
                        }
                    };
                    Collections.sort(messageTypes, cmp);
                    request.setAttribute("messageTypes", messageTypes);

                    // we create a wrapper ArrayList because the list must implement the remove method
                    final List<Type> smsMessageTypes = new ArrayList<Type>(Arrays.asList(Message.Type.values()));
                    CollectionUtils.filter(smsMessageTypes, new Predicate() {
                        @Override
                        public boolean evaluate(final Object object) {
                            final Message.Type type = (Message.Type) object;
                            switch (type) {
                            case FROM_MEMBER:
                            case FROM_ADMIN_TO_MEMBER:
                            case FROM_ADMIN_TO_GROUP:
                                return false;
                            default:
                                return true;
                            }
                        }
                    });
                    Collections.sort(smsMessageTypes, cmp);
                    request.setAttribute("smsMessageTypes", smsMessageTypes);

                    // Store the possible groups for expiration
                    final GroupQuery query = new GroupQuery();
                    query.setNatures(Group.Nature.MEMBER, Group.Nature.BROKER);
                    final List<? extends Group> groups = groupService.search(query);
                    groups.remove(group);
                    request.setAttribute("possibleExpirationGroups", groups);
                    request.setAttribute("expirationTimeFields", Arrays.asList(TimePeriod.Field.DAYS,
                            TimePeriod.Field.WEEKS, TimePeriod.Field.MONTHS, TimePeriod.Field.YEARS));

                    // Store transfer types for SMS charge
                    final TransferTypeQuery ttQuery = new TransferTypeQuery();
                    ttQuery.setFromGroups(Collections.singletonList(memberGroup));
                    ttQuery.setToNature(AccountType.Nature.SYSTEM);
                    final List<TransferType> smsChargeTransferTypes = transferTypeService.search(ttQuery);
                    request.setAttribute("smsChargeTransferTypes", smsChargeTransferTypes);

                    request.setAttribute("smsAdditionalChargedPeriodFields",
                            Arrays.asList(Field.DAYS, Field.WEEKS, Field.MONTHS));

                    // Retrieve the card types
                    request.setAttribute("cardTypes", cardTypeService.listAll());
                }
                if (isBrokerGroup) {
                    // Retrieve the possible groups for registered members by broker
                    final GroupQuery query = new GroupQuery();
                    query.setNatures(Group.Nature.MEMBER, Group.Nature.BROKER);
                    query.setStatus(Group.Status.NORMAL);
                    request.setAttribute("memberGroups", groupService.search(query));
                }
                if (isMemberGroup || isBrokerGroup) {
                    RequestHelper.storeEnum(request, MemberGroupSettings.ExternalAdPublication.class,
                            "externalAdPublications");
                }
                if (isOperatorGroup) {
                    // Load the associated transaction types for the max amount per day
                    group = groupService.load(group.getId(),
                            RelationshipHelper.nested(OperatorGroup.Relationships.MEMBER,
                                    Element.Relationships.GROUP),
                            OperatorGroup.Relationships.MAX_AMOUNT_PER_DAY_BY_TRANSFER_TYPE,
                            OperatorGroup.Relationships.MAX_AMOUNT_PER_DAY_BY_TRANSFER_TYPE);
                    final OperatorGroup operatorGroup = (OperatorGroup) group;
                    request.setAttribute("transferTypes", operatorGroup.getMember().getGroup().getTransferTypes());
                }

                // Retrieve the associated customized files
                final CustomizedFileQuery cfQuery = new CustomizedFileQuery();
                cfQuery.setGroup(group);
                request.setAttribute("customizedFiles", customizedFileService.search(cfQuery));

                // Check whether the login page name will be shown
                request.setAttribute("showLoginPageName",
                        customizationHelper.isAnyFileRelatedToLoginPage(group.getCustomizedFiles()));
            }
            getDataBinder(group.getNature()).writeAsString(form.getGroup(), group);
            request.setAttribute("group", group);
            request.setAttribute("isMemberGroup", isMemberGroup);
            request.setAttribute("isBrokerGroup", isBrokerGroup);
            request.setAttribute("isOperatorGroup", isOperatorGroup);

            if (isMemberGroup) {
                // Show scheduling options when there's a schedulable transfer type
                final TransferTypeQuery ttQuery = new TransferTypeQuery();
                ttQuery.setPageForCount();
                ttQuery.setContext(TransactionContext.PAYMENT);
                ttQuery.setGroup(group);
                ttQuery.setSchedulable(true);
                request.setAttribute("showScheduling",
                        PageHelper.getTotalCount(transferTypeService.search(ttQuery)) > 0);

                // Channels that the group of member have access
                final Collection<Channel> channels = channelService.list();
                // The "web" channel can not be customized by the user, so it should not be sent to the JSP page
                final Channel webChannel = channelService.loadByInternalName(Channel.WEB);
                channels.remove(webChannel);
                request.setAttribute("channels", channels);

                RequestHelper.storeEnum(request, EmailValidation.class, "emailValidations");
            }

            if (context.isAdmin()) {
                AdminGroup adminGroup = context.getGroup();
                adminGroup = groupService.load(adminGroup.getId(), AdminGroup.Relationships.MANAGES_GROUPS);
                if (permissionService.hasPermission(permissionByNature.get(group.getNature()))
                        && (Group.Nature.ADMIN.equals(group.getNature())
                                || adminGroup.getManagesGroups().contains(group))) {
                    editable = true;
                }
            } else {
                // It's a member updating an operator group
                editable = permissionService.hasPermission(MemberPermission.OPERATORS_MANAGE);
            }
            canManageFiles = customizedFileService.canViewOrManageInGroup(group);
        }

        request.setAttribute("isInsert", isInsert);
        request.setAttribute("editable", editable);
        request.setAttribute("canManageFiles", canManageFiles);
        RequestHelper.storeEnum(request, PasswordPolicy.class, "passwordPolicies");
    }

    @Override
    protected void validateForm(final ActionContext context) {
        final Group group = readGroup(context);
        // Check if it's a insert operation
        groupService.validate(group);
        if (group instanceof MemberGroup) {
            final EditGroupForm form = context.getForm();
            final MemberGroup memberGroup = (MemberGroup) group;
            // Ensure the smsContext is present when the checkbox for using a custom context is checked
            if (form.isUseCustomSMSContextClass()
                    && StringUtils.isEmpty(memberGroup.getMemberSettings().getSmsContextClassName())) {
                throw new ValidationException("errors.required",
                        context.message("group.settings.smsContextClassName"));
            }
        }
    }

    private void initBasic(final BeanBinder<? extends Group> groupBinder,
            final BeanBinder<? extends BasicGroupSettings> basicSettingsBinder) {
        groupBinder.registerBinder("basicSettings", basicSettingsBinder);

        groupBinder.registerBinder("id", PropertyBinder.instance(Long.class, "id", IdConverter.instance()));
        groupBinder.registerBinder("name", PropertyBinder.instance(String.class, "name"));
        groupBinder.registerBinder("description", PropertyBinder.instance(String.class, "description"));
        groupBinder.registerBinder("status", PropertyBinder.instance(Group.Status.class, "status"));

        basicSettingsBinder.registerBinder("passwordLength",
                DataBinderHelper.rangeConstraintBinder("passwordLength"));
        basicSettingsBinder.registerBinder("passwordPolicy",
                PropertyBinder.instance(PasswordPolicy.class, "passwordPolicy"));
        basicSettingsBinder.registerBinder("maxPasswordWrongTries",
                PropertyBinder.instance(Integer.TYPE, "maxPasswordWrongTries"));
        basicSettingsBinder.registerBinder("deactivationAfterMaxPasswordTries",
                DataBinderHelper.timePeriodBinder("deactivationAfterMaxPasswordTries"));
        basicSettingsBinder.registerBinder("passwordExpiresAfter",
                DataBinderHelper.timePeriodBinder("passwordExpiresAfter"));
        basicSettingsBinder.registerBinder("transactionPassword",
                PropertyBinder.instance(TransactionPassword.class, "transactionPassword"));
        basicSettingsBinder.registerBinder("transactionPasswordLength",
                PropertyBinder.instance(Integer.TYPE, "transactionPasswordLength"));
        basicSettingsBinder.registerBinder("maxTransactionPasswordWrongTries",
                PropertyBinder.instance(Integer.TYPE, "maxTransactionPasswordWrongTries"));
        basicSettingsBinder.registerBinder("hideCurrencyOnPayments",
                PropertyBinder.instance(Boolean.TYPE, "hideCurrencyOnPayments"));
    }

    private void initBroker(final BeanBinder<? extends BrokerGroup> brokerGroupBinder) {
        brokerGroupBinder.registerBinder("possibleInitialGroups",
                SimpleCollectionBinder.instance(MemberGroup.class, "possibleInitialGroups"));
    }

    private void initMember(final BeanBinder<? extends MemberGroup> memberGroupBinder,
            final BeanBinder<? extends MemberGroupSettings> memberSettingsBinder) {
        memberGroupBinder.registerBinder("memberSettings", memberSettingsBinder);
        memberGroupBinder.registerBinder("active", PropertyBinder.instance(Boolean.TYPE, "active"));
        memberGroupBinder.registerBinder("initialGroup", PropertyBinder.instance(Boolean.TYPE, "initialGroup"));
        memberGroupBinder.registerBinder("initialGroupShow",
                PropertyBinder.instance(String.class, "initialGroupShow"));
        memberGroupBinder.registerBinder("registrationAgreement",
                PropertyBinder.instance(RegistrationAgreement.class, "registrationAgreement"));
        memberGroupBinder.registerBinder("defaultMailMessages",
                SimpleCollectionBinder.instance(Message.Type.class, "defaultMailMessages"));
        memberGroupBinder.registerBinder("smsMessages",
                SimpleCollectionBinder.instance(Message.Type.class, "smsMessages"));
        memberGroupBinder.registerBinder("defaultSmsMessages",
                SimpleCollectionBinder.instance(Message.Type.class, "defaultSmsMessages"));
        memberGroupBinder.registerBinder("defaultAllowChargingSms",
                PropertyBinder.instance(Boolean.TYPE, "defaultAllowChargingSms"));
        memberGroupBinder.registerBinder("defaultAcceptFreeMailing",
                PropertyBinder.instance(Boolean.TYPE, "defaultAcceptFreeMailing"));
        memberGroupBinder.registerBinder("defaultAcceptPaidMailing",
                PropertyBinder.instance(Boolean.TYPE, "defaultAcceptPaidMailing"));
        memberGroupBinder.registerBinder("channels", SimpleCollectionBinder.instance(Channel.class, "channels"));
        memberGroupBinder.registerBinder("defaultChannels",
                SimpleCollectionBinder.instance(Channel.class, "defaultChannels"));
        memberGroupBinder.registerBinder("cardType", PropertyBinder.instance(CardType.class, "cardType"));

        final LocalSettings localSettings = settingsService.getLocalSettings();
        memberSettingsBinder.registerBinder("pinLength", DataBinderHelper.rangeConstraintBinder("pinLength"));
        memberSettingsBinder.registerBinder("maxPinWrongTries",
                PropertyBinder.instance(Integer.TYPE, "maxPinWrongTries"));
        memberSettingsBinder.registerBinder("pinBlockTimeAfterMaxTries",
                DataBinderHelper.timePeriodBinder("pinBlockTimeAfterMaxTries"));
        memberSettingsBinder.registerBinder("pinLength", DataBinderHelper.rangeConstraintBinder("pinLength"));
        memberSettingsBinder.registerBinder("smsChargeTransferType",
                PropertyBinder.instance(TransferType.class, "smsChargeTransferType"));
        memberSettingsBinder.registerBinder("smsChargeAmount",
                PropertyBinder.instance(BigDecimal.class, "smsChargeAmount", localSettings.getNumberConverter()));
        memberSettingsBinder.registerBinder("smsFree", PropertyBinder.instance(Integer.TYPE, "smsFree"));
        memberSettingsBinder.registerBinder("smsShowFreeThreshold",
                PropertyBinder.instance(Integer.TYPE, "smsShowFreeThreshold"));
        memberSettingsBinder.registerBinder("smsAdditionalCharged",
                PropertyBinder.instance(Integer.TYPE, "smsAdditionalCharged"));
        memberSettingsBinder.registerBinder("smsAdditionalChargedPeriod",
                DataBinderHelper.timePeriodBinder("smsAdditionalChargedPeriod"));
        memberSettingsBinder.registerBinder("smsContextClassName",
                PropertyBinder.instance(String.class, "smsContextClassName"));

        memberSettingsBinder.registerBinder("maxAdsPerMember",
                PropertyBinder.instance(Integer.TYPE, "maxAdsPerMember"));
        memberSettingsBinder.registerBinder("maxAdImagesPerMember",
                PropertyBinder.instance(Integer.TYPE, "maxAdImagesPerMember"));
        memberSettingsBinder.registerBinder("defaultAdPublicationTime",
                DataBinderHelper.timePeriodBinder("defaultAdPublicationTime"));
        memberSettingsBinder.registerBinder("maxAdPublicationTime",
                DataBinderHelper.timePeriodBinder("maxAdPublicationTime"));
        memberSettingsBinder.registerBinder("enablePermanentAds",
                PropertyBinder.instance(Boolean.TYPE, "enablePermanentAds"));
        memberSettingsBinder.registerBinder("externalAdPublication",
                PropertyBinder.instance(MemberGroupSettings.ExternalAdPublication.class, "externalAdPublication"));
        memberSettingsBinder.registerBinder("maxAdDescriptionSize",
                PropertyBinder.instance(Integer.TYPE, "maxAdDescriptionSize"));
        memberSettingsBinder.registerBinder("sendPasswordByEmail",
                PropertyBinder.instance(Boolean.TYPE, "sendPasswordByEmail"));
        memberSettingsBinder.registerBinder("emailValidation",
                SimpleCollectionBinder.instance(EmailValidation.class, HashSet.class, "emailValidation"));
        memberSettingsBinder.registerBinder("maxImagesPerMember",
                PropertyBinder.instance(Integer.TYPE, "maxImagesPerMember"));
        memberSettingsBinder.registerBinder("viewLoansByGroup",
                PropertyBinder.instance(Boolean.TYPE, "viewLoansByGroup"));
        memberSettingsBinder.registerBinder("repayLoanByGroup",
                PropertyBinder.instance(Boolean.TYPE, "repayLoanByGroup"));
        memberSettingsBinder.registerBinder("maxSchedulingPayments",
                PropertyBinder.instance(Integer.TYPE, "maxSchedulingPayments"));
        memberSettingsBinder.registerBinder("maxSchedulingPeriod",
                DataBinderHelper.timePeriodBinder("maxSchedulingPeriod"));
        memberSettingsBinder.registerBinder("showPosWebPaymentDescription",
                PropertyBinder.instance(Boolean.TYPE, "showPosWebPaymentDescription"));
        memberSettingsBinder.registerBinder("expireMembersAfter",
                DataBinderHelper.timePeriodBinder("expireMembersAfter"));
        memberSettingsBinder.registerBinder("groupAfterExpiration",
                PropertyBinder.instance(MemberGroup.class, "groupAfterExpiration"));
    }

    private void initOperator(final BeanBinder<? extends OperatorGroup> operatorGroupBinder) {
        // Bind max amount per day by transfer type map
        final LocalSettings localSettings = settingsService.getLocalSettings();
        final PropertyBinder<TransferType> keyBinder = PropertyBinder.instance(TransferType.class,
                "transferTypeIds");
        final PropertyBinder<BigDecimal> valueBinder = PropertyBinder.instance(BigDecimal.class,
                "maxAmountPerDayByTT", localSettings.getNumberConverter());
        final MapBinder<TransferType, BigDecimal> maxAmountBinder = MapBinder.instance(keyBinder, valueBinder);
        operatorGroupBinder.registerBinder("maxAmountPerDayByTransferType", maxAmountBinder);
    }

    private void initSystem(final BeanBinder<? extends SystemGroup> groupBinder) {
        groupBinder.registerBinder("rootUrl", PropertyBinder.instance(String.class, "rootUrl"));
        groupBinder.registerBinder("loginPageName", PropertyBinder.instance(String.class, "loginPageName"));
        groupBinder.registerBinder("containerUrl", PropertyBinder.instance(String.class, "containerUrl"));
    }

    private Group readBaseGroup(final ActionContext context) {
        final EditGroupForm form = context.getForm();
        final long baseGroupId = form.getBaseGroupId();
        if (baseGroupId < 1) {
            return null;
        }
        return groupService.load(baseGroupId);
    }

    private Group readGroup(final ActionContext context) {
        final EditGroupForm form = context.getForm();
        final long id = form.getGroupId();
        Group.Nature nature;
        final boolean isInsert = (id <= 0L);
        if (isInsert) {
            nature = getGroupNature(context, false);
            // On insert, empty status means normal
            final String status = (String) form.getGroup("status");
            if (StringUtils.isEmpty(status)) {
                form.setGroup("status", Group.Status.NORMAL.name());
            }
        } else {
            nature = groupService.load(id).getNature();
        }
        final Group group = getDataBinder(nature).readFromString(form.getGroup());
        if (nature == Nature.OPERATOR) {
            // Ensure to set the logged member on operator groups, as this is not read from request
            final Member member = (Member) context.getElement();
            final OperatorGroup operatorGroup = (OperatorGroup) group;
            operatorGroup.setMember(member);
        }
        return group;
    }

}