/*
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.accounts.transactionfees;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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.entities.accounts.AccountType;
import nl.strohalm.cyclos.entities.accounts.MemberAccountType;
import nl.strohalm.cyclos.entities.accounts.fees.transaction.BrokerCommission;
import nl.strohalm.cyclos.entities.accounts.fees.transaction.BrokerCommission.WhichBroker;
import nl.strohalm.cyclos.entities.accounts.fees.transaction.SimpleTransactionFee;
import nl.strohalm.cyclos.entities.accounts.fees.transaction.SimpleTransactionFee.ARateRelation;
import nl.strohalm.cyclos.entities.accounts.fees.transaction.TransactionFee;
import nl.strohalm.cyclos.entities.accounts.fees.transaction.TransactionFee.ChargeType;
import nl.strohalm.cyclos.entities.accounts.fees.transaction.TransactionFee.Nature;
import nl.strohalm.cyclos.entities.accounts.fees.transaction.TransactionFee.Subject;
import nl.strohalm.cyclos.entities.accounts.transactions.TransferType;
import nl.strohalm.cyclos.entities.groups.Group;
import nl.strohalm.cyclos.entities.groups.GroupQuery;
import nl.strohalm.cyclos.entities.groups.MemberGroup;
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.transfertypes.TransactionFeeService;
import nl.strohalm.cyclos.services.transfertypes.TransferTypeService;
import nl.strohalm.cyclos.utils.ActionHelper;
import nl.strohalm.cyclos.utils.Amount;
import nl.strohalm.cyclos.utils.EntityHelper;
import nl.strohalm.cyclos.utils.RelationshipHelper;
import nl.strohalm.cyclos.utils.RequestHelper;
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.binding.SimpleCollectionBinder;
import nl.strohalm.cyclos.utils.conversion.CoercionHelper;
import nl.strohalm.cyclos.utils.conversion.IdConverter;
import nl.strohalm.cyclos.utils.validation.ValidationException;
import org.apache.struts.action.ActionForward;
/**
* Action used to edit a transaction fee
* @author luis
*/
public class EditTransactionFeeAction extends BaseFormAction implements LocalSettingsChangeListener {
private TransactionFeeService transactionFeeService;
private TransferTypeService transferTypeService;
private Map<Nature, DataBinder<? extends TransactionFee>> dataBinders;
private ReadWriteLock lock = new ReentrantReadWriteLock(true);
public DataBinder<? extends TransactionFee> getDataBinder(final Nature nature) {
try {
lock.readLock().lock();
if (dataBinders == null) {
final LocalSettings localSettings = settingsService.getLocalSettings();
final EnumMap<Nature, DataBinder<? extends TransactionFee>> temp = new EnumMap<Nature, DataBinder<? extends TransactionFee>>(Nature.class);
final BeanBinder<SimpleTransactionFee> simpleBinder = BeanBinder.instance(SimpleTransactionFee.class);
initBasic(simpleBinder, localSettings);
simpleBinder.registerBinder("receiver", PropertyBinder.instance(Subject.class, "receiver"));
simpleBinder.registerBinder("toFixedMember", PropertyBinder.instance(Member.class, "toFixedMember"));
simpleBinder.registerBinder("h", PropertyBinder.instance(BigDecimal.class, "h", localSettings.getHighPrecisionConverter()));
simpleBinder.registerBinder("aFIsZero", PropertyBinder.instance(BigDecimal.class, "aFIsZero", localSettings.getHighPrecisionConverter()));
simpleBinder.registerBinder("f1", PropertyBinder.instance(BigDecimal.class, "f1", localSettings.getHighPrecisionConverter()));
simpleBinder.registerBinder("fInfinite", PropertyBinder.instance(BigDecimal.class, "fInfinite", localSettings.getHighPrecisionConverter()));
simpleBinder.registerBinder("fMinimal", PropertyBinder.instance(BigDecimal.class, "fMinimal", localSettings.getHighPrecisionConverter()));
simpleBinder.registerBinder("gFIsZero", PropertyBinder.instance(BigDecimal.class, "gFIsZero", localSettings.getHighPrecisionConverter()));
temp.put(Nature.SIMPLE, simpleBinder);
final BeanBinder<BrokerCommission> brokerBinder = BeanBinder.instance(BrokerCommission.class);
initBasic(brokerBinder, localSettings);
brokerBinder.registerBinder("whichBroker", PropertyBinder.instance(WhichBroker.class, "whichBroker"));
brokerBinder.registerBinder("maxFixedValue", PropertyBinder.instance(BigDecimal.class, "maxFixedValue", localSettings.getNumberConverter()));
brokerBinder.registerBinder("maxPercentageValue", PropertyBinder.instance(BigDecimal.class, "maxPercentageValue", localSettings.getNumberConverter()));
brokerBinder.registerBinder("when", PropertyBinder.instance(BrokerCommission.When.class, "when"));
brokerBinder.registerBinder("count", PropertyBinder.instance(Integer.class, "count"));
brokerBinder.registerBinder("allBrokerGroups", PropertyBinder.instance(Boolean.TYPE, "allBrokerGroups"));
brokerBinder.registerBinder("brokerGroups", SimpleCollectionBinder.instance(MemberGroup.class, "brokerGroups"));
temp.put(Nature.BROKER, brokerBinder);
dataBinders = temp;
}
return dataBinders.get(nature);
} finally {
lock.readLock().unlock();
}
}
@Override
public void onLocalSettingsUpdate(final LocalSettingsEvent event) {
try {
lock.writeLock().lock();
dataBinders = null;
} finally {
lock.writeLock().unlock();
}
}
@Inject
public void setTransactionFeeService(final TransactionFeeService transactionFeeService) {
this.transactionFeeService = transactionFeeService;
}
@Inject
public void setTransferTypeService(final TransferTypeService transferTypeService) {
this.transferTypeService = transferTypeService;
}
@Override
protected ActionForward handleDisplay(final ActionContext context) throws Exception {
final HttpServletRequest request = context.getRequest();
final EditTransactionFeeForm form = context.getForm();
final long accountTypeId = form.getAccountTypeId();
final long transferTypeId = form.getTransferTypeId();
if (accountTypeId <= 0L || transferTypeId <= 0L) {
throw new ValidationException();
}
boolean editableGeneratedTT = true;
final TransferType transferType = transferTypeService.load(transferTypeId, RelationshipHelper.nested(TransferType.Relationships.FROM, AccountType.Relationships.CURRENCY), TransferType.Relationships.TO);
final long id = form.getTransactionFeeId();
final boolean isInsert = id <= 0L;
Nature nature;
ARateRelation arateRelation = ARateRelation.LINEAR;
if (isInsert) {
nature = CoercionHelper.coerce(TransactionFee.Nature.class, form.getNature());
if (nature == null) {
throw new ValidationException();
}
form.setTransactionFee("fromAllGroups", "true");
form.setTransactionFee("toAllGroups", "true");
form.setTransactionFee("allBrokerGroups", "true");
final Collection<WhichBroker> whichBrokers = (nature == Nature.BROKER) ? EnumSet.allOf(WhichBroker.class) : null;
final Collection<Subject> possibleSubjects = transactionFeeService.getPossibleSubjects(transferType, nature);
request.setAttribute("possibleSubjects", possibleSubjects);
request.setAttribute("whichBrokers", whichBrokers);
if (possibleSubjects != null & possibleSubjects.size() == 1) {
request.setAttribute("singleSubject", possibleSubjects.iterator().next());
}
} else {
final TransactionFee transactionFee = transactionFeeService.load(id, TransactionFee.Relationships.ORIGINAL_TRANSFER_TYPE, RelationshipHelper.nested(TransactionFee.Relationships.GENERATED_TRANSFER_TYPE, TransferType.Relationships.FROM));
final TransferType generatedType = transactionFee.getGeneratedTransferType();
nature = transactionFee.getNature();
List<TransferType> possibleGeneratedTypes = null;
if (nature == Nature.BROKER) {
editableGeneratedTT = generatedType.isFromSystem();
possibleGeneratedTypes = Collections.singletonList(generatedType);
} else {
final SimpleTransactionFee simpleFee = (SimpleTransactionFee) transactionFee;
possibleGeneratedTypes = transactionFeeService.searchGeneratedTransferTypes(transactionFee, form.isAllowAnyAccount(), generatedType.isFromSystem());
// When the possible generated types don't contain the current one, enable the "from any account" flag
if (!possibleGeneratedTypes.contains(generatedType) && !form.isAllowAnyAccount()) {
// Search again, setting the flag as true
form.setAllowAnyAccount(true);
possibleGeneratedTypes = transactionFeeService.searchGeneratedTransferTypes(transactionFee, form.isAllowAnyAccount(), false);
}
arateRelation = simpleFee.getfInfinite() == null ? ARateRelation.LINEAR : ARateRelation.ASYMPTOTICAL;
}
request.setAttribute("generatedTransferTypes", possibleGeneratedTypes);
request.setAttribute("transactionFee", transactionFee);
getDataBinder(nature).writeAsString(form.getTransactionFee(), transactionFee);
}
request.setAttribute("transferType", transferType);
request.setAttribute("isInsert", isInsert);
request.setAttribute("editableGeneratedTT", editableGeneratedTT);
final EnumSet<Subject> subjects = EnumSet.allOf(TransactionFee.Subject.class);
if (nature == Nature.BROKER) {
subjects.remove(TransactionFee.Subject.SOURCE_BROKER);
subjects.remove(TransactionFee.Subject.DESTINATION_BROKER);
}
request.setAttribute("subjects", subjects);
RequestHelper.storeEnum(request, TransactionFee.Nature.class, "natures");
RequestHelper.storeEnum(request, BrokerCommission.When.class, "whens");
RequestHelper.storeEnum(request, Amount.Type.class, "amountTypes");
final Collection<ChargeType> chargeTypes = transactionFeeService.getPossibleChargeType(transferType, nature);
request.setAttribute("allowARate", chargeTypes.contains(ChargeType.A_RATE) && chargeTypes.contains(ChargeType.MIXED_A_D_RATES));
request.setAttribute("allowDRate", chargeTypes.contains(ChargeType.D_RATE));
if (nature == TransactionFee.Nature.SIMPLE) {
// Rates are allowed only in payments from member to unlimited system accounts
if (transferType.isFromMember() && transferType.isToSystem() && !transferType.getTo().isLimited()) {
RequestHelper.storeEnum(request, ARateRelation.class, "aRateRelations");
form.setTransactionFee("aRateRelation", arateRelation.name());
}
}
request.setAttribute("chargeTypes", chargeTypes);
// If from member, search for groups related to the from account type...
if (transferType.isFromMember()) {
final GroupQuery groupQuery = new GroupQuery();
groupQuery.setNatures(Group.Nature.MEMBER, Group.Nature.BROKER);
groupQuery.setStatus(Group.Status.NORMAL);
groupQuery.setMemberAccountType((MemberAccountType) transferType.getFrom());
request.setAttribute("possibleFromGroups", groupService.search(groupQuery));
}
// ... same for to
if (transferType.isToMember()) {
final GroupQuery groupQuery = new GroupQuery();
groupQuery.setNatures(Group.Nature.MEMBER, Group.Nature.BROKER);
groupQuery.setStatus(Group.Status.NORMAL);
groupQuery.setMemberAccountType((MemberAccountType) transferType.getTo());
request.setAttribute("possibleToGroups", groupService.search(groupQuery));
}
// For broker groups, list only active
final GroupQuery groupQuery = new GroupQuery();
groupQuery.setNature(Group.Nature.BROKER);
groupQuery.setStatus(Group.Status.NORMAL);
groupQuery.setOnlyActive(true);
request.setAttribute("brokerGroups", groupService.search(groupQuery));
switch (nature) {
case SIMPLE:
return context.findForward("inputSimpleTransactionFee");
case BROKER:
return context.findForward("inputBrokerCommission");
default:
return null;
}
}
@Override
protected ActionForward handleSubmit(final ActionContext context) throws Exception {
final EditTransactionFeeForm form = context.getForm();
TransactionFee transactionFee = retrieveTransactionFee(form);
final boolean isInsert = transactionFee.getId() == null;
if (transactionFee instanceof BrokerCommission) {
final BrokerCommission brokerCommission = (BrokerCommission) transactionFee;
transactionFee = transactionFeeService.save(brokerCommission);
context.sendMessage(isInsert ? "brokerCommission.inserted" : "brokerCommission.modified");
} else {
final SimpleTransactionFee fee = (SimpleTransactionFee) transactionFee;
final ARateRelation aRateRelation = PropertyBinder.instance(ARateRelation.class, "aRateRelation").readFromString(form.getTransactionFee());
transactionFee = transactionFeeService.save(fee, aRateRelation);
context.sendMessage(isInsert ? "transactionFee.inserted" : "transactionFee.modified");
}
final Map<String, Object> params = new HashMap<String, Object>();
params.put("accountTypeId", form.getAccountTypeId());
params.put("transferTypeId", form.getTransferTypeId());
params.put("transactionFeeId", transactionFee.getId());
return ActionHelper.redirectWithParams(context.getRequest(), context.getSuccessForward(), params);
}
@Override
protected void validateForm(final ActionContext context) {
final EditTransactionFeeForm form = context.getForm();
final TransactionFee transactionFee = retrieveTransactionFee(form);
if (transactionFee instanceof BrokerCommission) {
final BrokerCommission brokerCommission = (BrokerCommission) transactionFee;
transactionFeeService.validate(brokerCommission);
} else {
final SimpleTransactionFee simpleFee = (SimpleTransactionFee) transactionFee;
final ARateRelation aRateRelation = PropertyBinder.instance(ARateRelation.class, "aRateRelation").readFromString(form.getTransactionFee());
transactionFeeService.validate(simpleFee, aRateRelation);
}
}
private void initBasic(final BeanBinder<? extends TransactionFee> binder, final LocalSettings localSettings) {
binder.registerBinder("id", PropertyBinder.instance(Long.class, "id", IdConverter.instance()));
binder.registerBinder("name", PropertyBinder.instance(String.class, "name"));
binder.registerBinder("description", PropertyBinder.instance(String.class, "description"));
binder.registerBinder("enabled", PropertyBinder.instance(Boolean.TYPE, "enabled"));
binder.registerBinder("generatedTransferType", PropertyBinder.instance(TransferType.class, "generatedTransferType"));
binder.registerBinder("chargeType", PropertyBinder.instance(ChargeType.class, "chargeType"));
binder.registerBinder("value", PropertyBinder.instance(BigDecimal.class, "value", localSettings.getHighPrecisionConverter()));
binder.registerBinder("payer", PropertyBinder.instance(Subject.class, "payer"));
binder.registerBinder("initialAmount", PropertyBinder.instance(BigDecimal.class, "initialAmount", localSettings.getNumberConverter()));
binder.registerBinder("finalAmount", PropertyBinder.instance(BigDecimal.class, "finalAmount", localSettings.getNumberConverter()));
binder.registerBinder("deductAmount", PropertyBinder.instance(Boolean.TYPE, "deductAmount"));
binder.registerBinder("fromAllGroups", PropertyBinder.instance(Boolean.TYPE, "fromAllGroups"));
binder.registerBinder("toAllGroups", PropertyBinder.instance(Boolean.TYPE, "toAllGroups"));
binder.registerBinder("fromGroups", SimpleCollectionBinder.instance(MemberGroup.class, "fromGroups"));
binder.registerBinder("toGroups", SimpleCollectionBinder.instance(MemberGroup.class, "toGroups"));
binder.registerBinder("fromFixedMember", PropertyBinder.instance(Member.class, "fromFixedMember"));
}
private TransactionFee retrieveTransactionFee(final EditTransactionFeeForm form) {
Nature nature;
try {
nature = Nature.valueOf(form.getNature());
} catch (final Exception e) {
throw new ValidationException();
}
final TransactionFee transactionFee = getDataBinder(nature).readFromString(form.getTransactionFee());
transactionFee.setOriginalTransferType(EntityHelper.reference(TransferType.class, form.getTransferTypeId()));
return transactionFee;
}
}