/*
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.transactions;
import java.util.Calendar;
import java.util.Collection;
import java.util.List;
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.access.PermissionCheck;
import nl.strohalm.cyclos.entities.Relationship;
import nl.strohalm.cyclos.entities.accounts.MemberAccount;
import nl.strohalm.cyclos.entities.accounts.loans.BaseLoanQuery;
import nl.strohalm.cyclos.entities.accounts.loans.Loan;
import nl.strohalm.cyclos.entities.accounts.loans.LoanPayment;
import nl.strohalm.cyclos.entities.accounts.loans.LoanPaymentQuery;
import nl.strohalm.cyclos.entities.accounts.loans.LoanQuery;
import nl.strohalm.cyclos.entities.accounts.loans.LoanRepaymentAmountsDTO;
import nl.strohalm.cyclos.entities.accounts.transactions.Payment;
import nl.strohalm.cyclos.entities.accounts.transactions.Transfer;
import nl.strohalm.cyclos.entities.accounts.transactions.TransferType;
import nl.strohalm.cyclos.entities.exceptions.UnexpectedEntityException;
import nl.strohalm.cyclos.entities.groups.AdminGroup;
import nl.strohalm.cyclos.entities.groups.Group;
import nl.strohalm.cyclos.entities.groups.MemberGroup;
import nl.strohalm.cyclos.entities.members.Member;
import nl.strohalm.cyclos.exceptions.PermissionDeniedException;
import nl.strohalm.cyclos.services.BaseServiceSecurity;
import nl.strohalm.cyclos.services.transactions.exceptions.CreditsException;
import nl.strohalm.cyclos.services.transactions.exceptions.UpperCreditLimitReachedException;
import nl.strohalm.cyclos.utils.RelationshipHelper;
import nl.strohalm.cyclos.utils.access.LoggedUser;
import nl.strohalm.cyclos.utils.access.PermissionHelper;
import nl.strohalm.cyclos.utils.validation.ValidationException;
/**
* Security implementation for {@link LoanService}
*
* @author Luis
*/
public class LoanServiceSecurity extends BaseServiceSecurity implements LoanService {
private LoanServiceLocal loanService;
@Override
public List<LoanPayment> calculatePaymentProjection(final ProjectionDTO params) {
permissionService.permission().admin(AdminMemberPermission.LOANS_GRANT).check();
return loanService.calculatePaymentProjection(params);
}
@Override
public LoanPayment discard(final LoanPaymentDTO dto) throws UnexpectedEntityException {
permission(dto).admin(AdminMemberPermission.LOANS_DISCARD).check();
Calendar date = dto.getDate();
// Check for a specific permission for admins to set a date
if (date != null) {
// Currently, there's no specific permission for discard with date. We use repay with date
permissionService.permission().admin(AdminMemberPermission.LOANS_REPAY_WITH_DATE).check();
}
return loanService.discard(dto);
}
@Override
public LoanRepaymentAmountsDTO getLoanPaymentAmount(final LoanPaymentDTO dto) {
checkView(permission(dto));
return loanService.getLoanPaymentAmount(dto);
}
@Override
public Loan grant(final GrantLoanDTO params) throws CreditsException {
if (params.isAutomatic()) {
// This is a manually granted loan
throw new PermissionDeniedException();
}
permissionService.permission(params.getMember()).admin(AdminMemberPermission.LOANS_GRANT).check();
Calendar date = params.getDate();
// Check for a specific permission for admins to set a date
if (date != null) {
// Currently, there's no specific permission for discard with date. We use repay with date
permissionService.permission().admin(AdminMemberPermission.LOANS_GRANT_WITH_DATE).check();
}
TransferType transferType = params.getTransferType();
// Null transfer type will fail validation, so, we don't need to check for it here
if (transferType != null) {
// Check the permission to the loan transfer type
AdminGroup group = LoggedUser.group();
Collection<TransferType> transferTypes = fetchService.fetch(group, Group.Relationships.TRANSFER_TYPES).getTransferTypes();
if (!transferTypes.contains(transferType)) {
throw new PermissionDeniedException();
}
}
return loanService.grant(params);
}
@Override
public Loan load(final Long id, final Relationship... fetch) {
Loan loan = loanService.load(id, fetch);
checkView(permission(loan));
return loan;
}
@Override
public TransactionSummaryVO loanSummary(final Member member) {
checkView(permissionService.permission(member));
return loanService.loanSummary(member);
}
@Override
public Loan markAsInProcess(final Loan loan) throws UnexpectedEntityException {
checkExpiredStatus(loan);
return loanService.markAsInProcess(loan);
}
@Override
public Loan markAsRecovered(final Loan loan) throws UnexpectedEntityException {
checkExpiredStatus(loan);
return loanService.markAsRecovered(loan);
}
@Override
public Loan markAsUnrecoverable(final Loan loan) throws UnexpectedEntityException {
checkExpiredStatus(loan);
return loanService.markAsUnrecoverable(loan);
}
@Override
public TransactionSummaryVO paymentsSummary(final LoanPaymentQuery query) {
applyRestrictions(query);
return loanService.paymentsSummary(query);
}
@Override
public Transfer repay(final RepayLoanDTO params) throws UpperCreditLimitReachedException, UnexpectedEntityException {
Loan loan = fetchLoan(params.getLoan());
// Members can repay loans to their loan groups, depending on group settings
if (LoggedUser.isMember() && loan.getLoanGroup() != null && !LoggedUser.member().equals(loan.getMember())) {
// Loan is to a group and not directly to the logged member
boolean hasPermission = false;
if (loan.getToMembers().contains(LoggedUser.member())) {
// The member was really a receiver of the loan, by the group
MemberGroup group = LoggedUser.member().getMemberGroup();
if (group.getMemberSettings().isRepayLoanByGroup()) {
// This permission does not check the member
hasPermission = permissionService.permission()
.member(MemberPermission.LOANS_REPAY)
.operator(OperatorPermission.LOANS_REPAY)
.hasPermission();
}
}
if (!hasPermission) {
// Cannot view
throw new PermissionDeniedException();
}
} else {
// Brokers cannot repay
permission(params)
.admin(AdminMemberPermission.LOANS_REPAY)
.member(MemberPermission.LOANS_REPAY)
.operator(OperatorPermission.LOANS_REPAY)
.check();
}
// Check for a specific permission for admins to set a date
Calendar date = params.getDate();
if (date != null) {
permissionService.permission().admin(AdminMemberPermission.LOANS_REPAY_WITH_DATE).check();
}
return loanService.repay(params);
}
@Override
public List<LoanPayment> search(final LoanPaymentQuery query) {
applyRestrictions(query);
return loanService.search(query);
}
@Override
public List<Loan> search(final LoanQuery query) {
applyRestrictions(query);
return loanService.search(query);
}
public void setLoanServiceLocal(final LoanServiceLocal loanService) {
this.loanService = loanService;
}
@Override
public void validate(final GrantLoanDTO params) {
// No permission check needed for validate
loanService.validate(params);
}
private void applyRestrictions(final BaseLoanQuery query) {
Member member = query.getMember();
if (member == null) {
permissionService.permission()
.admin(AdminMemberPermission.LOANS_VIEW)
.broker(BrokerPermission.LOANS_VIEW)
.member(MemberPermission.LOANS_VIEW)
.operator(OperatorPermission.LOANS_VIEW)
.check();
if (LoggedUser.isAdministrator()) {
query.setGroups(PermissionHelper.checkSelection(permissionService.getVisibleMemberGroups(), query.getGroups()));
} else if (LoggedUser.isBroker()) {
query.setBroker(LoggedUser.member());
}
} else {
permissionService.permission(member)
.admin(AdminMemberPermission.LOANS_VIEW)
.broker(BrokerPermission.LOANS_VIEW)
.member(MemberPermission.LOANS_VIEW)
.operator(OperatorPermission.LOANS_VIEW)
.check();
}
}
private void checkExpiredStatus(final Loan loan) {
permission(loan).admin(AdminMemberPermission.LOANS_MANAGE_EXPIRED_STATUS).check();
}
private void checkView(final PermissionCheck permission) {
permission
.admin(AdminMemberPermission.LOANS_VIEW)
.broker(BrokerPermission.LOANS_VIEW)
.member(MemberPermission.LOANS_VIEW)
.operator(OperatorPermission.LOANS_VIEW)
.check();
}
private Loan fetchLoan(final Loan loan) {
if (loan == null) {
throw new ValidationException();
}
return fetchService.fetch(loan, RelationshipHelper.nested(Loan.Relationships.TRANSFER, Payment.Relationships.TO, MemberAccount.Relationships.MEMBER));
}
private boolean loggedUserManagesSomebodyWhoCanViewTheLoan(final Loan loan) {
// Can manage the responsible
if (permissionService.manages(loan.getMember())) {
return true;
}
// Or, can manage one of the members of the loan group.
if (loan.getLoanGroup() != null) {
for (Member member : loan.getToMembers()) {
if (permissionService.manages(member)) {
MemberGroup group = member.getMemberGroup();
if (group.getMemberSettings().isViewLoansByGroup()) {
return true;
}
}
}
}
return false;
}
private PermissionCheck permission(Loan loan) {
loan = fetchLoan(loan);
if (loggedUserManagesSomebodyWhoCanViewTheLoan(loan)) {
// This permission does not check the member
return permissionService.permission()
.admin(AdminMemberPermission.LOANS_VIEW)
.broker(BrokerPermission.LOANS_VIEW)
.member(MemberPermission.LOANS_VIEW)
.operator(OperatorPermission.LOANS_VIEW);
}
// Cannot view
throw new PermissionDeniedException();
}
private PermissionCheck permission(final LoanPaymentDTO dto) {
if (dto.getLoan() != null) {
if (dto.getLoanPayment() != null) {
// When both are present, make
LoanPayment loanPayment = fetchService.fetch(dto.getLoanPayment());
if (!loanPayment.getLoan().equals(dto.getLoan())) {
throw new PermissionDeniedException();
}
}
return permission(dto.getLoan());
} else if (dto.getLoanPayment() != null) {
LoanPayment loanPayment = fetchService.fetch(dto.getLoanPayment());
return permission(loanPayment.getLoan());
}
throw new ValidationException();
}
}