/*
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.accounts.guarantees;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import nl.strohalm.cyclos.access.AdminMemberPermission;
import nl.strohalm.cyclos.dao.accounts.guarantees.CertificationDAO;
import nl.strohalm.cyclos.dao.accounts.guarantees.CertificationLogDAO;
import nl.strohalm.cyclos.entities.Relationship;
import nl.strohalm.cyclos.entities.accounts.Currency;
import nl.strohalm.cyclos.entities.accounts.guarantees.Certification;
import nl.strohalm.cyclos.entities.accounts.guarantees.Certification.Status;
import nl.strohalm.cyclos.entities.accounts.guarantees.CertificationLog;
import nl.strohalm.cyclos.entities.accounts.guarantees.CertificationQuery;
import nl.strohalm.cyclos.entities.accounts.guarantees.Guarantee;
import nl.strohalm.cyclos.entities.accounts.guarantees.GuaranteeType;
import nl.strohalm.cyclos.entities.accounts.guarantees.PaymentObligation;
import nl.strohalm.cyclos.entities.members.Member;
import nl.strohalm.cyclos.services.InitializingService;
import nl.strohalm.cyclos.services.accounts.guarantees.exceptions.CertificationStatusChangeException;
import nl.strohalm.cyclos.services.fetch.FetchServiceLocal;
import nl.strohalm.cyclos.services.permissions.PermissionServiceLocal;
import nl.strohalm.cyclos.utils.DateHelper;
import nl.strohalm.cyclos.utils.Period;
import nl.strohalm.cyclos.utils.access.LoggedUser;
import nl.strohalm.cyclos.utils.notifications.MemberNotificationHandler;
import nl.strohalm.cyclos.utils.query.Page;
import nl.strohalm.cyclos.utils.query.PageImpl;
import nl.strohalm.cyclos.utils.query.PageParameters;
import nl.strohalm.cyclos.utils.query.QueryParameters.ResultType;
import nl.strohalm.cyclos.utils.validation.GeneralValidation;
import nl.strohalm.cyclos.utils.validation.PeriodValidation;
import nl.strohalm.cyclos.utils.validation.PeriodValidation.ValidationType;
import nl.strohalm.cyclos.utils.validation.ValidationError;
import nl.strohalm.cyclos.utils.validation.ValidationException;
import nl.strohalm.cyclos.utils.validation.Validator;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Transformer;
public class CertificationServiceImpl implements CertificationServiceLocal, InitializingService {
private class ExistingActiveCertificationValidation implements GeneralValidation {
private static final long serialVersionUID = 840449718151754491L;
@Override
public ValidationError validate(final Object object) {
final Certification certification = (Certification) object;
final GuaranteeType guaranteeType = certification.getGuaranteeType();
final Member buyer = certification.getBuyer();
final Member issuer = certification.getIssuer();
if (guaranteeType == null || buyer == null || issuer == null) {
return null;
} else if (willBeActive(certification) && getActiveCertification(guaranteeType, buyer, issuer) != null) {
return new ValidationError("certification.error.certificationActiveExists");
} else {
return null;
}
}
private boolean willBeActive(final Certification certification) {
Calendar begin = certification.getValidity() == null ? null : certification.getValidity().getBegin();
Calendar end = certification.getValidity() == null ? null : certification.getValidity().getEnd();
if (begin == null || end == null) {
return false;
} else {
final Calendar currentDate = DateHelper.truncate(Calendar.getInstance());
begin = DateHelper.truncate(begin);
end = DateHelper.truncate(end);
return (begin.before(currentDate) || begin.equals(currentDate)) && (end.after(currentDate) || end.equals(currentDate));
}
}
}
private PermissionServiceLocal permissionService;
private GuaranteeServiceLocal guaranteeService;
private CertificationDAO certificationDao;
private CertificationLogDAO certificationLogDao;
private MemberNotificationHandler memberNotificationHandler;
private FetchServiceLocal fetchService;
@Override
public boolean canChangeStatus(final Certification certification, final Certification.Status newStatus) {
boolean isIssuer;
switch (newStatus) {
case ACTIVE:
final Certification activeCert = getActiveCertification(certification.getGuaranteeType().getCurrency(), certification.getBuyer(), certification.getIssuer());
isIssuer = guaranteeService.isIssuer() && certification.getIssuer().equals(LoggedUser.accountOwner());
return isIssuer && isInSomeStatus(certification, Status.SUSPENDED) && (activeCert == null || Status.SCHEDULED == calculateInitialStatus(certification));
case CANCELLED:
final boolean hasPermission = permissionService.hasPermission(AdminMemberPermission.GUARANTEES_CANCEL_CERTIFICATIONS_AS_MEMBER);
return hasPermission && isInSomeStatus(certification, Status.ACTIVE, Status.SUSPENDED, Status.SCHEDULED);
case SUSPENDED:
isIssuer = guaranteeService.isIssuer() && certification.getIssuer().equals(LoggedUser.accountOwner());
return isIssuer && isInSomeStatus(certification, Status.ACTIVE, Status.SCHEDULED);
default:
throw new CertificationStatusChangeException(newStatus, "Can't change certification's status, unsupported target status: " + newStatus);
}
}
/**
* @return true only if the certification's status is CANCELED and the logged user has the right permission granted
*/
@Override
public boolean canDelete(Certification certification) {
certification = fetchService.fetch(certification, Certification.Relationships.ISSUER);
final boolean hasPermision = permissionService.manages(certification.getIssuer()) && permissionService.hasPermission(AdminMemberPermission.GUARANTEES_CANCEL_CERTIFICATIONS_AS_MEMBER);
return hasPermision && isInSomeStatus(certification, Certification.Status.CANCELLED);
}
@Override
public void changeStatus(final Long certificationId, Certification.Status newStatus) {
Certification certification = load(certificationId);
final boolean changeAllowed = canChangeStatus(certification, newStatus);
if (!changeAllowed) {
throw new CertificationStatusChangeException(newStatus);
} else {
// if it's an activation we must check by certification's starting date
if (newStatus == Status.ACTIVE) {
newStatus = calculateInitialStatus(certification);
}
final CertificationLog log = certification.changeStatus(newStatus, LoggedUser.user().getElement());
saveLog(log);
certification = save(certification, false);
switch (newStatus) {
case CANCELLED:
memberNotificationHandler.certificationCanceledNotification(certificationId);
break;
default:
memberNotificationHandler.certificationStatusChangedNotification(certification);
}
}
}
@Override
public Certification getActiveCertification(final Currency currency, final Member buyer, final Member issuer) {
final List<Certification> activeCertifications = certificationDao.getActiveCertificationsForBuyer(buyer, currency);
for (final Certification cert : activeCertifications) {
if (issuer.equals(cert.getIssuer())) {
return cert;
}
}
return null;
}
@Override
public List<Member> getCertificationIssuers(final PaymentObligation paymentObligation) {
final List<Certification> activeCertifications = certificationDao.getActiveCertificationsForBuyer(paymentObligation.getBuyer(), paymentObligation.getCurrency());
final ArrayList<Member> issuers = new ArrayList<Member>();
for (final Certification cert : activeCertifications) {
issuers.add(cert.getIssuer());
}
return issuers;
}
@Override
public BigDecimal getUsedAmount(final Certification certification, final boolean includePendingGuarantees) {
// if we musn't take into account the loan repay then use the DAO implementation
// return certificationDao.getUsedAmount(certification);
// this implementation take into account the loan repayment
final List<Guarantee.Status> statusList = new ArrayList<Guarantee.Status>();
statusList.add(Guarantee.Status.ACCEPTED);
if (includePendingGuarantees) {
statusList.add(Guarantee.Status.PENDING_ADMIN);
statusList.add(Guarantee.Status.PENDING_ISSUER);
}
final List<Guarantee> guarantees = guaranteeService.getGuarantees(certification, PageParameters.all(), statusList);
BigDecimal notPaidAmount = BigDecimal.ZERO;
for (final Guarantee g : guarantees) {
if (g.getStatus() == Guarantee.Status.ACCEPTED && g.getLoan() != null) {
notPaidAmount = notPaidAmount.add(g.getLoan().getRemainingAmount());
} else { // if pending there is no associated loan
notPaidAmount = notPaidAmount.add(g.getAmount());
}
}
return notPaidAmount;
}
@Override
public void initializeService() {
processCertifications(Calendar.getInstance());
}
@Override
public Certification load(final Long id, final Relationship... fetch) {
return certificationDao.load(id, fetch);
}
@Override
public void processCertifications(final Calendar taskTime) {
processCertifications(taskTime, Certification.Status.ACTIVE);
processCertifications(taskTime, Certification.Status.EXPIRED);
}
@Override
public int remove(final Long certificationId) {
return certificationDao.delete(certificationId);
}
@Override
public Certification save(final Certification certification) {
return save(certification, true);
}
@Override
public List<Certification> search(final CertificationQuery queryParameters) {
return certificationDao.seach(queryParameters);
}
@Override
@SuppressWarnings("unchecked")
public List<CertificationDTO> searchWithUsedAmount(final CertificationQuery queryParameters) {
final List<Certification> certifications = certificationDao.seach(queryParameters);
final Transformer transformer = new Transformer() {
@Override
public Object transform(final Object input) {
final Certification certification = (Certification) input;
return new CertificationDTO(certification, getUsedAmount(certification, false));
}
};
List<CertificationDTO> result = (List<CertificationDTO>) CollectionUtils.collect(certifications, transformer, new ArrayList<CertificationDTO>());
if (certifications instanceof Page) {
final Page<Certification> original = (Page<Certification>) certifications;
final PageParameters pageParameters = new PageParameters(original.getPageSize(), original.getCurrentPage());
result = new PageImpl<CertificationDTO>(pageParameters, original.getTotalCount(), result);
}
return result;
}
public void setCertificationDao(final CertificationDAO certificationDao) {
this.certificationDao = certificationDao;
}
public void setCertificationLogDao(final CertificationLogDAO certificationLogDao) {
this.certificationLogDao = certificationLogDao;
}
public void setFetchServiceLocal(final FetchServiceLocal fetchService) {
this.fetchService = fetchService;
}
public void setGuaranteeServiceLocal(final GuaranteeServiceLocal guaranteeService) {
this.guaranteeService = guaranteeService;
}
public void setMemberNotificationHandler(final MemberNotificationHandler memberNotificationHandler) {
this.memberNotificationHandler = memberNotificationHandler;
}
public void setPermissionServiceLocal(final PermissionServiceLocal permissionService) {
this.permissionService = permissionService;
}
@Override
public void validate(final Certification certification) {
// we must run this general validation before to prevent validation process if this fails
final GeneralValidation val = new ExistingActiveCertificationValidation();
final ValidationError error = val.validate(certification);
if (error != null) {
final ValidationException vex = new ValidationException();
vex.addGeneralError(error);
vex.throwIfHasErrors();
} else {
getValidator().validate(certification);
}
}
private Status calculateInitialStatus(final Certification certification) {
final Calendar currentDate = DateHelper.truncate(Calendar.getInstance());
return DateHelper.truncate(certification.getValidity().getBegin()).after(currentDate) ? Certification.Status.SCHEDULED : Certification.Status.ACTIVE;
}
private Certification getActiveCertification(GuaranteeType guaranteeType, final Member buyer, final Member issuer) {
guaranteeType = fetchService.fetch(guaranteeType, GuaranteeType.Relationships.CURRENCY);
return getActiveCertification(guaranteeType.getCurrency(), buyer, issuer);
}
private Validator getValidator() {
final Validator validator = new Validator("certification");
validator.property("amount").required().positiveNonZero();
validator.property("guaranteeType").required().key("certification.guaranteeType");
validator.property("buyer").required().key("certification.buyerUsername");
validator.property("issuer").required().key("certification.issuerUsername");
validator.property("validity").add(new PeriodValidation(ValidationType.BOTH_REQUIRED_AND_NOT_EXPIRED)).key("certification.validity");
return validator;
}
private void initialize(final Certification certification) {
final Status status = calculateInitialStatus(certification);
certification.setStatus(status);
}
/**
* Checks if the certification has some of the specified states
*/
private boolean isInSomeStatus(final Certification certification, final Status... status) {
for (final Status s : status) {
if (certification.getStatus() == s) {
return true;
}
}
return false;
}
private void processCertifications(Calendar time, Certification.Status newStatus) {
time = DateHelper.truncate(time);
final Set<Relationship> fetch = new HashSet<Relationship>();
fetch.add(Certification.Relationships.BUYER);
fetch.add(Certification.Relationships.ISSUER);
fetch.add(Certification.Relationships.LOGS);
final CertificationQuery query = new CertificationQuery();
query.setResultType(ResultType.ITERATOR);
query.setFetch(fetch);
if (newStatus == Certification.Status.ACTIVE) {
query.setStartIn(Period.endingAt(time));
query.setStatusList(Collections.singletonList(Certification.Status.SCHEDULED));
} else {
time.add(Calendar.DATE, -1); // this is to discard the certifications expiring today
query.setEndIn(Period.endingAt(time));
query.setStatusList(Arrays.asList(Certification.Status.ACTIVE, Certification.Status.SUSPENDED));
}
final List<Certification> certifications = search(query);
for (final Certification certification : certifications) {
if (newStatus == Certification.Status.ACTIVE) { // we must search for an already active certification
final Certification alreadyActiveCertification = getActiveCertification(certification.getGuaranteeType(), certification.getBuyer(), certification.getIssuer());
if (alreadyActiveCertification != null) {
newStatus = Certification.Status.SUSPENDED;
}
}
certification.setStatus(newStatus);
final CertificationLog log = certification.changeStatus(newStatus, null);
saveLog(log);
save(certification, false);
// Notify
memberNotificationHandler.certificationStatusChangedNotification(certification);
}
}
private Certification save(Certification certification, final boolean validate) {
if (validate) {
validate(certification);
}
if (certification.isTransient()) {
initialize(certification);
certification = certificationDao.insert(certification);
final CertificationLog log = certification.getNewLog(certification.getStatus(), LoggedUser.user().getElement());
saveLog(log);
// Notify
memberNotificationHandler.certificationIssuedNotification(certification);
return certification;
} else {
return certificationDao.update(certification);
}
}
private CertificationLog saveLog(final CertificationLog certificationLog) {
if (certificationLog.isTransient()) {
return certificationLogDao.insert(certificationLog);
} else {
return certificationLogDao.update(certificationLog);
}
}
}