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

Java tutorial

Introduction

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

Source

/*
This file is part of Cyclos (www.cyclos.org).
A project of the Social Trade Organisation (www.socialtrade.org).
    
Cyclos is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
    
Cyclos is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
    
You should have received a copy of the GNU General Public License
along with Cyclos; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
    
 */
package nl.strohalm.cyclos.services.elements;

import java.math.BigDecimal;
import java.util.Calendar;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;

import nl.strohalm.cyclos.access.AdminMemberPermission;
import nl.strohalm.cyclos.access.BrokerPermission;
import nl.strohalm.cyclos.access.MemberPermission;
import nl.strohalm.cyclos.access.OperatorPermission;
import nl.strohalm.cyclos.dao.members.ElementDAO;
import nl.strohalm.cyclos.dao.sms.MemberSmsStatusDAO;
import nl.strohalm.cyclos.entities.access.User;
import nl.strohalm.cyclos.entities.accounts.Account;
import nl.strohalm.cyclos.entities.accounts.AccountStatus;
import nl.strohalm.cyclos.entities.accounts.MemberAccount;
import nl.strohalm.cyclos.entities.accounts.MemberAccountType;
import nl.strohalm.cyclos.entities.accounts.loans.Loan;
import nl.strohalm.cyclos.entities.accounts.loans.LoanQuery;
import nl.strohalm.cyclos.entities.accounts.transactions.Invoice;
import nl.strohalm.cyclos.entities.accounts.transactions.InvoiceQuery;
import nl.strohalm.cyclos.entities.accounts.transactions.InvoiceSummaryDTO;
import nl.strohalm.cyclos.entities.accounts.transactions.TransferQuery;
import nl.strohalm.cyclos.entities.accounts.transactions.TransferType;
import nl.strohalm.cyclos.entities.accounts.transactions.TransfersAwaitingAuthorizationQuery;
import nl.strohalm.cyclos.entities.customization.fields.MemberCustomField;
import nl.strohalm.cyclos.entities.exceptions.EntityNotFoundException;
import nl.strohalm.cyclos.entities.groups.BrokerGroup;
import nl.strohalm.cyclos.entities.groups.Group.Nature;
import nl.strohalm.cyclos.entities.groups.MemberGroup;
import nl.strohalm.cyclos.entities.groups.OperatorGroup;
import nl.strohalm.cyclos.entities.members.Administrator;
import nl.strohalm.cyclos.entities.members.BrokeringQuery;
import nl.strohalm.cyclos.entities.members.Element;
import nl.strohalm.cyclos.entities.members.FullTextMemberQuery;
import nl.strohalm.cyclos.entities.members.Member;
import nl.strohalm.cyclos.entities.members.Operator;
import nl.strohalm.cyclos.entities.members.PaymentsAwaitingFeedbackQuery;
import nl.strohalm.cyclos.entities.members.Reference;
import nl.strohalm.cyclos.entities.members.ReferenceQuery;
import nl.strohalm.cyclos.entities.members.brokerings.BrokerCommissionContract;
import nl.strohalm.cyclos.entities.members.brokerings.BrokerCommissionContractQuery;
import nl.strohalm.cyclos.entities.members.messages.MessageBox;
import nl.strohalm.cyclos.entities.members.messages.MessageQuery;
import nl.strohalm.cyclos.entities.members.preferences.NotificationPreference;
import nl.strohalm.cyclos.entities.sms.MemberSmsStatus;
import nl.strohalm.cyclos.exceptions.PermissionDeniedException;
import nl.strohalm.cyclos.services.access.AccessServiceLocal;
import nl.strohalm.cyclos.services.access.ChannelServiceLocal;
import nl.strohalm.cyclos.services.accounts.AccountServiceLocal;
import nl.strohalm.cyclos.services.accounts.GetTransactionsDTO;
import nl.strohalm.cyclos.services.accounts.rates.ConversionSimulationDTO;
import nl.strohalm.cyclos.services.accounts.rates.RateServiceLocal;
import nl.strohalm.cyclos.services.ads.AdServiceLocal;
import nl.strohalm.cyclos.services.customization.MemberCustomFieldService;
import nl.strohalm.cyclos.services.fetch.FetchServiceLocal;
import nl.strohalm.cyclos.services.groups.GroupServiceLocal;
import nl.strohalm.cyclos.services.permissions.PermissionServiceLocal;
import nl.strohalm.cyclos.services.preferences.PreferenceServiceLocal;
import nl.strohalm.cyclos.services.sms.ISmsContext;
import nl.strohalm.cyclos.services.sms.MemberGroupSmsContextImpl;
import nl.strohalm.cyclos.services.sms.exceptions.SmsContextInitializationException;
import nl.strohalm.cyclos.services.transactions.InvoiceServiceLocal;
import nl.strohalm.cyclos.services.transactions.LoanServiceLocal;
import nl.strohalm.cyclos.services.transactions.PaymentServiceLocal;
import nl.strohalm.cyclos.services.transactions.TransactionSummaryVO;
import nl.strohalm.cyclos.services.transactions.TransferAuthorizationServiceLocal;
import nl.strohalm.cyclos.services.transfertypes.TransactionFeePreviewForRatesDTO;
import nl.strohalm.cyclos.services.transfertypes.TransferTypeServiceLocal;
import nl.strohalm.cyclos.utils.CustomFieldHelper;
import nl.strohalm.cyclos.utils.CustomObjectHandler;
import nl.strohalm.cyclos.utils.DateHelper;
import nl.strohalm.cyclos.utils.IteratorListImpl;
import nl.strohalm.cyclos.utils.Period;
import nl.strohalm.cyclos.utils.RelationshipHelper;
import nl.strohalm.cyclos.utils.TimePeriod;
import nl.strohalm.cyclos.utils.TransactionHelper;
import nl.strohalm.cyclos.utils.Transactional;
import nl.strohalm.cyclos.utils.access.LoggedUser;
import nl.strohalm.cyclos.utils.lock.LockHandler;
import nl.strohalm.cyclos.utils.lock.LockHandlerFactory;
import nl.strohalm.cyclos.utils.query.IteratorList;
import nl.strohalm.cyclos.utils.query.PageHelper;
import nl.strohalm.cyclos.webservices.WebServiceContext;
import nl.strohalm.cyclos.webservices.members.FullTextMemberSearchParameters;
import nl.strohalm.cyclos.webservices.members.MemberResultPage;
import nl.strohalm.cyclos.webservices.model.MemberVO;
import nl.strohalm.cyclos.webservices.model.MyProfileVO;
import nl.strohalm.cyclos.webservices.utils.MemberHelper;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.transaction.TransactionStatus;

/**
 * Implementation for MemberService
 * @author luis
 */
public class MemberServiceImpl implements MemberServiceLocal {

    private FetchServiceLocal fetchService;
    private PreferenceServiceLocal preferenceService;
    private AccountServiceLocal accountService;
    private InvoiceServiceLocal invoiceService;
    private PermissionServiceLocal permissionService;
    private BrokeringServiceLocal brokeringService;
    private LoanServiceLocal loanService;
    private AdServiceLocal adService;
    private ReferenceServiceLocal referenceService;
    private PaymentServiceLocal paymentService;
    private TransferAuthorizationServiceLocal transferAuthorizationService;
    private MessageServiceLocal messageService;
    private ElementDAO elementDao;
    private RateServiceLocal rateService;
    private TransferTypeServiceLocal transferTypeService;
    private AccessServiceLocal accessService;
    private ChannelServiceLocal channelService;
    private MemberSmsStatusDAO memberSmsStatusDao;
    private GroupServiceLocal groupService;
    private LockHandlerFactory lockHandlerFactory;
    private TransactionHelper transactionHelper;
    private CommissionServiceLocal commissionService;
    private CustomObjectHandler customObjectHandler;
    private ElementServiceLocal elementService;
    private MemberHelper memberHelper;
    private MemberCustomFieldService memberCustomFieldService;
    private CustomFieldHelper customFieldHelper;

    /**
     * Ensure the sms status has the "allow charging sms" flag set correctly.<br>
     * If there isn't free sms, the additional charged package's size is one and the member accept any feature by sms then the flag is set to true.
     */
    @Override
    public void ensureAllowChargingSms(final MemberSmsStatus memberSmsStatus, final boolean hasNotificationsBySms) {
        // Check if we must set the allowChargingSms flag
        Member member = memberSmsStatus.getMember();
        final ISmsContext smsContext = getSmsContext(member);
        final int smsFree = smsContext.getFreeSms(member);
        final int smsAdditionalCharged = smsContext.getAdditionalChargedSms(member);
        if (smsAdditionalCharged == 1 && smsFree == 0) {
            Member fetchedMember = fetchService.fetch(member, Member.Relationships.CHANNELS);
            final boolean smsChannelEnabled = accessService.getChannelsEnabledForMember(fetchedMember)
                    .contains(channelService.getSmsChannel());
            final boolean allowChargingSms = smsChannelEnabled || memberSmsStatus.isAcceptPaidMailing()
                    || hasNotificationsBySms;
            memberSmsStatus.setAllowChargingSms(allowChargingSms);
        }
    }

    @Override
    public ActivitiesVO getActivities(Member member) {
        member = fetchService.fetch(member, Element.Relationships.GROUP);
        return doGetActivities(member);
    }

    @Override
    public Map<MemberGroup, Integer> getGroupMemberCount(final Collection<MemberGroup> groups,
            final Calendar timePoint) {
        Map<Long, Integer> countPerGroupId;
        if (timePoint == null) {
            countPerGroupId = elementDao.getCountPerGroup(groups);
        } else {
            countPerGroupId = elementDao.getCountPerGroup(groups, timePoint);
        }
        final Map<MemberGroup, Integer> groupMemberCount = new TreeMap<MemberGroup, Integer>();
        // Initialize with zeros
        for (MemberGroup group : groups) {
            group = groupService.load(group.getId());
            groupMemberCount.put(group, 0);
        }
        // Count
        for (final Entry<Long, Integer> entry : countPerGroupId.entrySet()) {
            MemberGroup group = groupService.load(entry.getKey());
            Integer count = entry.getValue();
            groupMemberCount.put(group, count);
        }
        return groupMemberCount;
    }

    @Override
    public MemberResultPage getMemberResultPage(final FullTextMemberSearchParameters params) {
        FullTextMemberQuery query = memberHelper.toFullTextQuery(params);
        if (!elementService.applyQueryRestrictions(query)) {
            throw new PermissionDeniedException();
        }
        @SuppressWarnings("unchecked")
        List<Member> members = (List<Member>) elementService.fullTextSearch(query);
        return memberHelper.toResultPage(members, params.getShowCustomFields(), params.getShowImages());
    }

    @Override
    public MemberVO getMemberVO(final Member member, final boolean useMemberFields, final boolean useImages) {
        List<MemberCustomField> fields = null;
        if (useMemberFields) {
            fields = memberCustomFieldService.list();
            if (!LoggedUser.isUnrestrictedClient()) {
                MemberGroup group = LoggedUser.member().getMemberGroup();
                fields = customFieldHelper.onlyVisibleFields(fields, group);
            }
        }
        return memberHelper.toVO(member, fields, useImages);
    }

    @Override
    public MyProfileVO getMyProfileVO(final Member member) {
        return memberHelper.toMyProfileVO(member);
    }

    @Override
    public QuickAccessVO getQuickAccess() {
        final Member member = (Member) LoggedUser.accountOwner();
        final QuickAccessVO quickAccess = new QuickAccessVO();
        quickAccess.setUpdateProfile(true); // Update profile is always visible
        quickAccess.setSearchMembers(CollectionUtils.isNotEmpty(permissionService.getVisibleMemberGroups()));
        quickAccess.setAccountInformation(permissionService.permission(member).member()
                .operator(OperatorPermission.ACCOUNT_ACCOUNT_INFORMATION).hasPermission()
                && CollectionUtils.isNotEmpty(accountService.getAccounts(member)));
        if (LoggedUser.isOperator()) {
            quickAccess.setMemberPayment(permissionService.permission()
                    .operator(OperatorPermission.PAYMENTS_PAYMENT_TO_MEMBER).hasPermission()
                    && permissionService.permission().operator(OperatorPermission.ACCOUNT_ACCOUNT_INFORMATION)
                            .hasPermission());
        } else if (LoggedUser.isMember()) {
            quickAccess.setMemberPayment(permissionService.permission()
                    .member(MemberPermission.PAYMENTS_PAYMENT_TO_MEMBER).hasPermission());
        }
        quickAccess.setPublishAd(permissionService.permission().member(MemberPermission.ADS_PUBLISH)
                .operator(OperatorPermission.ADS_PUBLISH).hasPermission());
        quickAccess.setSearchAds(permissionService.permission().member(MemberPermission.ADS_VIEW)
                .operator(MemberPermission.ADS_VIEW).hasPermission());
        quickAccess.setViewMessages(permissionService.permission().member(MemberPermission.MESSAGES_VIEW)
                .operator(OperatorPermission.MESSAGES_VIEW).hasPermission());
        quickAccess.setViewContacts(
                permissionService.permission().member().operator(OperatorPermission.CONTACTS_VIEW).hasPermission());
        return quickAccess;
    }

    @Override
    public ISmsContext getSmsContext(final Member member) throws SmsContextInitializationException {
        MemberGroup memberGroup = member.getMemberGroup();
        String className = memberGroup.getMemberSettings().getSmsContextClassName();
        if (StringUtils.isEmpty(className)) {
            return MemberGroupSmsContextImpl.getInstance();
        } else {
            try {
                return customObjectHandler.get(className);
            } catch (Exception e) {
                throw new SmsContextInitializationException(memberGroup, className, e.getMessage());
            }
        }
    }

    @Override
    public MemberSmsStatus getSmsStatus(final Member member, final boolean update) {
        boolean isNewTransaction = !update;
        if (isNewTransaction) {
            // Run in a new transaction to support retry if there is a locking exception
            MemberSmsStatus status = transactionHelper.runInNewTransaction(new Transactional<MemberSmsStatus>() {
                @Override
                public MemberSmsStatus afterCommit(final MemberSmsStatus result) {
                    // Ensure the status is attached to the current transaction
                    return fetchService.fetch(result, RelationshipHelper
                            .nested(MemberSmsStatus.Relationships.MEMBER, Element.Relationships.GROUP));
                }

                @Override
                public MemberSmsStatus doInTransaction(final TransactionStatus status) {
                    if (!update) {
                        status.setRollbackOnly();
                    }
                    return performGetSmsStatus(member);
                }
            });

            // because the Tx is rolled back we set the member directly
            status.setMember(member);
            return status;
        } else {
            return performGetSmsStatus(member);
        }
    }

    @Override
    public MemberStatusVO getStatus() {
        final MemberStatusVO status = new MemberStatusVO();

        final Member member = (Member) LoggedUser.accountOwner();
        MemberGroup group = member.getMemberGroup();
        final User user = LoggedUser.user();
        final Calendar lastLogin = user.getLastLogin();
        final boolean isOperator = LoggedUser.isOperator();

        // Count the unread messages
        if (permissionService
                .hasPermission(isOperator ? OperatorPermission.MESSAGES_VIEW : MemberPermission.MESSAGES_VIEW)) {
            final MessageQuery messages = new MessageQuery();
            messages.setGetter(member);
            messages.setMessageBox(MessageBox.INBOX);
            messages.setRead(false);
            messages.setPageForCount();
            status.setUnreadMessages(PageHelper.getTotalCount(messageService.search(messages)));
        }

        // Count the new payments since the last login
        group = fetchService.fetch(group, MemberGroup.Relationships.ACCOUNT_SETTINGS);
        final Collection<MemberAccountType> accountTypes = group.getAccountTypes();
        if (CollectionUtils.isNotEmpty(accountTypes) && !(isOperator
                && !permissionService.hasPermission(OperatorPermission.ACCOUNT_ACCOUNT_INFORMATION))) {
            final TransferQuery transfers = new TransferQuery();
            transfers.setRootOnly(true);
            transfers.setToAccountOwner(member);
            transfers.setLoanTransfer(false);
            if (lastLogin != null) {
                transfers.setPeriod(Period.begginingAt(lastLogin).useTime());
            }
            transfers.setPageForCount();
            status.setNewPayments(PageHelper.getTotalCount(paymentService.search(transfers)));
        }

        // Count the new references since the last login
        if (permissionService.hasPermission(
                isOperator ? OperatorPermission.REFERENCES_VIEW : MemberPermission.REFERENCES_VIEW)) {
            final ReferenceQuery references = new ReferenceQuery();
            references.setNature(Reference.Nature.GENERAL);
            references.setTo(member);
            if (lastLogin != null) {
                references.setPeriod(Period.begginingAt(lastLogin).useTime());
            }
            references.setPageForCount();
            status.setNewReferences(PageHelper.getTotalCount(referenceService.search(references)));
        }

        // Count the open invoices
        if (permissionService
                .hasPermission(isOperator ? OperatorPermission.INVOICES_VIEW : MemberPermission.INVOICES_VIEW)) {
            final InvoiceQuery invoices = new InvoiceQuery();
            invoices.setOwner(member);
            invoices.setDirection(InvoiceQuery.Direction.INCOMING);
            invoices.setStatus(Invoice.Status.OPEN);
            invoices.setPageForCount();
            status.setOpenInvoices(PageHelper.getTotalCount(invoiceService.search(invoices)));
        }

        // Count the open loans
        if (permissionService
                .hasPermission(isOperator ? OperatorPermission.LOANS_VIEW : MemberPermission.LOANS_VIEW)) {
            final LoanQuery loans = new LoanQuery();
            loans.setMember(member);
            loans.setStatus(Loan.Status.OPEN);
            loans.setPageForCount();
            status.setOpenLoans(PageHelper.getTotalCount(loanService.search(loans)));
        }

        // Count the payments awaiting feedback
        if (!(isOperator && !permissionService
                .hasPermission(OperatorPermission.REFERENCES_MANAGE_MEMBER_TRANSACTION_FEEDBACKS))) {
            final PaymentsAwaitingFeedbackQuery awaitingFeedback = new PaymentsAwaitingFeedbackQuery();
            awaitingFeedback.setPageForCount();
            awaitingFeedback.setExpired(false);
            awaitingFeedback.setMember(member);
            status.setPaymentsAwaitingFeedback(
                    PageHelper.getTotalCount(referenceService.searchPaymentsAwaitingFeedback(awaitingFeedback)));
        }

        // Count the payment awaiting authorization
        if (permissionService.hasPermission(
                isOperator ? OperatorPermission.PAYMENTS_AUTHORIZE : MemberPermission.PAYMENTS_AUTHORIZE)) {
            final TransfersAwaitingAuthorizationQuery awaitingAuthorization = new TransfersAwaitingAuthorizationQuery();
            awaitingAuthorization.setPageForCount();
            status.setPaymentsToAuthorize(PageHelper.getTotalCount(
                    transferAuthorizationService.searchTransfersAwaitingAuthorization(awaitingAuthorization)));
        }

        // Check if there are pending commission contracts
        if (member.getBroker() != null && !isOperator) {
            BrokerCommissionContractQuery contractsQuery = new BrokerCommissionContractQuery();
            contractsQuery.setPageForCount();
            contractsQuery.setMember(member);
            contractsQuery.setStatus(BrokerCommissionContract.Status.PENDING);
            status.setHasPendingCommissionContracts(
                    PageHelper.hasResults(commissionService.searchBrokerCommissionContracts(contractsQuery)));
        }

        return status;
    }

    @Override
    public boolean hasValueForField(final Member member, final MemberCustomField field) {
        return elementDao.hasValueForField(member, field);
    }

    @Override
    public IteratorList<Member> iterateByGroup(final boolean ordered, final MemberGroup... groups) {
        return new IteratorListImpl<Member>(elementDao.iterateMembers(ordered, groups));
    }

    @Override
    public IteratorList<Member> iterateByGroup(final MemberGroup... groups) {
        return iterateByGroup(false, groups);
    }

    @Override
    public Member loadByIdOrPrincipal(final Long id, final String principalType, final String principal) {
        Member member;
        if (id != null) {
            // By id
            try {
                member = (Member) elementService.load(id);
            } catch (ClassCastException e) {
                throw new EntityNotFoundException(Member.class);
            }
        } else if (StringUtils.isNotEmpty(principal)) {
            // By principal
            member = memberHelper.loadByPrincipal(principalType, principal);
        } else {
            // No data
            return null;
        }
        Member restrictedMember = WebServiceContext.getMember();
        if (restrictedMember != null && !permissionService.relatesTo(member)) {
            throw new EntityNotFoundException(Member.class);
        }
        return member;
    }

    public void setAccessServiceLocal(final AccessServiceLocal accessService) {
        this.accessService = accessService;
    }

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

    public void setAdServiceLocal(final AdServiceLocal adService) {
        this.adService = adService;
    }

    public void setBrokeringServiceLocal(final BrokeringServiceLocal brokeringService) {
        this.brokeringService = brokeringService;
    }

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

    public void setCommissionServiceLocal(final CommissionServiceLocal commissionService) {
        this.commissionService = commissionService;
    }

    public void setCustomFieldHelper(final CustomFieldHelper customFieldHelper) {
        this.customFieldHelper = customFieldHelper;
    }

    public void setCustomObjectHandler(final CustomObjectHandler customObjectHandler) {
        this.customObjectHandler = customObjectHandler;
    }

    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 setGroupServiceLocal(final GroupServiceLocal groupService) {
        this.groupService = groupService;
    }

    public void setInvoiceServiceLocal(final InvoiceServiceLocal invoiceService) {
        this.invoiceService = invoiceService;
    }

    public void setLoanServiceLocal(final LoanServiceLocal loanService) {
        this.loanService = loanService;
    }

    public void setLockHandlerFactory(final LockHandlerFactory lockHandlerFactory) {
        this.lockHandlerFactory = lockHandlerFactory;
    }

    public void setMemberCustomFieldService(final MemberCustomFieldService memberCustomFieldService) {
        this.memberCustomFieldService = memberCustomFieldService;
    }

    public void setMemberHelper(final MemberHelper memberHelper) {
        this.memberHelper = memberHelper;
    }

    public void setMemberSmsStatusDao(final MemberSmsStatusDAO memberSmsStatusDao) {
        this.memberSmsStatusDao = memberSmsStatusDao;
    }

    public void setMessageServiceLocal(final MessageServiceLocal messageService) {
        this.messageService = messageService;
    }

    public void setPaymentServiceLocal(final PaymentServiceLocal paymentService) {
        this.paymentService = paymentService;
    }

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

    public void setPreferenceServiceLocal(final PreferenceServiceLocal preferenceService) {
        this.preferenceService = preferenceService;
    }

    public void setRateServiceLocal(final RateServiceLocal rateService) {
        this.rateService = rateService;
    }

    public void setReferenceServiceLocal(final ReferenceServiceLocal referenceService) {
        this.referenceService = referenceService;
    }

    public void setTransactionHelper(final TransactionHelper transactionHelper) {
        this.transactionHelper = transactionHelper;
    }

    public void setTransferAuthorizationServiceLocal(
            final TransferAuthorizationServiceLocal transferAuthorizationService) {
        this.transferAuthorizationService = transferAuthorizationService;
    }

    public void setTransferTypeServiceLocal(final TransferTypeServiceLocal transferTypeService) {
        this.transferTypeService = transferTypeService;
    }

    @Override
    public MemberSmsStatus updateSmsStatus(final MemberSmsStatus memberSmsStatus) {
        return memberSmsStatusDao.update(memberSmsStatus);
    }

    private boolean canViewAccountInformation(final Element element, MemberAccount memberAccount) {
        memberAccount = fetchService.fetch(memberAccount, Account.Relationships.TYPE,
                RelationshipHelper.nested(MemberAccount.Relationships.MEMBER, Member.Relationships.BROKER));
        if (element instanceof Administrator) {
            return true;
        } else if (element instanceof Operator) {
            final Operator operator = fetchService.fetch((Operator) element,
                    RelationshipHelper.nested(Operator.Relationships.MEMBER, Element.Relationships.GROUP),
                    RelationshipHelper.nested(Element.Relationships.GROUP,
                            OperatorGroup.Relationships.CAN_VIEW_INFORMATION_OF));
            if (memberAccount.getMember().equals(operator.getMember())) {
                if (operator.getOperatorGroup().getCanViewInformationOf().contains(memberAccount.getType())) {
                    return true;
                }
            } else {
                final MemberGroup operatorsMemberGroup = fetchService.fetch(operator.getMember().getMemberGroup(),
                        MemberGroup.Relationships.CAN_VIEW_INFORMATION_OF);
                if (operatorsMemberGroup.getCanViewInformationOf().contains(memberAccount.getType())) {
                    return true;
                }
            }
        } else if (element instanceof Member) {
            final Member member = fetchService.fetch((Member) element,
                    RelationshipHelper.nested(Element.Relationships.GROUP,
                            MemberGroup.Relationships.CAN_VIEW_INFORMATION_OF),
                    RelationshipHelper.nested(Element.Relationships.GROUP,
                            BrokerGroup.Relationships.BROKER_CAN_VIEW_INFORMATION_OF));
            if (member.getGroup() instanceof BrokerGroup) {
                if (member.equals(memberAccount.getMember().getBroker())) {
                    final BrokerGroup brokerGroup = (BrokerGroup) member.getMemberGroup();
                    if (brokerGroup.getBrokerCanViewInformationOf().contains(memberAccount.getType())) {
                        return true;
                    } else {
                        return false;
                    }
                }
            }
            final MemberGroup memberGroup = member.getMemberGroup();
            if (memberGroup.getCanViewInformationOf().contains(memberAccount.getType())) {
                return true;
            }
        }
        return false;
    }

    @SuppressWarnings("unchecked")
    private ActivitiesVO doGetActivities(final Member member) {
        final ActivitiesVO vo = new ActivitiesVO();

        // Check if account information will be retrieved
        boolean showAccountInformation;
        boolean showAdsInformation = adService.visibleGroupsForAds().contains(member.getGroup());
        boolean showNonActiveAdsInformation = false;

        final Element loggedElement = LoggedUser.element();
        if (permissionService.manages(member)) {
            // A managed member
            showAccountInformation = permissionService.permission(member)
                    .admin(AdminMemberPermission.REPORTS_SHOW_ACCOUNT_INFORMATION)
                    .broker(BrokerPermission.ACCOUNTS_INFORMATION).member()
                    .operator(OperatorPermission.ACCOUNT_ACCOUNT_INFORMATION).hasPermission();
            showNonActiveAdsInformation = showAdsInformation;
        } else {
            // Another related member
            showAccountInformation = permissionService.permission()
                    .member(MemberPermission.REPORTS_SHOW_ACCOUNT_INFORMATION)
                    .operator(MemberPermission.REPORTS_SHOW_ACCOUNT_INFORMATION).hasPermission();
        }

        boolean showReferencesInformation = permissionService.permission()
                .admin(AdminMemberPermission.REFERENCES_VIEW).member(MemberPermission.REFERENCES_VIEW)
                .operator(OperatorPermission.REFERENCES_VIEW).hasPermission();

        boolean showInvoicesInformation = showAccountInformation && permissionService.permission()
                .admin(AdminMemberPermission.INVOICES_VIEW).member(MemberPermission.INVOICES_VIEW)
                .operator(OperatorPermission.INVOICES_VIEW).hasPermission();

        vo.setShowAccountInformation(showAccountInformation);
        vo.setShowInvoicesInformation(showInvoicesInformation);
        vo.setShowReferencesInformation(showReferencesInformation);
        vo.setShowAdsInformation(showAdsInformation);
        vo.setShowNonActiveAdsInformation(showNonActiveAdsInformation);

        // Since active
        vo.setSinceActive(member.getActivationDate());

        // Number of brokered members
        boolean isBroker = member.getGroup().getNature() == Nature.BROKER;
        if (isBroker) {
            final BrokeringQuery query = new BrokeringQuery();
            query.setBroker(member);
            query.setStatus(BrokeringQuery.Status.ACTIVE);
            query.setPageForCount();
            vo.setNumberBrokeredMembers(PageHelper.getTotalCount(brokeringService.search(query)));
        }

        // References
        if (showReferencesInformation) {
            vo.setReceivedReferencesByLevel(
                    referenceService.countReferencesByLevel(Reference.Nature.GENERAL, member, true));
            vo.setGivenReferencesByLevel(
                    referenceService.countReferencesByLevel(Reference.Nature.GENERAL, member, false));
        }

        // Ads
        if (vo.isShowAdsInformation()) {
            vo.setAdsByStatus(adService.getNumberOfAds(null, member));
        }

        final List<MemberAccount> accounts = (List<MemberAccount>) accountService.getAccounts(member);

        // Get invoice information
        if (showInvoicesInformation) {
            // Incoming invoices
            final InvoiceSummaryDTO incomingInvoicesDTO = new InvoiceSummaryDTO();
            incomingInvoicesDTO.setOwner(member);
            incomingInvoicesDTO.setDirection(InvoiceQuery.Direction.INCOMING);
            incomingInvoicesDTO.setStatus(Invoice.Status.OPEN);
            vo.setIncomingInvoices(invoiceService.getSummary(incomingInvoicesDTO));

            // Outgoing invoices
            final InvoiceSummaryDTO summaryDTO = new InvoiceSummaryDTO();
            summaryDTO.setOwner(member);
            summaryDTO.setDirection(InvoiceQuery.Direction.OUTGOING);
            summaryDTO.setStatus(Invoice.Status.OPEN);
            vo.setOutgoingInvoices(invoiceService.getSummary(summaryDTO));
        }

        // 30 days ago
        final Calendar days30 = DateHelper.truncate(Calendar.getInstance());
        days30.add(Calendar.DATE, -30);

        // Account activities; as rate info is NOT subject to permissions, always do the loop
        for (final MemberAccount account : accounts) {
            boolean hasRateInfo = false;

            final GetTransactionsDTO allTime = new GetTransactionsDTO(account);
            // final GetTransactionsDTO last30Days = new GetTransactionsDTO(account, Period.begginingAt(days30));

            // Build an account activities VO
            final AccountActivitiesVO activities = new AccountActivitiesVO();

            // Get the account status
            AccountStatus accountStatus = accountService.getRatedStatus(account, null);
            activities.setAccountStatus(accountStatus);

            // as AccountStatus contains rate info, no need to get it separately
            hasRateInfo = rateService.isAnyRateEnabled(account, null);
            activities.setHasRateInfo(hasRateInfo);

            // get the conversion result
            if (hasRateInfo) {
                // get the relevant transfer type for conversions
                final Collection<TransferType> currencyConversionTTs = transferTypeService
                        .getConversionTTs(account.getType().getCurrency());
                final Collection<TransferType> accountConversionTTs = transferTypeService
                        .getConversionTTs(account.getType());
                TransferType conversionTT = null;
                // there must be only 1 TT available on the account. if more than one, we don't know which to choose so show nothing.
                if (accountConversionTTs.size() == 1) {
                    final Object[] ttArray = accountConversionTTs.toArray();
                    conversionTT = (TransferType) ttArray[0];
                } else if (accountConversionTTs.size() == 0 && currencyConversionTTs.size() == 1) {
                    // OR in case there is none on the account, we will take the only one available on the currency.
                    final Object[] ttArray = currencyConversionTTs.toArray();
                    conversionTT = (TransferType) ttArray[0];
                }
                // if no balance or no TT, there's nothing to convert.
                final BigDecimal balance = accountStatus.getBalance();
                if (balance.compareTo(BigDecimal.ZERO) > 0 && conversionTT != null) {
                    final ConversionSimulationDTO dto = new ConversionSimulationDTO();
                    dto.setTransferType(conversionTT);
                    dto.setAccount(account);
                    dto.setAmount(balance);
                    dto.setUseActualRates(true);
                    dto.setDate(Calendar.getInstance());
                    final TransactionFeePreviewForRatesDTO result = paymentService.simulateConversion(dto);
                    activities.setTotalFeePercentage(result.getRatesAsFeePercentage());
                }
            }

            // rest of the activities info is subject to permissions
            if (showAccountInformation) {
                // Check if user has permission to view information of that account
                if (account.getMember().equals(loggedElement)
                        || canViewAccountInformation(loggedElement, account)) {

                    activities.setShowAccountInfo(true);

                    GetTransactionsDTO txAllTime = new GetTransactionsDTO(account);
                    txAllTime.setRootOnly(true);
                    GetTransactionsDTO tx30Days = new GetTransactionsDTO(account, Period.begginingAt(days30));
                    tx30Days.setRootOnly(true);

                    activities.setCreditsAllTime(accountService.getCredits(txAllTime));
                    activities.setDebitsAllTime(accountService.getDebits(txAllTime));
                    activities.setCreditsLast30Days(accountService.getCredits(tx30Days));
                    activities.setDebitsLast30Days(accountService.getDebits(tx30Days));

                    // Get the broker commission
                    if (isBroker) {
                        activities.setBrokerCommission(accountService.getBrokerCommissions(allTime));
                    }

                    // Calculate the total of remaining loans
                    final LoanQuery loanQuery = new LoanQuery();
                    loanQuery.setMember(member);
                    loanQuery.setStatus(Loan.Status.OPEN);
                    loanQuery.setAccountType(account.getType());
                    int remainingLoans = 0;
                    BigDecimal remainingLoanAmount = BigDecimal.ZERO;
                    final List<Loan> loans = loanService.search(loanQuery);
                    for (final Loan loan : loans) {
                        remainingLoans++;
                        remainingLoanAmount = remainingLoanAmount.add(loan.getRemainingAmount());
                    }
                    activities.setRemainingLoans(new TransactionSummaryVO(remainingLoans, remainingLoanAmount));

                }
            }
            // Store this one, but only if there is info
            if (activities.isShowAccountInfo() || activities.isHasRateInfo()) {
                vo.addAccountActivities(account.getType().getName(), activities);
            }
        }
        return vo;
    }

    /**
     * 
     * @param member
     * @return true if the member has set at least one notification by sms
     */
    private boolean hasNotificationsBySms(final Member member) {
        Collection<NotificationPreference> preferences = preferenceService.load(member);
        if (CollectionUtils.isEmpty(preferences)) {
            return false;
        }

        for (NotificationPreference preference : preferences) {
            if (preference.isSms()) {
                return true;
            }
        }
        return false;
    }

    private MemberSmsStatus performGetSmsStatus(Member member) {
        // First, acquire a pessimistic lock
        LockHandler lockHandler = lockHandlerFactory.getLockHandler();
        lockHandler.lockSmsStatus(member);

        // Try to load the sms status. If none found, a new one will be returned
        member = fetchService.fetch(member, Element.Relationships.GROUP);
        final Calendar today = Calendar.getInstance();
        final Period currentMonth = TimePeriod.ONE_MONTH.currentPeriod(today);
        MemberSmsStatus status;
        try {
            // Try loading the member status
            status = memberSmsStatusDao.load(member);

            // If got to this line, the status exists
            boolean changed = false;
            if (today.after(status.getFreeSmsExpiration())) {
                // The free sms period has expired. Reset.
                status.setFreeSmsSent(0);
                status.setFreeSmsExpiration(currentMonth.getEnd());
                changed = true;
            }
            final Calendar paidSmsExpiration = status.getPaidSmsExpiration();
            if (paidSmsExpiration != null && today.after(paidSmsExpiration)) {
                // The paid sms messages have expired. Reset.
                status.setPaidSmsLeft(0);
                status.setPaidSmsExpiration(null);
                changed = true;
            }
            if (changed) {
                // Update the record if it has changed
                status = memberSmsStatusDao.update(status);
            }
        } catch (final EntityNotFoundException e) {
            // The status does not exist. Create a new one.
            MemberGroup group = member.getMemberGroup();
            status = new MemberSmsStatus();
            status.setMember(member);
            status.setFreeSmsExpiration(currentMonth.getEnd());
            status.setAllowChargingSms(group.isDefaultAllowChargingSms());
            status.setAcceptFreeMailing(group.isDefaultAcceptFreeMailing());
            status.setAcceptPaidMailing(group.isDefaultAcceptPaidMailing());

            ensureAllowChargingSms(status, hasNotificationsBySms(member));

            status = memberSmsStatusDao.insert(status);
        } finally {
            lockHandler.release();
        }
        return status;

    }

}