nl.strohalm.cyclos.services.elements.ReferenceServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for nl.strohalm.cyclos.services.elements.ReferenceServiceImpl.java

Source

/*
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.elements;

import java.util.Calendar;
import java.util.Collection;
import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

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.dao.members.ReferenceDAO;
import nl.strohalm.cyclos.dao.members.ReferenceHistoryDAO;
import nl.strohalm.cyclos.entities.Relationship;
import nl.strohalm.cyclos.entities.accounts.transactions.Payment;
import nl.strohalm.cyclos.entities.accounts.transactions.PaymentAwaitingFeedbackDTO;
import nl.strohalm.cyclos.entities.accounts.transactions.ScheduledPayment;
import nl.strohalm.cyclos.entities.accounts.transactions.Transfer;
import nl.strohalm.cyclos.entities.accounts.transactions.TransferType;
import nl.strohalm.cyclos.entities.alerts.Alert;
import nl.strohalm.cyclos.entities.alerts.AlertQuery;
import nl.strohalm.cyclos.entities.alerts.MemberAlert;
import nl.strohalm.cyclos.entities.exceptions.EntityNotFoundException;
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.GeneralReference;
import nl.strohalm.cyclos.entities.members.Member;
import nl.strohalm.cyclos.entities.members.PaymentsAwaitingFeedbackQuery;
import nl.strohalm.cyclos.entities.members.Reference;
import nl.strohalm.cyclos.entities.members.Reference.Level;
import nl.strohalm.cyclos.entities.members.Reference.Nature;
import nl.strohalm.cyclos.entities.members.ReferenceHistoryLog;
import nl.strohalm.cyclos.entities.members.ReferenceHistoryLogQuery;
import nl.strohalm.cyclos.entities.members.ReferenceQuery;
import nl.strohalm.cyclos.entities.members.TransactionFeedback;
import nl.strohalm.cyclos.entities.settings.AlertSettings;
import nl.strohalm.cyclos.exceptions.PermissionDeniedException;
import nl.strohalm.cyclos.services.alerts.AlertServiceLocal;
import nl.strohalm.cyclos.services.fetch.FetchServiceLocal;
import nl.strohalm.cyclos.services.permissions.PermissionServiceLocal;
import nl.strohalm.cyclos.services.settings.SettingsServiceLocal;
import nl.strohalm.cyclos.utils.CacheCleaner;
import nl.strohalm.cyclos.utils.DateHelper;
import nl.strohalm.cyclos.utils.EntityHelper;
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.PageHelper;
import nl.strohalm.cyclos.utils.query.QueryParameters.ResultType;
import nl.strohalm.cyclos.utils.validation.GeneralValidation;
import nl.strohalm.cyclos.utils.validation.InvalidError;
import nl.strohalm.cyclos.utils.validation.PropertyValidation;
import nl.strohalm.cyclos.utils.validation.RequiredError;
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.lang.StringUtils;

/**
 * Implementation class for reference services
 * @author rafael
 * @author luis
 */
public class ReferenceServiceImpl implements ReferenceServiceLocal {

    private SettingsServiceLocal settingsService;
    private AlertServiceLocal alertService;
    private FetchServiceLocal fetchService;
    private PermissionServiceLocal permissionService;
    private ReferenceDAO referenceDao;
    private ReferenceHistoryDAO referenceHistoryDao;
    private MemberNotificationHandler memberNotificationHandler;

    @Override
    public boolean canGiveGeneralReference(final Member member) {
        return !LoggedUser.isAdministrator() && !LoggedUser.element().getAccountOwner().equals(member)
                && permissionService.permission().member(MemberPermission.REFERENCES_GIVE)
                        .operator(OperatorPermission.REFERENCES_MANAGE_MEMBER_REFERENCES).hasPermission()
                && permissionService.relatesTo(member);
    }

    @Override
    public boolean canManage(final Reference ref) {
        switch (ref.getNature()) {
        case GENERAL:
            return canManageGeneralReference(ref.getFrom());
        case TRANSACTION:

            // If it's a new transaction feedback (no comments have been added) then if the logged user is a member or an operator, they must
            // manage the from of the transaction feedback.
            if (ref.isTransient() && (LoggedUser.isMember() || LoggedUser.isOperator())
                    && !permissionService.manages(ref.getFrom())) {
                return false;
            }

            // If it's a reply feedback (no reply comments have been added) then if the logged user is a member or an operator, he must manage the
            // to of the transaction feedback.
            if (!ref.isTransient() && (LoggedUser.isMember() || LoggedUser.isOperator())
                    && !permissionService.manages(ref.getTo())) {
                return false;
            }

            if (!(permissionService.manages(ref.getFrom()) || permissionService.manages(ref.getTo()))) {
                return false;

                // If the Logged user is a broker, then he can only manage his own transaction feedbacks.
            } else if (LoggedUser.isBroker()
                    && !(LoggedUser.element().equals(ref.getFrom()) || LoggedUser.element().equals(ref.getTo()))) {
                return false;
            }

            return permissionService.permission().admin(AdminMemberPermission.TRANSACTION_FEEDBACKS_MANAGE).member()
                    .operator(OperatorPermission.REFERENCES_MANAGE_MEMBER_TRANSACTION_FEEDBACKS).hasPermission();
        }
        return false;
    }

    @Override
    public boolean canManageGeneralReference(final Member member) {
        return permissionService.permission(member).admin(AdminMemberPermission.REFERENCES_MANAGE)
                .broker(BrokerPermission.REFERENCES_MANAGE).member(MemberPermission.REFERENCES_GIVE)
                .operator(OperatorPermission.REFERENCES_MANAGE_MEMBER_REFERENCES).hasPermission();
    }

    @Override
    public boolean canReplyFeedbackNow(final TransactionFeedback transactionFeedback) {
        Payment payment = transactionFeedback.getPayment();
        Calendar date = transactionFeedback.getDate();
        final Calendar replyLimit = payment.getType().getFeedbackReplyExpirationTime().add(date);
        return replyLimit.after(Calendar.getInstance());
    }

    @Override
    public Map<Level, Integer> countGivenReferencesByLevel(final Reference.Nature nature) {
        Collection<MemberGroup> memberGroups = null;
        if (LoggedUser.hasUser()) {
            AdminGroup adminGroup = LoggedUser.group();
            adminGroup = fetchService.fetch(adminGroup, AdminGroup.Relationships.MANAGES_GROUPS);
            memberGroups = adminGroup.getManagesGroups();
        }
        return referenceDao.countGivenReferencesByLevel(nature, memberGroups);
    }

    @Override
    public Map<Level, Integer> countReferencesByLevel(final Reference.Nature nature, final Member member,
            final boolean received) {
        return normalizeCountByLevel(referenceDao.countReferencesByLevel(nature, null, member, received));
    }

    @Override
    public Map<Level, Integer> countReferencesHistoryByLevel(final Reference.Nature nature, final Member member,
            final Period period, final boolean received) {
        Map<Level, Integer> countByLevel;
        if (nature == Reference.Nature.TRANSACTION) {
            countByLevel = referenceDao.countReferencesByLevel(nature, period, member, received);
        } else {
            countByLevel = referenceHistoryDao.countReferencesHistoryByLevel(member, period, received);
        }
        return normalizeCountByLevel(countByLevel);
    }

    @Override
    public Collection<Nature> getNaturesByGroup(MemberGroup group) {
        final Collection<Nature> natures = EnumSet.noneOf(Nature.class);

        // Check for transaction references
        group = fetchService.fetch(group, Group.Relationships.TRANSFER_TYPES);
        for (final TransferType transferType : group.getTransferTypes()) {
            if (transferType.isRequiresFeedback()) {
                natures.add(Nature.TRANSACTION);
                break;
            }
        }

        if (permissionService.hasPermission(group, MemberPermission.REFERENCES_GIVE)) {
            natures.add(Reference.Nature.GENERAL);
        }

        return natures;
    }

    @Override
    public TransactionFeedbackAction getPossibleAction(final TransactionFeedback transactionFeedback) {
        if (transactionFeedback.isTransient()) {
            if (canFeedbackNow(transactionFeedback)) {
                return TransactionFeedbackAction.COMMENTS;
            } else {
                return TransactionFeedbackAction.VIEW;
            }
        } else {
            // Check the current feedback
            final TransactionFeedback current = (TransactionFeedback) load(transactionFeedback.getId());
            if (LoggedUser.isAdministrator()) {
                return TransactionFeedbackAction.ADMIN_EDIT;
            } else if ((LoggedUser.element().getAccountOwner().equals(current.getTo())
                    && StringUtils.isEmpty(current.getReplyComments())) && canReplyFeedbackNow(current)) {
                return TransactionFeedbackAction.REPLY_COMMENTS;
            } else {
                return TransactionFeedbackAction.VIEW;
            }
        }
    }

    @Override
    public Reference load(final Long id, final Relationship... fetch) {
        return referenceDao.load(id, fetch);
    }

    @Override
    @SuppressWarnings("unchecked")
    public GeneralReference loadGeneral(final Member from, final Member to, final Relationship... fetch)
            throws EntityNotFoundException {
        final ReferenceQuery query = new ReferenceQuery();
        query.fetch(fetch);
        query.setNature(Reference.Nature.GENERAL);
        query.setFrom(from);
        query.setTo(to);
        final List<GeneralReference> list = (List<GeneralReference>) search(query);
        if (list.isEmpty()) {
            throw new EntityNotFoundException(GeneralReference.class);
        }
        return list.iterator().next();
    }

    @Override
    @SuppressWarnings("unchecked")
    public TransactionFeedback loadTransactionFeedback(final Payment payment, final Relationship... fetch)
            throws EntityNotFoundException {
        final ReferenceQuery query = new ReferenceQuery();
        query.setNature(Nature.TRANSACTION);
        if (payment instanceof ScheduledPayment) {
            query.setScheduledPayment((ScheduledPayment) payment);
        } else {
            query.setTransfer((Transfer) payment);
        }
        query.setUniqueResult();
        final List<TransactionFeedback> refs = (List<TransactionFeedback>) search(query);
        if (refs.isEmpty()) {
            throw new EntityNotFoundException(TransactionFeedback.class);
        }
        return refs.iterator().next();
    }

    @Override
    public int processExpiredFeedbacks(Calendar time) {
        time = DateHelper.truncate(time);
        final PaymentsAwaitingFeedbackQuery query = new PaymentsAwaitingFeedbackQuery();
        query.setExpired(true);
        query.setResultType(ResultType.ITERATOR);
        final List<PaymentAwaitingFeedbackDTO> paymentsAwaitingFeedback = searchPaymentsAwaitingFeedback(query);
        int processed = 0;
        CacheCleaner cleaner = new CacheCleaner(fetchService);
        for (final PaymentAwaitingFeedbackDTO dto : paymentsAwaitingFeedback) {
            // Fetch the transfer type
            TransferType transferType = EntityHelper.reference(TransferType.class, dto.getTransferTypeId());
            transferType = fetchService.fetch(transferType);

            // Fetch the payment
            Class<? extends Payment> paymentClass = dto.isScheduled() ? ScheduledPayment.class : Transfer.class;
            Payment payment = fetchService.fetch(EntityHelper.reference(paymentClass, dto.getId()),
                    Payment.Relationships.FROM, Payment.Relationships.TO);

            // Insert the feedback with the default comments
            final TransactionFeedback feedback = new TransactionFeedback();
            feedback.setDate(time);
            feedback.setComments(transferType.getDefaultFeedbackComments());
            feedback.setLevel(transferType.getDefaultFeedbackLevel());
            feedback.setFrom((Member) payment.getFromOwner());
            feedback.setTo((Member) payment.getToOwner());
            feedback.setPayment(payment);
            referenceDao.insert(feedback, false);
            cleaner.clearCache();
            processed++;
        }
        return processed;
    }

    @Override
    public int remove(final Long... ids) {
        // Before remove the references, update their last reference history logs
        for (final Long id : ids) {
            final Reference reference = load(id, (Relationship[]) null);
            updatePreviousReferenceHistoryLog(reference);
        }
        return referenceDao.delete(ids);
    }

    @Override
    public GeneralReference save(final GeneralReference reference) {
        reference.setDate(Calendar.getInstance());
        return (GeneralReference) doSave(reference);
    }

    @Override
    public TransactionFeedback save(final TransactionFeedback transactionFeedback) {
        switch (getPossibleAction(transactionFeedback)) {
        case ADMIN_EDIT:
            return saveTransactionFeedbackByAdmin(transactionFeedback);
        case COMMENTS:
            return saveTransactionFeedbackComments(transactionFeedback);
        case REPLY_COMMENTS:
            return saveTransactionFeedbackReplyComments(transactionFeedback);
        default:
            throw new PermissionDeniedException();
        }
    }

    @Override
    public List<? extends Reference> search(final ReferenceQuery query) {
        return referenceDao.search(query);
    }

    @Override
    public List<PaymentAwaitingFeedbackDTO> searchPaymentsAwaitingFeedback(
            final PaymentsAwaitingFeedbackQuery query) {
        return referenceDao.searchPaymentsAwaitingFeedback(query);
    }

    public void setAlertServiceLocal(final AlertServiceLocal alertService) {
        this.alertService = alertService;
    }

    public void setFetchServiceLocal(final FetchServiceLocal fetchService) {
        this.fetchService = fetchService;
    }

    public void setMemberNotificationHandler(final MemberNotificationHandler memberNotificationHandler) {
        this.memberNotificationHandler = memberNotificationHandler;
    }

    public void setPermissionServiceLocal(final PermissionServiceLocal permissionService) {
        this.permissionService = permissionService;
    }

    public void setReferenceDao(final ReferenceDAO referenceDAO) {
        referenceDao = referenceDAO;
    }

    public void setReferenceHistoryDao(final ReferenceHistoryDAO referenceHistoryDao) {
        this.referenceHistoryDao = referenceHistoryDao;
    }

    public void setSettingsServiceLocal(final SettingsServiceLocal settingsService) {
        this.settingsService = settingsService;
    }

    @Override
    public void validate(final Reference reference) throws ValidationException {
        Validator validator;
        if (reference instanceof GeneralReference) {
            validator = getGeneralValidator();
        } else {
            validator = getTransactionFeedbackValidator((TransactionFeedback) reference);
        }
        validator.validate(reference);
    }

    /**
     * Returns true if a member or an operator are in time to make the feedback.
     * @param transactionFeedback
     * @return
     */
    private boolean canFeedbackNow(final TransactionFeedback transactionFeedback) {
        Payment payment = transactionFeedback.getPayment();
        payment = fetchService.fetch(payment, Payment.Relationships.TYPE);
        Calendar paymentDate;
        if (payment instanceof ScheduledPayment) {
            paymentDate = payment.getDate();
        } else {
            paymentDate = payment.getProcessDate();
        }
        final Calendar commentLimit = payment.getType().getFeedbackExpirationTime().add(paymentDate);
        return commentLimit.after(Calendar.getInstance());
    }

    private Validator createBasicValidator() {
        final Validator validator = new Validator("reference");
        validator.property("level").required();
        validator.property("from").required();
        validator.property("to").required().add(new PropertyValidation() {
            private static final long serialVersionUID = 5881069152089528552L;

            @Override
            public ValidationError validate(final Object object, final Object name, final Object value) {
                final Reference reference = (Reference) object;
                // From and to cannot be the same
                final Member from = reference.getFrom();
                if (from != null && from.equals(value)) {
                    return new InvalidError();
                }
                return null;
            }
        });
        validator.property("comments").required().maxLength(1000);
        return validator;
    }

    /**
     * Create a reference history log of the reference
     */
    private void createNewReferenceHistoryLog(final Reference reference) {
        final ReferenceHistoryLog log = new ReferenceHistoryLog();
        log.setFrom(reference.getFrom());
        log.setTo(reference.getTo());
        log.setLevel(reference.getLevel());
        log.setPeriod(Period.begginingAt(reference.getDate()));
        referenceHistoryDao.insert(log);
    }

    private Reference doSave(Reference reference) {
        validate(reference);
        boolean isInsert = reference.isTransient();
        if (isInsert) {
            // Insert the reference
            reference = referenceDao.insert(reference);
        } else {
            // Update the current reference
            reference = referenceDao.update(reference);
            // Update the log of the previous reference
            if (reference instanceof GeneralReference) {
                updatePreviousReferenceHistoryLog(reference);
            }
        }
        // Specific operators for general references
        if (reference instanceof GeneralReference) {
            // Insert a log for the new reference
            createNewReferenceHistoryLog(reference);

            // Compute the given / received references to check if should create an alert
            final AlertSettings alertSettings = settingsService.getAlertSettings();
            // Given
            final Member from = reference.getFrom();
            if (alertSettings.getGivenVeryBadRefs() > 0) {
                final int givenVeryBad = referenceDao
                        .countReferencesByLevel(reference.getNature(), null, from, false)
                        .get(Reference.Level.VERY_BAD);
                if (givenVeryBad >= alertSettings.getGivenVeryBadRefs()) {
                    final AlertQuery query = new AlertQuery();
                    query.setType(Alert.Type.MEMBER);
                    query.setMember(from);
                    query.setKey(MemberAlert.Alerts.GIVEN_VERY_BAD_REFS.getValue());
                    query.setPageForCount();
                    final boolean hasAlert = PageHelper.getTotalCount(alertService.search(query)) > 0;
                    if (!hasAlert) {
                        alertService.create(from, MemberAlert.Alerts.GIVEN_VERY_BAD_REFS, givenVeryBad);
                    }
                }
            }
            // Received
            final Member to = reference.getTo();
            if (alertSettings.getReceivedVeryBadRefs() > 0) {
                final int receivedVeryBad = referenceDao
                        .countReferencesByLevel(reference.getNature(), null, to, true)
                        .get(Reference.Level.VERY_BAD);
                if (receivedVeryBad >= alertSettings.getReceivedVeryBadRefs()) {
                    final AlertQuery query = new AlertQuery();
                    query.setType(Alert.Type.MEMBER);
                    query.setMember(from);
                    query.setKey(MemberAlert.Alerts.RECEIVED_VERY_BAD_REFS.getValue());
                    query.setPageForCount();
                    final boolean hasAlert = PageHelper.getTotalCount(alertService.search(query)) > 0;
                    if (!hasAlert) {
                        alertService.create(to, MemberAlert.Alerts.RECEIVED_VERY_BAD_REFS, receivedVeryBad);
                    }
                }
            }
        }
        if (isInsert && reference instanceof GeneralReference) {
            memberNotificationHandler.receivedReferenceNotification(reference);
        }
        return reference;
    }

    private Validator getGeneralValidator() {
        return createBasicValidator();
    }

    private Validator getTransactionFeedbackValidator(final TransactionFeedback transactionFeedback) {
        final Validator transactionFeedbackValidator = createBasicValidator();
        transactionFeedbackValidator.property("payment").required();

        transactionFeedbackValidator.general(new GeneralValidation() {

            private static final long serialVersionUID = 1L;

            @Override
            public ValidationError validate(final Object object) {
                TransactionFeedback transactionFeedback = (TransactionFeedback) object;
                // Check if the time to make the feedback has expired.
                if (transactionFeedback.isTransient()) {
                    boolean readyForFeedback = !LoggedUser.isAdministrator()
                            && (LoggedUser.element().getAccountOwner().equals(transactionFeedback.getFrom()));
                    if (readyForFeedback && !canFeedbackNow(transactionFeedback)) {
                        return new ValidationError("reference.transactionFeedback.feedbackPeriodExpired");
                    }
                }

                // Check if the time to make the reply has expired.
                if (!transactionFeedback.isTransient()) {
                    TransactionFeedback current = (TransactionFeedback) load(transactionFeedback.getId());
                    boolean readyForReply = (!LoggedUser.isAdministrator())
                            && (LoggedUser.element().getAccountOwner().equals(current.getTo())
                                    && StringUtils.isEmpty(current.getReplyComments()));
                    if (readyForReply && !canReplyFeedbackNow(current)) {
                        return new ValidationError("reference.transactionFeedback.feedbackPeriodExpired");
                    }
                }
                return null;
            }
        });

        return transactionFeedbackValidator;
    }

    private Map<Level, Integer> normalizeCountByLevel(final Map<Level, Integer> countMap) {
        final Map<Level, Integer> countByLevel = new LinkedHashMap<Level, Integer>();
        for (final Level level : settingsService.getLocalSettings().getReferenceLevelList()) {
            final Integer count = countMap.get(level);
            countByLevel.put(level, count == null ? 0 : count);
        }
        return countByLevel;
    }

    private TransactionFeedback saveTransactionFeedbackByAdmin(TransactionFeedback transactionFeedback) {
        final Calendar now = Calendar.getInstance();
        final Reference.Level level = transactionFeedback.getLevel();
        final String comments = transactionFeedback.getComments();
        final String replyComments = transactionFeedback.getReplyComments();
        final String adminComments = transactionFeedback.getAdminComments();
        transactionFeedback = referenceDao.load(transactionFeedback.getId());
        transactionFeedback.setLevel(level);
        transactionFeedback.setComments(comments);
        if (StringUtils.isNotEmpty(replyComments)) {
            transactionFeedback.setReplyComments(replyComments);
            if (transactionFeedback.getReplyCommentsDate() == null) {
                transactionFeedback.setReplyCommentsDate(now);
            }
        } else {
            transactionFeedback.setReplyComments(null);
        }
        if (StringUtils.isNotEmpty(adminComments)) {
            transactionFeedback.setAdminComments(adminComments);
            transactionFeedback.setAdminCommentsDate(now);
        } else {
            transactionFeedback.setAdminComments(null);
            transactionFeedback.setAdminCommentsDate(null);
        }
        validate(transactionFeedback);
        transactionFeedback = referenceDao.update(transactionFeedback);
        memberNotificationHandler.transactionFeedBackAdminCommentsNotification(transactionFeedback);
        return transactionFeedback;
    }

    private TransactionFeedback saveTransactionFeedbackComments(TransactionFeedback transactionFeedback) {

        final Payment payment = fetchService.fetch(transactionFeedback.getPayment(), Payment.Relationships.TYPE,
                Payment.Relationships.FROM, Payment.Relationships.TO);
        final Reference.Level level = transactionFeedback.getLevel();
        final String comments = transactionFeedback.getComments();

        if (!payment.getType().isRequiresFeedback()) {
            throw new UnexpectedEntityException();
        }

        // Check whether the feedback exists
        try {
            transactionFeedback = loadTransactionFeedback(payment);
            // It already exists. Return the current one
        } catch (final EntityNotFoundException e) {
            // Doesn't exist yet. Let's save it
            transactionFeedback = new TransactionFeedback();
            transactionFeedback.setDate(Calendar.getInstance());
            transactionFeedback.setPayment(payment);
            transactionFeedback.setFrom((Member) payment.getFromOwner());
            transactionFeedback.setTo((Member) payment.getToOwner());
            transactionFeedback.setLevel(level);
            transactionFeedback.setComments(comments);
            transactionFeedback = (TransactionFeedback) doSave(transactionFeedback);
        }
        memberNotificationHandler.transactionFeedBackReceivedNotification(transactionFeedback);
        return transactionFeedback;
    }

    private TransactionFeedback saveTransactionFeedbackReplyComments(TransactionFeedback transactionFeedback) {
        final String replyComments = transactionFeedback.getReplyComments();
        transactionFeedback = referenceDao.load(transactionFeedback.getId());

        // There was a reply already
        if (transactionFeedback.getReplyCommentsDate() != null) {
            return transactionFeedback;
        }

        // Validate
        if (StringUtils.isEmpty(replyComments)) {
            throw new ValidationException("replyComments", "reference.replyComments", new RequiredError());
        }

        // Set the comments
        transactionFeedback.setReplyCommentsDate(Calendar.getInstance());
        transactionFeedback.setReplyComments(replyComments);
        validate(transactionFeedback);
        transactionFeedback = referenceDao.update(transactionFeedback);
        memberNotificationHandler.transactionFeedBackReplyNotification(transactionFeedback);
        return transactionFeedback;
    }

    /*
     * Update the previous reference history log of the reference This methods set the final date of the log to the date of the current reference
     */
    private void updatePreviousReferenceHistoryLog(final Reference reference) {
        final ReferenceHistoryLogQuery query = new ReferenceHistoryLogQuery();
        query.setFrom(reference.getFrom());
        query.setTo(reference.getTo());
        final ReferenceHistoryLog previousLog = referenceHistoryDao.getOpenReferenceHistoryLog(query);
        if (previousLog != null) {
            final Period period = previousLog.getPeriod();
            period.setEnd(reference.getDate());
            previousLog.setPeriod(period);
            referenceHistoryDao.update(previousLog);
        }
    }

}