/*
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;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
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.entities.accounts.AccountOwner;
import nl.strohalm.cyclos.entities.accounts.AccountType;
import nl.strohalm.cyclos.entities.accounts.Currency;
import nl.strohalm.cyclos.entities.accounts.MemberAccountType;
import nl.strohalm.cyclos.entities.accounts.transactions.TransferType;
import nl.strohalm.cyclos.entities.accounts.transactions.TransferTypeQuery;
import nl.strohalm.cyclos.entities.customization.fields.CustomFieldValue;
import nl.strohalm.cyclos.entities.customization.fields.PaymentCustomField;
import nl.strohalm.cyclos.entities.customization.fields.PaymentCustomFieldValue;
import nl.strohalm.cyclos.entities.members.Element;
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.AccountTypeService;
import nl.strohalm.cyclos.services.accounts.CurrencyService;
import nl.strohalm.cyclos.services.transactions.DoPaymentDTO;
import nl.strohalm.cyclos.services.transactions.PaymentService;
import nl.strohalm.cyclos.services.transfertypes.TransferTypeService;
import nl.strohalm.cyclos.utils.binding.BeanBinder;
import nl.strohalm.cyclos.utils.binding.BeanCollectionBinder;
import nl.strohalm.cyclos.utils.binding.DataBinder;
import nl.strohalm.cyclos.utils.binding.PropertyBinder;
import nl.strohalm.cyclos.utils.conversion.AccountOwnerConverter;
import nl.strohalm.cyclos.utils.conversion.CoercionHelper;
import nl.strohalm.cyclos.utils.conversion.HtmlConverter;
import nl.strohalm.cyclos.utils.conversion.IdConverter;
import nl.strohalm.cyclos.utils.validation.ValidationException;
import org.apache.commons.collections.CollectionUtils;
/**
* Base action for payments
* @author luis
*/
public abstract class BasePaymentAction extends BaseFormAction implements LocalSettingsChangeListener {
protected AccountTypeService accountTypeService;
protected PaymentService paymentService;
protected CurrencyService currencyService;
protected TransferTypeService transferTypeService;
private DataBinder<DoPaymentDTO> dataBinder;
public DataBinder<DoPaymentDTO> getDataBinder() {
if (dataBinder == null) {
dataBinder = initDataBinder();
}
return dataBinder;
}
@Override
public void onLocalSettingsUpdate(final LocalSettingsEvent event) {
dataBinder = null;
}
@Inject
public final void setAccountTypeService(final AccountTypeService accountTypeService) {
this.accountTypeService = accountTypeService;
}
@Inject
public final void setCurrencyService(final CurrencyService currencyService) {
this.currencyService = currencyService;
}
@Inject
public final void setPaymentService(final PaymentService paymentService) {
this.paymentService = paymentService;
}
@Inject
public final void setTransferTypeService(final TransferTypeService transferTypeService) {
this.transferTypeService = transferTypeService;
}
@Override
protected void formAction(final ActionContext context) throws Exception {
final DoPaymentDTO dto = resolvePaymentDTO(context);
context.getSession().setAttribute("payment", dto);
}
protected abstract AccountOwner getFromOwner(ActionContext context);
protected PaymentService getPaymentService() {
return paymentService;
}
protected DataBinder<DoPaymentDTO> initDataBinder() {
final LocalSettings localSettings = settingsService.getLocalSettings();
final BeanBinder<? extends CustomFieldValue> customValueBinder = BeanBinder.instance(PaymentCustomFieldValue.class);
customValueBinder.registerBinder("field", PropertyBinder.instance(PaymentCustomField.class, "field"));
customValueBinder.registerBinder("value", PropertyBinder.instance(String.class, "value", HtmlConverter.instance()));
final BeanBinder<DoPaymentDTO> binder = BeanBinder.instance(DoPaymentDTO.class);
binder.registerBinder("from", PropertyBinder.instance(AccountOwner.class, "from", AccountOwnerConverter.instance()));
binder.registerBinder("transferType", PropertyBinder.instance(TransferType.class, "type"));
binder.registerBinder("amount", PropertyBinder.instance(BigDecimal.class, "amount", localSettings.getNumberConverter()));
binder.registerBinder("currency", PropertyBinder.instance(Currency.class, "currency"));
binder.registerBinder("date", PropertyBinder.instance(Calendar.class, "date", localSettings.getRawDateConverter()));
binder.registerBinder("description", PropertyBinder.instance(String.class, "description"));
binder.registerBinder("customValues", BeanCollectionBinder.instance(customValueBinder, "customValues"));
return binder;
}
@Override
protected void prepareForm(final ActionContext context) throws Exception {
final BasePaymentForm form = context.getForm();
final HttpServletRequest request = context.getRequest();
// Check whether the payment is as a member
final Long fromId = IdConverter.instance().valueOf(form.getFrom());
final boolean asMember = fromId != null;
Member fromMember = null;
if (asMember) {
final Element element = elementService.load(fromId, Element.Relationships.GROUP, Element.Relationships.USER);
if (element instanceof Member) {
fromMember = (Member) element;
request.setAttribute("member", fromMember);
}
}
request.setAttribute("asMember", asMember);
// Get the member in action
Member member = fromMember;
if (member == null && context.isMember()) {
member = context.getElement();
}
// Resolve the possible currencies
final List<Currency> currencies = resolveCurrencies(context);
// Resolve the transfer types
final TransferTypeQuery ttQuery = resolveTransferTypeQuery(context);
if (ttQuery != null) {
Currency defaultCurrency = null;
if (member != null) {
final MemberAccountType defaultAccountType = accountTypeService.getDefault(member.getMemberGroup(), AccountType.Relationships.CURRENCY);
if (defaultAccountType != null) {
defaultCurrency = defaultAccountType.getCurrency();
}
}
// Check for transfer types for each currency, removing those currencies without transfer types
final Map<Currency, List<TransferType>> transferTypesPerCurrency = new LinkedHashMap<Currency, List<TransferType>>();
final List<TransferType> allTransferTypes = new ArrayList<TransferType>();
for (final Iterator<Currency> iterator = currencies.iterator(); iterator.hasNext();) {
final Currency currency = iterator.next();
final TransferTypeQuery currentQuery = (TransferTypeQuery) ttQuery.clone();
currentQuery.setCurrency(currency);
final List<TransferType> tts = transferTypeService.search(currentQuery);
allTransferTypes.addAll(tts);
if (tts.isEmpty()) {
iterator.remove();
} else {
transferTypesPerCurrency.put(currency, tts);
}
}
// Check which currency to preselect
Currency currency = null;
if (CollectionUtils.isNotEmpty(transferTypesPerCurrency.get(defaultCurrency))) {
// There are TTs for the default currency: preselect it
currency = defaultCurrency;
} else if (!transferTypesPerCurrency.isEmpty()) {
// Get the first currency with TTs
currency = transferTypesPerCurrency.keySet().iterator().next();
}
form.setCurrency(CoercionHelper.coerce(String.class, currency));
// Store the transfer types associated with the preselected currency
request.setAttribute("transferTypes", allTransferTypes);
}
if (CollectionUtils.isEmpty(currencies)) {
// No currency with possible transfer type!!!
throw new ValidationException("payment.error.noTransferType");
} else if (currencies.size() == 1) {
request.setAttribute("singleCurrency", currencies.iterator().next());
}
}
protected List<Currency> resolveCurrencies(final ActionContext context) {
final BasePaymentForm form = context.getForm();
final HttpServletRequest request = context.getRequest();
final List<Currency> currencies;
final AccountOwner fromOwner = getFromOwner(context);
if (fromOwner instanceof Member) {
final Member member = elementService.load(((Member) fromOwner).getId(), Element.Relationships.GROUP);
currencies = currencyService.listByMember(member);
final MemberAccountType defaultAccountType = accountTypeService.getDefault(member.getMemberGroup(), AccountType.Relationships.CURRENCY);
// Preselect the default currency
if (defaultAccountType != null) {
form.setCurrency(CoercionHelper.coerce(String.class, defaultAccountType.getCurrency()));
}
} else {
currencies = currencyService.listAll();
}
request.setAttribute("currencies", currencies);
if (currencies.isEmpty()) {
// No currencies means no possible payment!!!
throw new ValidationException("payment.error.noTransferType");
} else if (currencies.size() == 1) {
// Special case: There is a single currency. The JSP will use this object
request.setAttribute("singleCurrency", currencies.get(0));
}
return currencies;
}
/**
* Reads the payment DTO from the form
*/
protected DoPaymentDTO resolvePaymentDTO(final ActionContext context) {
return getDataBinder().readFromString(context.getForm());
}
/**
* Should be overridden to return a transfer type query. When null is returned, it is assumed that there will be no transfer types on the request.
* Otherwise, a query is executed and an error is generated if there is no returned transfer type
*/
protected abstract TransferTypeQuery resolveTransferTypeQuery(ActionContext context);
@Override
protected void validateForm(final ActionContext context) {
final DoPaymentDTO dto = resolvePaymentDTO(context);
paymentService.validate(dto);
}
}