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

Java tutorial

Introduction

Here is the source code for nl.strohalm.cyclos.services.elements.BrokeringServiceImpl.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.HashSet;
import java.util.List;
import java.util.Set;

import nl.strohalm.cyclos.dao.members.ElementDAO;
import nl.strohalm.cyclos.dao.members.RemarkDAO;
import nl.strohalm.cyclos.dao.members.brokerings.BrokerCommissionContractDAO;
import nl.strohalm.cyclos.dao.members.brokerings.BrokeringCommissionStatusDAO;
import nl.strohalm.cyclos.dao.members.brokerings.BrokeringDAO;
import nl.strohalm.cyclos.entities.Relationship;
import nl.strohalm.cyclos.entities.accounts.fees.transaction.BrokerCommission;
import nl.strohalm.cyclos.entities.accounts.fees.transaction.TransactionFeeQuery;
import nl.strohalm.cyclos.entities.exceptions.UnexpectedEntityException;
import nl.strohalm.cyclos.entities.groups.BrokerGroup;
import nl.strohalm.cyclos.entities.groups.MemberGroup;
import nl.strohalm.cyclos.entities.members.BrokeringQuery;
import nl.strohalm.cyclos.entities.members.BrokeringQuery.Status;
import nl.strohalm.cyclos.entities.members.Element;
import nl.strohalm.cyclos.entities.members.FullTextMemberQuery;
import nl.strohalm.cyclos.entities.members.Member;
import nl.strohalm.cyclos.entities.members.brokerings.BrokerCommissionContract;
import nl.strohalm.cyclos.entities.members.brokerings.BrokerCommissionContractQuery;
import nl.strohalm.cyclos.entities.members.brokerings.Brokering;
import nl.strohalm.cyclos.entities.members.brokerings.BrokeringCommissionStatus;
import nl.strohalm.cyclos.entities.members.brokerings.BrokeringCommissionStatusQuery;
import nl.strohalm.cyclos.entities.members.remarks.BrokerRemark;
import nl.strohalm.cyclos.entities.settings.LocalSettings;
import nl.strohalm.cyclos.services.InitializingService;
import nl.strohalm.cyclos.services.elements.exceptions.MemberAlreadyInBrokeringsException;
import nl.strohalm.cyclos.services.fetch.FetchServiceLocal;
import nl.strohalm.cyclos.services.settings.SettingsServiceLocal;
import nl.strohalm.cyclos.services.transfertypes.TransactionFeeServiceLocal;
import nl.strohalm.cyclos.utils.CacheCleaner;
import nl.strohalm.cyclos.utils.DataIteratorHelper;
import nl.strohalm.cyclos.utils.DateHelper;
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.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.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;

/**
 * Implementation for brokering service
 * @author luis
 */
public class BrokeringServiceImpl implements BrokeringServiceLocal, InitializingService {

    /**
     * Validates a property as being a broker
     * @author luis
     */
    public final class BrokerValidation implements PropertyValidation {
        private static final long serialVersionUID = 580603314020373024L;

        @Override
        public ValidationError validate(final Object object, final Object name, final Object value) {
            final ChangeBrokerDTO dto = (ChangeBrokerDTO) object;
            final Member member = fetchService.fetch((Member) value, Element.Relationships.GROUP);
            if (value == null) {
                return null;
            }
            if (member != null && !member.getMemberGroup().isBroker()) {
                return new InvalidError();
            }
            final MemberGroup viewerGroup = (MemberGroup) member.getGroup();
            final Member brokeredMember = fetchService.fetch(dto.getMember());

            if (!viewerGroup.getCanViewProfileOfGroups().contains(brokeredMember.getGroup())) {
                throw new ValidationException();
            }

            return null;
        }
    }

    private BrokerCommissionContractDAO brokerCommissionContractDao;
    private BrokeringCommissionStatusDAO brokeringCommissionStatusDao;
    private BrokeringDAO brokeringDao;
    private CommissionServiceLocal commissionService;
    private ElementServiceLocal elementService;
    private ElementDAO elementDao;
    private FetchServiceLocal fetchService;
    private RemarkDAO remarkDao;
    private SettingsServiceLocal settingsService;
    private TransactionFeeServiceLocal transactionFeeService;

    private MemberNotificationHandler memberNotificationHandler;

    @Override
    @SuppressWarnings("unchecked")
    public BulkMemberActionResultVO bulkChangeMemberBroker(final FullTextMemberQuery query, Member newBroker,
            final boolean suspendCommission, final String comments) {
        if (newBroker == null || newBroker.isTransient()) {
            throw new ValidationException();
        }
        if (StringUtils.isEmpty(comments)) {
            throw new ValidationException();
        }
        newBroker = fetchService.fetch(newBroker);

        int changed = 0;
        int unchanged = 0;
        // force the result type to ITERATOR to avoid load all members in memory
        query.setIterateAll();
        final List<Member> members = (List<Member>) elementService.fullTextSearch(query);
        CacheCleaner cacheCleaner = new CacheCleaner(fetchService);
        for (final Member member : members) {
            if (newBroker.equals(member.getBroker())) {
                unchanged++;
            } else {
                final ChangeBrokerDTO dto = new ChangeBrokerDTO();
                dto.setMember(member);
                dto.setNewBroker(newBroker);
                dto.setSuspendCommission(suspendCommission);
                dto.setComments(comments);
                changeBroker(dto);
                changed++;

                cacheCleaner.clearCache();
            }
        }
        return new BulkMemberActionResultVO(changed, unchanged);
    }

    @Override
    public Brokering changeBroker(final ChangeBrokerDTO dto) {
        memberNotificationHandler.removedBrokeringNotification(dto);
        return doChangeBroker(dto);
    }

    /**
     * Creates a new brokering
     */
    @Override
    public Brokering create(final Member broker, final Member brokered) {
        Brokering brokering = new Brokering();
        brokering.setBroker(broker);
        brokering.setBrokered(brokered);
        brokering.setStartDate(Calendar.getInstance());
        brokering = brokeringDao.insert(brokering);

        // Create brokering commission status
        createBrokeringCommissionStatus(brokering);

        return brokering;
    }

    @Override
    public Brokering getActiveBrokering(Member member) {
        member = fetchService.fetch(member, Member.Relationships.BROKER);
        if (member.getBroker() == null) {
            return null;
        }

        // Verifies if there is an active broker for the subject
        final BrokeringQuery query = new BrokeringQuery();
        query.fetch(Brokering.Relationships.BROKER);
        query.setBroker(member.getBroker());
        query.setBrokered(member);
        query.setStatus(BrokeringQuery.Status.ACTIVE);
        query.setUniqueResult();
        final List<Brokering> list = search(query);
        if (list != null && !list.isEmpty()) {
            return list.get(0);
        }
        return null;
    }

    @Override
    public Brokering getBrokering(final Member broker, final Member member) throws ValidationException {
        final BrokeringQuery brokeringQuery = new BrokeringQuery();
        brokeringQuery.setBroker(broker);
        brokeringQuery.setBrokered(member);
        brokeringQuery.setStatus(BrokeringQuery.Status.ACTIVE);
        final List<Brokering> brokerings = search(brokeringQuery);
        if (CollectionUtils.isEmpty(brokerings)) {
            throw new ValidationException();
        }
        final Brokering brokering = brokerings.iterator().next();
        return brokering;
    }

    public ElementDAO getElementDao() {
        return elementDao;
    }

    @Override
    public void initializeService() {
        removeExpiredBrokerings(Calendar.getInstance());
    }

    @Override
    public Collection<Status> listPossibleStatuses(BrokerGroup brokerGroup) {
        brokerGroup = fetchService.fetch(brokerGroup, BrokerGroup.Relationships.POSSIBLE_INITIAL_GROUPS);
        boolean hasInactive = false;
        Collection<MemberGroup> possibleInitialGroups = brokerGroup.getPossibleInitialGroups();
        for (MemberGroup group : possibleInitialGroups) {
            if (!group.isActive()) {
                hasInactive = true;
                break;
            }
        }
        return hasInactive ? EnumSet.allOf(BrokeringQuery.Status.class)
                : EnumSet.complementOf(EnumSet.of(BrokeringQuery.Status.PENDING));
    }

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

    @Override
    public Brokering remove(final Brokering brokering, final String remark) throws UnexpectedEntityException {
        if (brokering == null || brokering.isTransient() || brokering.getEndDate() != null) {
            throw new UnexpectedEntityException();
        }

        final ChangeBrokerDTO dto = new ChangeBrokerDTO();
        dto.setComments(remark);
        dto.setMember(brokering.getBrokered());
        dto.setNewBroker(null);
        return doChangeBroker(dto);
    }

    @Override
    public void removeExpiredBrokerings(final Calendar time) {
        final LocalSettings localSettings = settingsService.getLocalSettings();
        final TimePeriod brokeringExpirationPeriod = localSettings.getBrokeringExpirationPeriod();
        if (brokeringExpirationPeriod == null || brokeringExpirationPeriod.getNumber() <= 0) {
            return;
        }

        final Calendar startDate = brokeringExpirationPeriod.remove(DateHelper.truncate(time));
        final BrokeringQuery query = new BrokeringQuery();
        query.setResultType(ResultType.ITERATOR);
        query.setStatus(BrokeringQuery.Status.ACTIVE);
        query.setStartExpirationDate(startDate);
        CacheCleaner cleaner = new CacheCleaner(fetchService);
        final List<Brokering> expired = search(query);
        try {
            for (final Brokering brokering : expired) {
                // Update the brokering, expiring it
                brokering.setEndDate(time);
                brokeringDao.update(brokering);
                memberNotificationHandler.expiredBrokeringNotification(brokering);
                cleaner.clearCache();
            }
        } finally {
            DataIteratorHelper.close(expired);
        }
    }

    @Override
    public List<Brokering> search(final BrokeringQuery query) {
        return brokeringDao.search(query);
    }

    public void setBrokerCommissionContractDao(final BrokerCommissionContractDAO brokerCommissionContractDao) {
        this.brokerCommissionContractDao = brokerCommissionContractDao;
    }

    public void setBrokeringCommissionStatusDao(final BrokeringCommissionStatusDAO brokeringCommissionStatusDao) {
        this.brokeringCommissionStatusDao = brokeringCommissionStatusDao;
    }

    public void setBrokeringDao(final BrokeringDAO brokeringDAO) {
        brokeringDao = brokeringDAO;
    }

    public void setCommissionServiceLocal(final CommissionServiceLocal commissionService) {
        this.commissionService = commissionService;
    }

    public void setElementDao(final ElementDAO elementDao) {
        this.elementDao = elementDao;
    }

    public void setElementServiceLocal(final ElementServiceLocal elementService) {
        this.elementService = elementService;
    }

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

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

    public void setRemarkDao(final RemarkDAO remarkDao) {
        this.remarkDao = remarkDao;
    }

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

    public void setTransactionFeeServiceLocal(final TransactionFeeServiceLocal transactionFeeService) {
        this.transactionFeeService = transactionFeeService;
    }

    @Override
    public void validate(final Brokering brokering) throws ValidationException {
        getBrokeringValidator().validate(brokering);
    }

    @Override
    public void validate(final ChangeBrokerDTO dto) throws ValidationException {
        getDtoValidator().validate(dto);
    }

    private void closeBrokerContractsAndBrokeringStatus(final Brokering brokering) {
        // Close active broker commission contracts
        final BrokerCommissionContractQuery bccQuery = new BrokerCommissionContractQuery();
        bccQuery.setBroker(brokering.getBroker());
        bccQuery.setMember(brokering.getBrokered());
        bccQuery.setStatus(BrokerCommissionContract.Status.ACTIVE);
        final List<BrokerCommissionContract> brokerCommissionContracts = brokerCommissionContractDao
                .search(bccQuery);
        for (final BrokerCommissionContract brokerCommissionContract : brokerCommissionContracts) {
            brokerCommissionContract.setStatus(BrokerCommissionContract.Status.CLOSED);
            brokerCommissionContractDao.update(brokerCommissionContract);
        }

        // Close active brokering commission status
        final Calendar now = Calendar.getInstance();
        final BrokeringCommissionStatusQuery bcsQuery = new BrokeringCommissionStatusQuery();
        bcsQuery.setBrokering(brokering);
        bcsQuery.setOnlyActive(true);
        final List<BrokeringCommissionStatus> brokeringCommissionStatusList = brokeringCommissionStatusDao
                .search(bcsQuery);
        for (final BrokeringCommissionStatus brokeringCommissionStatus : brokeringCommissionStatusList) {
            brokeringCommissionStatus.getPeriod().setEnd(now);
            brokeringCommissionStatusDao.update(brokeringCommissionStatus);
        }

    }

    /*
     * Creates a new brokering commission status for each broker commission and the specified brokering. This method is called by doChangeBroker()
     */
    @SuppressWarnings("unchecked")
    private void createBrokeringCommissionStatus(final Brokering brokering) {
        final Member broker = fetchService.fetch(brokering.getBroker(), Element.Relationships.GROUP);
        final BrokerGroup brokerGroup = (BrokerGroup) broker.getGroup();

        // Create a brokering commission status for each broker commission applicable to the broker
        final TransactionFeeQuery query = new TransactionFeeQuery();
        query.setEntityType(BrokerCommission.class);
        query.setBrokerGroup(brokerGroup);
        query.setReturnDisabled(true);
        final List<BrokerCommission> brokerCommissions = (List<BrokerCommission>) transactionFeeService
                .search(query);
        for (final BrokerCommission brokerCommission : brokerCommissions) {
            commissionService.createBrokeringCommissionStatus(brokering, brokerCommission);
        }
    }

    private Brokering doChangeBroker(final ChangeBrokerDTO dto) {
        validate(dto);
        final Calendar now = Calendar.getInstance();
        final Member member = fetchService.reload(dto.getMember(), Element.Relationships.USER);
        final Brokering oldBrokering = getActiveBrokering(member);
        final Member oldBroker = (oldBrokering == null) ? null : oldBrokering.getBroker();
        final Member newBroker = fetchService.fetch(dto.getNewBroker(), Member.Relationships.BROKER);

        // Check if it's just to suspend commission
        final boolean justSuspendCommission = (oldBroker != null && oldBroker.equals(newBroker)
                && dto.isSuspendCommission());

        if (!justSuspendCommission) {

            // Check for circular brokerings
            if (member.equals(newBroker)) {
                throw new ValidationException("brokering.error.circularBrokering");
            }
            Member current = newBroker;
            final Set<Member> visited = new HashSet<Member>();
            while (current != null) {
                if (visited.contains(current)) {
                    throw new ValidationException("brokering.error.circularBrokering");
                }
                visited.add(current);
                current = fetchService.fetch(current, Member.Relationships.BROKER).getBroker();
            }

            // Check if the member is already on the brokering list
            if (newBroker != null) {
                final BrokeringQuery query = new BrokeringQuery();
                query.setBroker(newBroker);
                query.setBrokered(member);
                query.setStatus(BrokeringQuery.Status.ACTIVE);
                if (!search(query).isEmpty()) {
                    throw new MemberAlreadyInBrokeringsException();
                }
            }

            // Update the brokered member
            member.setBroker(newBroker);
            elementDao.update(member, false);

            // Reindex the element
            elementDao.addToIndex(member);
        }

        // Update the active brokering
        if (oldBrokering != null) {
            if (!justSuspendCommission) {
                oldBrokering.setEndDate(now);
            }
            brokeringDao.update(oldBrokering, false);

            // Close broker commission contracts and brokering commission status
            closeBrokerContractsAndBrokeringStatus(oldBrokering);
        }

        // Build a brokering to return
        Brokering brokering;
        if (justSuspendCommission) {
            // If just suspending commission, the result will be the same brokering
            brokering = oldBrokering;
        } else if (newBroker != null) {
            // Create a new brokering if not just suspending commission
            brokering = new Brokering();
            brokering.setBroker(newBroker);
            brokering.setBrokered(member);
            brokering.setStartDate(now);
            brokering = brokeringDao.insert(brokering, false);
            createBrokeringCommissionStatus(brokering);
        } else {
            // No new broker - return null
            brokering = null;
        }

        // Create the broker remark
        final BrokerRemark remark = new BrokerRemark();
        remark.setWriter(LoggedUser.element());
        remark.setSubject(member);
        remark.setDate(now);
        remark.setComments(dto.getComments());
        remark.setOldBroker(oldBroker);
        remark.setNewBroker(newBroker);
        remark.setSuspendCommission(dto.isSuspendCommission());
        remarkDao.insert(remark);

        return brokering;
    }

    private Validator getBrokeringValidator() {
        final Validator brokeringValidator = new Validator();
        brokeringValidator.property("broker").key("member.broker").required().add(new BrokerValidation());
        brokeringValidator.property("brokered").key("member.member").required();
        brokeringValidator.property("notes").key("brokering.notes").maxLength(1000);
        return brokeringValidator;
    }

    private Validator getDtoValidator() {
        final Validator dtoValidator = new Validator();
        dtoValidator.property("member").required();
        dtoValidator.property("newBroker").key("changeBroker.new").add(new BrokerValidation())
                .add(new PropertyValidation() {
                    private static final long serialVersionUID = 580603314020373024L;

                    @Override
                    public ValidationError validate(final Object object, final Object name, final Object value) {
                        final ChangeBrokerDTO dto = (ChangeBrokerDTO) object;
                        final Member newBroker = (Member) value;
                        if (dto.isSuspendCommission()) {
                            // When commission is suspended, there must be a new broker
                            if (newBroker == null || newBroker.isTransient()) {
                                return new RequiredError();
                            }
                        } else {
                            // The old and new brokers can be the same only when suspending commission
                            final Member member = fetchService.fetch(dto.getMember());
                            if (member != null && newBroker != null && newBroker.equals(member.getBroker())) {
                                return new InvalidError();
                            }
                        }
                        return null;
                    }
                });
        dtoValidator.property("comments").key("remark.comments").required().maxLength(4000);
        return dtoValidator;
    }
}