/*
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.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import nl.strohalm.cyclos.dao.accounts.guarantees.PaymentObligationDAO;
import nl.strohalm.cyclos.dao.accounts.guarantees.PaymentObligationLogDAO;
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.PaymentObligation;
import nl.strohalm.cyclos.entities.accounts.guarantees.PaymentObligation.Status;
import nl.strohalm.cyclos.entities.accounts.guarantees.PaymentObligationLog;
import nl.strohalm.cyclos.entities.accounts.guarantees.PaymentObligationQuery;
import nl.strohalm.cyclos.entities.members.Member;
import nl.strohalm.cyclos.services.InitializingService;
import nl.strohalm.cyclos.services.accounts.guarantees.exceptions.PaymentObligationStatusChangeException;
import nl.strohalm.cyclos.services.fetch.FetchServiceLocal;
import nl.strohalm.cyclos.utils.DateHelper;
import nl.strohalm.cyclos.utils.Period;
import nl.strohalm.cyclos.utils.TimePeriod;
import nl.strohalm.cyclos.utils.access.LoggedUser;
import nl.strohalm.cyclos.utils.notifications.MemberNotificationHandler;
import nl.strohalm.cyclos.utils.query.QueryParameters.ResultType;
import nl.strohalm.cyclos.utils.validation.GeneralValidation;
import nl.strohalm.cyclos.utils.validation.ValidationError;
import nl.strohalm.cyclos.utils.validation.ValidationException;
import nl.strohalm.cyclos.utils.validation.Validator;
public class PaymentObligationServiceImpl implements PaymentObligationServiceLocal, InitializingService {
private class ExistingActiveCertificationValidation implements GeneralValidation {
private static final long serialVersionUID = -5784222476641557090L;
@Override
public ValidationError validate(final Object object) {
final PaymentObligation paymentObligation = (PaymentObligation) object;
final Member buyer = paymentObligation.getBuyer();
Currency currency = fetchService.fetch(paymentObligation.getCurrency());
if (buyer == null || currency == null) {
return null;
} else if (certificationService.getCertificationIssuers(paymentObligation).isEmpty()) {
return new ValidationError("paymentObligation.error.certificationActiveNotExists", currency.getName());
} else {
return null;
}
}
}
private class MaxPublicationDateBeforeExpirationDateValidation implements GeneralValidation {
private static final long serialVersionUID = 1L;
@Override
public ValidationError validate(final Object object) {
final PaymentObligation paymentObligation = (PaymentObligation) object;
final Calendar maxPublishDate = paymentObligation.getMaxPublishDate();
final Calendar expirationDate = paymentObligation.getExpirationDate();
if (maxPublishDate != null && expirationDate != null && maxPublishDate.after(expirationDate)) {
return new ValidationError("paymentObligation.error.maxPublicationDateAfterExpirationDate");
}
return null;
}
}
private PaymentObligationDAO paymentObligationDao;
private PaymentObligationLogDAO paymentObligationLogDao;
private GuaranteeServiceLocal guaranteeService;
private CertificationServiceLocal certificationService;
private MemberNotificationHandler memberNotificationHandler;
private FetchServiceLocal fetchService;
@Override
public boolean canChangeStatus(final PaymentObligation paymentObligation, final Status newStatus) {
boolean isBuyer;
boolean isSeller;
switch (newStatus) {
case PUBLISHED:
final Calendar today = DateHelper.truncate(Calendar.getInstance());
final Calendar maxPublishDate = paymentObligation.getMaxPublishDate();
isBuyer = guaranteeService.isBuyer() && paymentObligation.getBuyer().equals(LoggedUser.accountOwner());
final boolean validDates = maxPublishDate.equals(today) || maxPublishDate.after(today);
return isBuyer && validDates && isInSomeStatus(paymentObligation, Status.REGISTERED);
case REGISTERED: // conceal
isBuyer = guaranteeService.isBuyer() && paymentObligation.getBuyer().equals(LoggedUser.accountOwner());
return isBuyer && isInSomeStatus(paymentObligation, Status.PUBLISHED);
case ACCEPTED:
case REJECTED:
isSeller = guaranteeService.isSeller() && paymentObligation.getSeller().equals(LoggedUser.accountOwner());
return isSeller && isInSomeStatus(paymentObligation, Status.PUBLISHED);
default:
throw new PaymentObligationStatusChangeException(newStatus, "Can't change payment obligation's status, unsupported target status: " + newStatus);
}
}
@Override
public boolean canDelete(final PaymentObligation paymentObligation) {
return isInSomeStatus(paymentObligation, Status.REGISTERED) && guaranteeService.isBuyer() && paymentObligation.getBuyer().equals(LoggedUser.accountOwner());
}
@Override
public PaymentObligation changeStatus(final Long paymentObligationId, final Status newStatus) {
final PaymentObligation paymentObligation = load(paymentObligationId);
final boolean changeAllowed = canChangeStatus(paymentObligation, newStatus);
if (!changeAllowed) {
throw new PaymentObligationStatusChangeException(newStatus);
} else {
final PaymentObligationLog log = paymentObligation.changeStatus(newStatus, LoggedUser.user().getElement());
saveLog(log);
save(paymentObligation, false);
switch (newStatus) {
case PUBLISHED:
memberNotificationHandler.paymentObligationPublishedNotification(paymentObligation);
break;
case REJECTED:
memberNotificationHandler.paymentObligationRejectedNotification(paymentObligation);
break;
}
}
return paymentObligation;
}
@Override
public Long[] checkPaymentObligationPeriod(final PaymentObligationPackDTO dto) {
if (dto.getPaymentObligations() == null || dto.getPaymentObligations().length == 0) {
return new Long[0];
}
final Long[] paymentObligationIds = dto.getPaymentObligations();
final List<PaymentObligation> paymentObligations = paymentObligationDao.loadOrderedByExpiration(paymentObligationIds);
// take the first to get the currency and buyer (all belong to the same buyer and have the same currency)
final PaymentObligation firstPaymentObligation = paymentObligations.get(0);
final Certification certification = certificationService.getActiveCertification(firstPaymentObligation.getCurrency(), firstPaymentObligation.getBuyer(), dto.getIssuer());
final TimePeriod paymentObligationPeriod = certification.getGuaranteeType().getPaymentObligationPeriod();
final Calendar limit = Calendar.getInstance();
limit.setTimeInMillis(firstPaymentObligation.getExpirationDate().getTimeInMillis());
limit.add(paymentObligationPeriod.getField().getCalendarValue(), paymentObligationPeriod.getNumber());
final List<Long> exceeded = new ArrayList<Long>();
for (final PaymentObligation po : paymentObligations) {
if (po.getExpirationDate().after(limit)) {
exceeded.add(po.getId());
}
}
return exceeded.toArray(new Long[exceeded.size()]);
}
@Override
public PaymentObligation.Status[] getSellerStatusToFilter() {
final PaymentObligation.Status[] status = new PaymentObligation.Status[4];
status[0] = PaymentObligation.Status.PUBLISHED;
status[1] = PaymentObligation.Status.ACCEPTED;
status[2] = PaymentObligation.Status.REJECTED;
status[3] = PaymentObligation.Status.EXPIRED;
return status;
}
@Override
public PaymentObligation.Status[] getStatusToFilter() {
final boolean isSeller = guaranteeService.isSeller();
final boolean isBuyer = guaranteeService.isBuyer();
if (isSeller && !isBuyer) {
return getSellerStatusToFilter();
} else {
return PaymentObligation.Status.values();
}
}
@Override
public void initializeService() {
processPaymentObligations(Calendar.getInstance());
}
@Override
public PaymentObligation load(final Long id, final Relationship... fetch) {
return paymentObligationDao.load(id, fetch);
}
@Override
public List<PaymentObligation> loadOrderedByExpiration(final Long... ids) {
return paymentObligationDao.loadOrderedByExpiration(ids);
}
@Override
public void processPaymentObligations(final Calendar taskTime) {
final Calendar time = DateHelper.truncate(taskTime);
final PaymentObligationQuery query = new PaymentObligationQuery();
time.add(Calendar.DATE, -1); // this is to discard the POs expiring today
query.setExpiration(Period.endingAt(time));
query.setResultType(ResultType.ITERATOR);
query.setApplyExpirationToMaxPublishDate(true);
final Set<Relationship> fetch = new HashSet<Relationship>();
fetch.add(PaymentObligation.Relationships.LOGS);
query.setFetch(fetch);
query.setStatusList(Arrays.asList(PaymentObligation.Status.PUBLISHED));
final List<PaymentObligation> paymentObligations = paymentObligationDao.search(query);
for (final PaymentObligation paymentObligation : paymentObligations) {
final Calendar expirationDate = DateHelper.truncate(paymentObligation.getExpirationDate());
// the expiration date or the max publication date are before task's time
final PaymentObligation.Status newStatus = expirationDate.before(taskTime) ? PaymentObligation.Status.EXPIRED : PaymentObligation.Status.REGISTERED;
paymentObligation.setStatus(newStatus);
final PaymentObligationLog log = paymentObligation.changeStatus(newStatus, null);
saveLog(log);
save(paymentObligation, false);
}
}
@Override
public int remove(final Long paymentObligationId) {
return paymentObligationDao.delete(paymentObligationId);
}
@Override
public PaymentObligation save(PaymentObligation paymentObligation, final boolean validate) {
if (validate) {
validate(paymentObligation);
}
if (paymentObligation.isTransient()) {
initialize(paymentObligation);
paymentObligation = paymentObligationDao.insert(paymentObligation);
final PaymentObligationLog paymentObligationLog = paymentObligation.getNewLog(paymentObligation.getStatus(), LoggedUser.user().getElement());
saveLog(paymentObligationLog);
return paymentObligation;
} else {
return paymentObligationDao.update(paymentObligation);
}
}
@Override
public PaymentObligationLog saveLog(final PaymentObligationLog paymentObligationLog) {
if (paymentObligationLog.isTransient()) {
return paymentObligationLogDao.insert(paymentObligationLog);
} else {
return paymentObligationLogDao.update(paymentObligationLog);
}
}
@Override
public List<PaymentObligation> search(final PaymentObligationQuery queryParameters) {
return paymentObligationDao.search(queryParameters);
}
public void setCertificationServiceLocal(final CertificationServiceLocal certificationService) {
this.certificationService = certificationService;
}
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 setPaymentObligationDao(final PaymentObligationDAO paymentObligationDao) {
this.paymentObligationDao = paymentObligationDao;
}
public void setPaymentObligationLogDao(final PaymentObligationLogDAO paymentObligationLogDao) {
this.paymentObligationLogDao = paymentObligationLogDao;
}
@Override
public void validate(final PaymentObligation paymentObligation) {
// we must run this general validation before to prevent validation process if this fails
final GeneralValidation val = new ExistingActiveCertificationValidation();
final ValidationError error = val.validate(paymentObligation);
if (error != null) {
final ValidationException vex = new ValidationException();
vex.addGeneralError(error);
vex.throwIfHasErrors();
} else {
getValidator().validate(paymentObligation);
}
}
private Validator getValidator() {
final Validator validator = new Validator("paymentObligation");
// the status is not validated because it is set in at insert
validator.property("buyer").required().key("paymentObligation.buyerUsername");
validator.property("seller").required().key("paymentObligation.sellerUsername");
validator.property("expirationDate").required().futureOrToday();
validator.property("maxPublishDate").required().futureOrToday();
validator.property("amount").required().positiveNonZero();
validator.property("currency").required();
validator.property("description").required();
validator.general(new MaxPublicationDateBeforeExpirationDateValidation());
return validator;
}
private void initialize(final PaymentObligation paymentObligation) {
paymentObligation.setStatus(PaymentObligation.Status.REGISTERED);
paymentObligation.setRegistrationDate(Calendar.getInstance());
}
/**
* Checks if the payment obligation has some of the specified states
* @param certification
* @param status
*/
private boolean isInSomeStatus(final PaymentObligation paymentObligation, final Status... status) {
for (final Status s : status) {
if (paymentObligation.getStatus() == s) {
return true;
}
}
return false;
}
}