nl.strohalm.cyclos.controls.payments.conversionsimulation.SimulateConversionAction.java Source code

Java tutorial

Introduction

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

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import javax.servlet.http.HttpServletRequest;

import nl.strohalm.cyclos.annotations.Inject;
import nl.strohalm.cyclos.controls.ActionContext;
import nl.strohalm.cyclos.controls.BaseFormAction;
import nl.strohalm.cyclos.controls.reports.statistics.graphs.StatisticalDataProducer;
import nl.strohalm.cyclos.entities.accounts.Account;
import nl.strohalm.cyclos.entities.accounts.AccountType;
import nl.strohalm.cyclos.entities.accounts.MemberAccount;
import nl.strohalm.cyclos.entities.accounts.fees.transaction.TransactionFee;
import nl.strohalm.cyclos.entities.accounts.transactions.TransferType;
import nl.strohalm.cyclos.entities.groups.BrokerGroup;
import nl.strohalm.cyclos.entities.groups.Group;
import nl.strohalm.cyclos.entities.members.Member;
import nl.strohalm.cyclos.entities.settings.LocalSettings;
import nl.strohalm.cyclos.entities.settings.events.LocalSettingsChangeListener;
import nl.strohalm.cyclos.entities.settings.events.LocalSettingsEvent;
import nl.strohalm.cyclos.services.accounts.AccountService;
import nl.strohalm.cyclos.services.accounts.rates.ConversionSimulationDTO;
import nl.strohalm.cyclos.services.stats.StatisticalResultDTO;
import nl.strohalm.cyclos.services.transactions.PaymentService;
import nl.strohalm.cyclos.services.transfertypes.TransactionFeePreviewForRatesDTO;
import nl.strohalm.cyclos.services.transfertypes.TransferTypeService;
import nl.strohalm.cyclos.utils.DateHelper;
import nl.strohalm.cyclos.utils.RelationshipHelper;
import nl.strohalm.cyclos.utils.binding.BeanBinder;
import nl.strohalm.cyclos.utils.binding.DataBinder;
import nl.strohalm.cyclos.utils.binding.PropertyBinder;
import nl.strohalm.cyclos.utils.conversion.NumberConverter;
import nl.strohalm.cyclos.utils.validation.ValidationException;

import org.apache.struts.action.ActionForward;
import org.jfree.chart.plot.Marker;

/**
 * Action used to simulate a conversion
 * @author luis
 * @author Rinke
 */
public class SimulateConversionAction extends BaseFormAction implements LocalSettingsChangeListener {

    private AccountService accountService;
    private TransferTypeService transferTypeService;
    private PaymentService paymentService;

    private DataBinder<ConversionSimulationDTO> dataBinder;
    private ReadWriteLock lock = new ReentrantReadWriteLock(true);

    @Override
    public void onLocalSettingsUpdate(final LocalSettingsEvent event) {
        try {
            lock.writeLock().lock();
            dataBinder = null;
        } finally {
            lock.writeLock().unlock();
        }
    }

    @Inject
    public void setAccountService(final AccountService accountService) {
        this.accountService = accountService;
    }

    @Inject
    public void setPaymentService(final PaymentService paymentService) {
        this.paymentService = paymentService;
    }

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

    @Override
    protected ActionForward handleSubmit(final ActionContext context) throws Exception {
        final HttpServletRequest request = context.getRequest();
        final SimulateConversionForm form = context.getForm();
        if (form.isReloadData()) {
            return handleDisplay(context);
        }
        reloadForm(context);
        final ConversionSimulationDTO dto = getDataBinder().readFromString(form.getSimulation());
        final Member member = resolveMember(context);
        final boolean myAccount = member.equals(context.getAccountOwner());

        showResults(request, dto, myAccount);
        showGraph(context, dto);
        return context.getInputForward();
    }

    @Override
    protected void prepareForm(final ActionContext context) throws Exception {
        final HttpServletRequest request = context.getRequest();
        final SimulateConversionForm form = context.getForm();

        final boolean firstLoad = (!form.isReloadData()) && (request.getParameter("advanced") == null);
        if (firstLoad) {
            if (context.isAdmin()) {
                form.setAdvanced(true);
            } else {
                form.setAdvanced(false);
            }
        }

        reloadForm(context);
    }

    @Override
    protected void validateForm(final ActionContext context) {
        final SimulateConversionForm form = context.getForm();
        final ConversionSimulationDTO dto = getDataBinder().readFromString(form.getSimulation());
        paymentService.validate(dto);
    }

    /**
     * replaces the marker keys with their message labels from the resource bundle. Also sets the subTitle of the graph.
     */
    private void attachLabels(final StatisticalResultDTO rawDataObject, final ConversionSimulationDTO dto,
            final ActionContext context) {
        final Marker[] markers = rawDataObject.getDomainMarkers();
        if (markers != null) {
            for (final Marker marker : markers) {
                final String key = marker.getLabel();
                if (key != null) {
                    final String title = context.message(key);
                    marker.setLabel(title);
                }
            }
        }
        final LocalSettings localSettings = settingsService.getLocalSettings();
        TransferType transferType = dto.getTransferType();
        transferType = transferTypeService.load(transferType.getId(), TransferType.Relationships.TRANSACTION_FEES,
                RelationshipHelper.nested(TransferType.Relationships.FROM, AccountType.Relationships.CURRENCY));
        final String unitsPattern = transferType.getFrom().getCurrency().getPattern();
        final NumberConverter<BigDecimal> numberConverter = localSettings.getUnitsConverter(unitsPattern);
        final String numberString = numberConverter.toString(dto.getAmount());
        final String subTitle = context.message("conversionSimulation.result.graph.subtitle", numberString);
        rawDataObject.setSubTitle(subTitle);
    }

    /**
     * filters out all the transferTypes of allTransferTypes which do not have account as their from account.
     * @return only transferTypes remain which go FROM account
     */
    private List<TransferType> filterTransferTypesByAccount(final MemberAccount account,
            final Collection<TransferType> allTransferTypes) {
        final List<TransferType> result = new ArrayList<TransferType>(allTransferTypes.size());
        for (final TransferType currentTT : allTransferTypes) {
            if (account.getType().getFromTransferTypes().contains(currentTT)) {
                result.add(currentTT);
            }
        }
        return result;
    }

    private DataBinder<ConversionSimulationDTO> getDataBinder() {
        try {
            lock.readLock().lock();
            if (dataBinder == null) {
                final LocalSettings localSettings = settingsService.getLocalSettings();
                final BeanBinder<ConversionSimulationDTO> binder = BeanBinder
                        .instance(ConversionSimulationDTO.class);
                binder.registerBinder("account", PropertyBinder.instance(MemberAccount.class, "account"));
                binder.registerBinder("transferType", PropertyBinder.instance(TransferType.class, "transferType"));
                binder.registerBinder("amount",
                        PropertyBinder.instance(BigDecimal.class, "amount", localSettings.getNumberConverter()));
                binder.registerBinder("useActualRates", PropertyBinder.instance(Boolean.TYPE, "useActualRates"));
                binder.registerBinder("arate",
                        PropertyBinder.instance(BigDecimal.class, "arate", localSettings.getNumberConverter()));
                binder.registerBinder("drate",
                        PropertyBinder.instance(BigDecimal.class, "drate", localSettings.getNumberConverter()));
                binder.registerBinder("date",
                        PropertyBinder.instance(Calendar.class, "date", localSettings.getRawDateConverter()));
                binder.registerBinder("graph", PropertyBinder.instance(Boolean.TYPE, "graph"));
                dataBinder = binder;
            }
            return dataBinder;
        } finally {
            lock.readLock().unlock();
        }
    }

    private void reloadForm(final ActionContext context) {
        final HttpServletRequest request = context.getRequest();
        final SimulateConversionForm form = context.getForm();

        final Member member = resolveMember(context);
        final boolean myAccount = member.equals(context.getAccountOwner());
        final Collection<TransferType> allowedTTs = resolveAllowedTTs(context, myAccount);

        ConversionSimulationDTO dto = getDataBinder().readFromString(form.getSimulation());
        MemberAccount account = dto.getAccount();
        final List<Account> allowedAccounts = new ArrayList<Account>(
                accountService.getAccountsFromTTs(member, allowedTTs, TransferType.Direction.FROM));

        // Check if we have to reload data, and get default account
        boolean changed = false;
        if (account == null) {
            account = (MemberAccount) accountService.getDefaultAccountFromList(member, allowedAccounts);
            changed = true;
        }

        // limit transferTypes to only the relevant for this account
        account = accountService.load(account.getId());
        final List<TransferType> transferTypes = filterTransferTypesByAccount(account, allowedTTs);

        // get the default rates
        final ConversionSimulationDTO defaultDto = paymentService.getDefaultConversionDTO(account, transferTypes);
        if (changed || form.isReloadData()) {
            // reloadData is only true if the user chose another account.
            dto = defaultDto;
        }
        getDataBinder().writeAsString(form.getSimulation(), dto);

        request.setAttribute("member", member);
        request.setAttribute("account", account);
        request.setAttribute("myAccount", myAccount);

        request.setAttribute("accounts", allowedAccounts);
        if (allowedAccounts.size() == 1) {
            request.setAttribute("singleAccount", allowedAccounts.get(0));
        }
        request.setAttribute("arateDefault", defaultDto.getArate());
        request.setAttribute("drateDefault", defaultDto.getDrate());

        request.setAttribute("tts", transferTypes);
        if (transferTypes.size() == 1) {
            request.setAttribute("singleTT", transferTypes.get(0));
        }
    }

    private Collection<TransferType> resolveAllowedTTs(final ActionContext context, final boolean myAccount) {
        Group loggedUserGroup = context.getGroup();
        final Collection<TransferType> allowedTTs;
        if (!myAccount && loggedUserGroup instanceof BrokerGroup) {
            loggedUserGroup = groupService.load(loggedUserGroup.getId(),
                    BrokerGroup.Relationships.BROKER_CONVERSION_SIMULATION_TTS);
            allowedTTs = ((BrokerGroup) loggedUserGroup).getBrokerConversionSimulationTTs();
        } else {
            loggedUserGroup = groupService.load(loggedUserGroup.getId(),
                    Group.Relationships.CONVERSION_SIMULATION_TTS);
            allowedTTs = loggedUserGroup.getConversionSimulationTTs();
        }
        return allowedTTs;
    }

    private Member resolveMember(final ActionContext context) {
        final SimulateConversionForm form = context.getForm();
        Member member = null;
        final long memberId = form.getMemberId();
        if (memberId > 0) {
            try {
                member = elementService.load(memberId);
            } catch (final Exception e) {
                member = null;
            }
        }
        if (member == null && !context.isAdmin()) {
            member = (Member) context.getAccountOwner();
        }
        if (member == null) {
            throw new ValidationException();
        }
        return member;
    }

    private void showGraph(final ActionContext context, final ConversionSimulationDTO dto) {
        if (dto.isGraph()) {
            final HttpServletRequest request = context.getRequest();
            final List<StatisticalDataProducer> dataList = new ArrayList<StatisticalDataProducer>();
            final StatisticalResultDTO rawDataObject = paymentService.getSimulateConversionGraph(dto);
            attachLabels(rawDataObject, dto, context);
            final StatisticalDataProducer producer = new StatisticalDataProducer(rawDataObject, context);
            final LocalSettings localSettings = settingsService.getLocalSettings();
            producer.setSettings(localSettings);
            dataList.add(producer);
            request.setAttribute("dataList", dataList);
        }
    }

    private void showResults(final HttpServletRequest request, final ConversionSimulationDTO dto,
            final boolean myAccount) {
        final Calendar now = Calendar.getInstance();
        if (dto.getDate() == null) {
            dto.setDate(now);
        }
        // adapt the time so that the date entered in the form has the same time as now
        final Calendar equalizedProcessDate = DateHelper.equalizeTime(dto.getDate(), now);
        dto.setDate(equalizedProcessDate);
        // Perform the simulation
        final TransactionFeePreviewForRatesDTO result = paymentService.simulateConversion(dto);
        request.setAttribute("feelessAmount", result.getFinalAmount());
        request.setAttribute("totalAmount", result.getAmount());
        request.setAttribute("totalFees", result.getRatesAsFeePercentage());
        request.setAttribute("totalFeeAmount", result.getTotalFeeAmount());
        request.setAttribute("fees", result.getFees());
        // jsp will read wrong percentages from fees as fees objects will change when creating the graph. So we store the percentages directly
        final ArrayList<BigDecimal> feePercentages = new ArrayList<BigDecimal>(result.getFees().size());
        for (final TransactionFee fee : result.getFees().keySet()) {
            feePercentages.add(fee.getAmount().getValue());
        }
        request.setAttribute("feePercentages", feePercentages);
        // save the used rates to the form (via the accountStatus methods)
        TransferType transferType = dto.getTransferType();
        transferType = transferTypeService.load(transferType.getId(), TransferType.Relationships.TRANSACTION_FEES,
                RelationshipHelper.nested(TransferType.Relationships.FROM, AccountType.Relationships.CURRENCY));
        request.setAttribute("unitsPattern", transferType.getFrom().getCurrency().getPattern());
        request.setAttribute("usedARate", result.getARate());
        request.setAttribute("usedDRate", result.getDRate());
        if (transferType.isHavingRatedFees()) {
            request.setAttribute("usedDate", equalizedProcessDate);
        }
    }

}