AbstractFEC.java

package com.tradecloud.domain.model.fec;

import com.tradecloud.common.base.HibernateUtils;
import com.tradecloud.common.base.PersistenceBase;
import com.tradecloud.domain.event.DealsEvent;
import com.tradecloud.domain.exception.ConfirmationException;
import com.tradecloud.domain.helper.XMLHelper;
import com.tradecloud.domain.model.ActualExposure;
import com.tradecloud.domain.model.ForexGroup;
import com.tradecloud.domain.model.Money;
import com.tradecloud.domain.model.audit.AuditEvent;
import com.tradecloud.domain.model.organisationalunit.OrganisationalUnit;
import com.tradecloud.domain.treasury.TreasuryBank;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.Type;
import org.joda.time.LocalDate;

import javax.persistence.*;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import java.math.BigDecimal;
import java.util.*;

@MappedSuperclass
@Table(name = "fec")
public abstract class AbstractFEC extends PersistenceBase implements IFEC, ActualExposure {

    private static final long serialVersionUID = 1L;

    @Embedded
    protected BankReference bankReference;
    @Embedded
    protected InternalReference internalReference;
    @Embedded
    @AttributeOverrides({@AttributeOverride(name = "currency", column = @Column(name = "amount_currency")),
            @AttributeOverride(name = "value", column = @Column(name = "amount_value", precision = 19, scale = 6))})
    protected Money amount;
    @Embedded
    @AttributeOverrides({@AttributeOverride(name = "currency", column = @Column(name = "unlinked_amount_currency")),
            @AttributeOverride(name = "value", column = @Column(name = "unlinked_amount_value", precision = 19, scale = 6))})
    protected Money unlinkedAmount;

    @Column(name = "maturity_date")
    @Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDate")
    protected LocalDate maturityDate;
    @Embedded
    protected BankDetails bankDetails;

    @Embedded
    @AttributeOverrides({@AttributeOverride(name = "currency", column = @Column(name = "spot_rate_currency")),
            @AttributeOverride(name = "value", column = @Column(name = "spot_rate_value", precision = 19, scale = 6))})
    protected Money spotRate;

    @Embedded
    @AttributeOverrides({@AttributeOverride(name = "currency", column = @Column(name = "forward_rate_currency")),
            @AttributeOverride(name = "value", column = @Column(name = "forward_rate_value", precision = 19, scale = 6))})
    protected Money forwardRate;

    @Embedded
    @AttributeOverrides({@AttributeOverride(name = "currency", column = @Column(name = "costing_rate_currency")),
            @AttributeOverride(name = "value", column = @Column(name = "costing_rate_value", precision = 19, scale = 6))})
    protected Money costingRate;

    protected boolean confirmed;

    protected Date confirmedDateTime;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "parent_fec")
    protected FEC parent;

    @OneToMany(mappedBy = "parent", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @OrderBy("confirmedDateTime desc")
    @Fetch(FetchMode.SUBSELECT)
    private List<FEC> fecLegs = new ArrayList<>();

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "extensionPreDeliverPair_id")
    protected FEC extensionPreDeliverPair;

    @Enumerated(value = EnumType.STRING)
    @Column(name = "type", nullable = false, insertable = false, updatable = false)
    // this will be readonly as it is declared as a @DiscriminatorColumn in FEC
    protected FECType type;

    @Column(name = "deal_date")
    @Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDate")
    protected LocalDate dealDate;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "organisationalunit_id")
    protected OrganisationalUnit organisationalUnit;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "forexgroup_id")
    protected ForexGroup forexGroup;

    //@Enumerated(EnumType.STRING)
    @Type(type = "com.tradecloud.domain.infrastructure.persistence.userTypes.FECTransactionTypeUserType")
    protected FECTransactionType fecTransactionType;


    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @Fetch(value = FetchMode.SUBSELECT)
    @XmlElementWrapper(name = "DealsEvents")
    @XmlElement(name = "DealsEvent")
    @OrderBy("createDateTime")
    protected List<DealsEvent> events = new LinkedList<DealsEvent>();

    @Override
    public String getStateAsXML() {
        StringBuilder builder = new StringBuilder();
        builder.append("<fec>");
        // builder.append("<ForexGroup>").append(forexGroup == null ? "" : forexGroup.getName()).append("</ForexGroup>");
        builder.append("<ForexGroup>").append(XMLHelper.getForexGroupXML(forexGroup)).append("</ForexGroup>");
        builder.append("<fecReference>").append(getId()).append("</fecReference>");
        builder.append("<bankReference>").append((bankReference == null) ? "" : XMLHelper.encodeToXMLString(bankReference.getReference()))
                .append("</bankReference>");
        builder.append("<internalReference>")
                .append((internalReference == null) ? "" : XMLHelper.encodeToXMLString(internalReference.getReference()))
                .append("</internalReference>");
        builder.append("<maturityDate>").append(maturityDate).append("</maturityDate>");
        builder.append("<dealDate>").append(dealDate == null ? LocalDate.now() : dealDate).append("</dealDate>");
        builder.append("<amount>").append(XMLHelper.getMoneyXML(amount)).append("</amount>");
        builder.append("<unlinkedAmount>").append(XMLHelper.getMoneyXML(unlinkedAmount)).append("</unlinkedAmount>");
        builder.append("<localCurrencyAmount>").append(XMLHelper.getLocalCurrencyXML(forwardRate.getValue(), amount.getValue()))
                .append("</localCurrencyAmount>");
        builder.append("<spotRate>").append(XMLHelper.getMoneyXML(spotRate)).append("</spotRate>");
        builder.append("<forwardRate>").append(XMLHelper.getMoneyXML(forwardRate)).append("</forwardRate>");
        builder.append("<bankDetails>").append(XMLHelper.getBankDetailsXML(bankDetails)).append("</bankDetails>");
        builder.append("<confirmed>").append(confirmed).append("</confirmed>");
        builder.append("<matured>").append(isMatured()).append("</matured>");
        builder.append("<linkedAmount>").append(XMLHelper.getMoneyXML(calculateLinkedAmount())).append("</linkedAmount>");
        builder.append("<status>").append(XMLHelper.encodeToXMLString(status().getStatusName())).append("</status>");
        builder.append("<division>").append(XMLHelper.getOrganisationalUnitXML(organisationalUnit)).append("</division>");
        builder.append("<fecTransactionType>").append((getFecTransactionType() == null) ? "" :
                XMLHelper.encodeToXMLString(getFecTransactionType().getName())).append("</fecTransactionType>");
        builder.append("</fec>");
        return builder.toString();
    }

    public String getStateAsXMLWithStringFields() {
        StringBuilder builder = new StringBuilder();
        builder.append("<fec>");
        builder.append("<ForexGroup>").append(XMLHelper.getForexGroupXML(forexGroup)).append("</ForexGroup>");
        builder.append("<fecReference>").append(getId()).append("</fecReference>");
        builder.append("<bankReference>").append((bankReference == null) ? "" : XMLHelper.encodeToXMLString(bankReference.getReference()))
                .append("</bankReference>");
        builder.append("<internalReference>")
                .append((internalReference == null) ? "" : XMLHelper.encodeToXMLString(internalReference.getReference()))
                .append("</internalReference>");
        builder.append("<maturityDate>").append(maturityDate).append("</maturityDate>");
        builder.append("<dealDate>").append(dealDate == null ? LocalDate.now() : dealDate).append("</dealDate>");
        builder.append("<amount>").append(XMLHelper.getMoneyXML(amount, 2)).append("</amount>");
        builder.append("<unlinkedAmount>").append(XMLHelper.getMoneyXML(unlinkedAmount)).append("</unlinkedAmount>");
        builder.append("<localCurrencyAmount>").append(XMLHelper.getLocalCurrencyXML(forwardRate.getValue(), amount.getValue()))
                .append("</localCurrencyAmount>");
        builder.append("<spotRate>").append(XMLHelper.getMoneyXML(spotRate, 4)).append("</spotRate>");
        builder.append("<forwardRate>").append(XMLHelper.getMoneyXML(forwardRate, 4)).append("</forwardRate>");
        builder.append("<bankDetails>").append(XMLHelper.getBankDetailsXML(bankDetails)).append("</bankDetails>");
        builder.append("<confirmed>").append(confirmed).append("</confirmed>");
        builder.append("<matured>").append(isMatured()).append("</matured>");
        builder.append("<linkedAmount>").append(XMLHelper.getMoneyXML(calculateLinkedAmount())).append("</linkedAmount>");
        builder.append("<status>").append(XMLHelper.encodeToXMLString(status().getStatusName())).append("</status>");
        builder.append("<division>").append(XMLHelper.getOrganisationalUnitXML(organisationalUnit)).append("</division>");
        builder.append("<fecTransactionType>").append((getFecTransactionType() == null) ? "" :
                XMLHelper.encodeToXMLString(getFecTransactionType().getName())).append("</fecTransactionType>");
        builder.append("</fec>");
        return builder.toString();
    }

    @Override
    public Money calculateLinkedAmount() {
        if (amount != null && unlinkedAmount != null) {
            return amount != null ? amount.subtract(unlinkedAmount) : new Money(BigDecimal.ZERO, amount.getCurrency());
        }
        return null;
    }

    @Override
    public void checkConfirmation(boolean appendFecTypeToMessage, Confirmation confirmation) {
        String fecType = "";
        if (appendFecTypeToMessage) {
            fecType = confirmation.getConfirmationType().getDescription() + " ";
        }
        validate(confirmation.getForwardRate().getValue(), this.forwardRate.getValue(), "Confirmation failure : " + fecType
                + "Forward Rate value mismatch.");
        validate(confirmation.getForwardRate().getCurrency(), this.forwardRate.getCurrency(), "Confirmation failure : " + fecType
                + "Forward Rate currency mismatch.");
        validate(confirmation.getSpotRate().getValue(), this.spotRate.getValue(), "Confirmation failure : " + fecType + "Spot Rate value mismatch.");
        validate(confirmation.getSpotRate().getCurrency(), this.spotRate.getCurrency(), "Confirmation failure : " + fecType
                + "Spot Rate currency mismatch.");
        validate(confirmation.getAmount().getValue(), this.amount.getValue(), "Confirmation failure : " + fecType + "Amount value mismatch.");
        validate(confirmation.getAmount().getCurrency(), this.amount.getCurrency(), "Confirmation failure : " + fecType
                + "Amount currency mismatch.");
        validate(confirmation.getMaturityDate(), this.maturityDate, "Confirmation failure : " + fecType + "Maturity Date mismatch.");
        if (confirmation.getOrganisationalUnit() != null) {
            validate(confirmation.getOrganisationalUnit().getCode(), this.organisationalUnit.getCode(), "Confirmation failure : " + fecType
                    + " OrganisationalUnit mismatch.");
        }
        if (confirmation.getForexGroup() != null) {
            validate(confirmation.getForexGroup(), this.forexGroup, "Confirmation failure : " + fecType + "forex group mismatch.");
        }
        if (this.forwardRate.getValue().compareTo(this.spotRate.getValue()) < 0) {
            throw new ConfirmationException("Confirmation failure: the Forward Rate (" + forwardRate + ") " + "may not be less than the Spot Rate ("
                    + spotRate + ")");
        }
        if (this.forwardRate.getValue().compareTo(BigDecimal.ZERO) == 0) {
            throw new ConfirmationException("Confirmation failure: Forward Rate may not be zero");
        }
        if (this.spotRate.getValue().compareTo(BigDecimal.ZERO) == 0) {
            throw new ConfirmationException("Confirmation failure: Spot Rate may not be zero");
        }
    }

    /**
     * Override if necessary.
     *
     * @return
     */
    public boolean isMatured() {
        return (confirmed && (!maturityDate.isAfter(new LocalDate())));
    }

    @Override
    public FECStatusType status() {
        if (!confirmed) {
            return FECStatusType.ESTABLISHED;
        } else if (!maturityDate.isAfter(new LocalDate())) {
            return FECStatusType.MATURED;
        } else {
            return FECStatusType.CONFIRMED;
        }
    }

    public FECStatusType getStatus() {
        return status();
    }

    private void validate(Object first, Object second, String message) {
        if (!first.equals(second)) {
            throw new ConfirmationException(message);
        }
    }

    @Override
    public StringBuilder getAuditEventsAsXML(List<AuditEvent> events) {
        StringBuilder builder = new StringBuilder();
        builder.append("<stateHistory>");
        builder.append(getStateAsXML());
        builder.append("<auditEvents>");
        for (AuditEvent event : events) {
            builder.append(event.getStateAsXML());
        }
        builder.append("</auditEvents>");
        builder.append("</stateHistory>");
        return builder;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (!HibernateUtils.proxyClassEquals(this, obj)) {
            return false;
        }
        final AbstractFEC other = (AbstractFEC) obj;
        return new EqualsBuilder().append(this.bankReference, other.getBankReference()).isEquals();
    }

    @Override
    public int hashCode() {
        return new HashCodeBuilder().append(this.bankReference).toHashCode();
    }

    @Override
    public Money getAmount() {
        return amount;
    }

    @Override
    public OrganisationalUnit getDivision() {
        return organisationalUnit;
    }

    @Override
    public void setDivision(OrganisationalUnit organisationalUnit) {
        this.organisationalUnit = organisationalUnit;
    }

    @Override
    public OrganisationalUnit getOrganisationalUnit() {
        return organisationalUnit;
    }

    public void setOrganisationalUnit(OrganisationalUnit organisationalUnit) {
        this.organisationalUnit = organisationalUnit;
    }

    @Override
    public ForexGroup getForexGroup() {
        return forexGroup;
    }

    @Override
    public void setForexGroup(ForexGroup forexGroup) {
        this.forexGroup = forexGroup;
    }

    @Override
    public BankReference getBankReference() {
        return bankReference;
    }

    public void setBankReference(BankReference bankReference) {
        this.bankReference = bankReference;
    }

    @Override
    public InternalReference getInternalReference() {
        return internalReference;
    }

    public void setInternalReference(InternalReference internalReference) {
        this.internalReference = internalReference;
    }

    @Override
    public Money getUnlinkedAmount() {
        return unlinkedAmount;
    }

    @Override
    public void setUnlinkedAmount(Money unlinkedAmount) {
        this.unlinkedAmount = unlinkedAmount;
    }

    @Override
    public LocalDate getMaturityDate() {
        return maturityDate;
    }

    public void setMaturityDate(LocalDate maturityDate) {
        this.maturityDate = maturityDate;
    }

    @Override
    public BankDetails getBankDetails() {
        return bankDetails;
    }

    public void setBankDetails(BankDetails bankDetails) {
        this.bankDetails = bankDetails;
    }

    @Override
    public Money getSpotRate() {
        return spotRate;
    }

    public void setSpotRate(Money spotRate) {
        this.spotRate = spotRate;
    }

    @Override
    public Money getForwardRate() {
        return forwardRate;
    }

    public void setForwardRate(Money forwardRate) {
        this.forwardRate = forwardRate;
    }

    @Override
    public boolean isConfirmed() {
        return confirmed;
    }

    @Override
    public FEC getParent() {
        return parent;
    }

    @Override
    public void setParent(FEC parent) {
        this.parent = parent;
        parent.addLeg((FEC) this);
    }

    @Override
    public void addLeg(FEC ifec) {
        fecLegs.add(ifec);
    }

    public FECType getType() {
        return type;
    }

    public void setType(FECType type) {
        this.type = type;
    }

    @Override
    public LocalDate getDealDate() {
        return dealDate;
    }

    public void setDealDate(LocalDate dealDate) {
        this.dealDate = dealDate;
    }

    public void setAmount(Money amount) {
        this.amount = amount;
    }

    public FECTransactionType getFecTransactionType() {
        return fecTransactionType;
    }

    public void setFecTransactionType(FECTransactionType fecTransactionType) {
        this.fecTransactionType = fecTransactionType;
    }

    @Override
    public void unConfirm() {
        confirmed = false;
        confirmedDateTime = null;
    }

    @Override
    public Currency getCurrency() {
        return amount.getCurrency();
    }

    @Override
    public Money getCostingRate() {
        return costingRate;
    }

    @Override
    public void setCostingRate(Money costingRate) {
        this.costingRate = costingRate;
    }

    public FEC getExtensionPreDeliverPair() {
        return extensionPreDeliverPair;
    }

    public void setExtensionPreDeliverPair(FEC extensionPreDeliverPair) {
        this.extensionPreDeliverPair = extensionPreDeliverPair;
    }

    public Date getConfirmedDateTime() {
        return confirmedDateTime;
    }

    public List<FEC> getFecLegs() {
        return fecLegs;
    }

    public List<DealsEvent> getEvents() {
        return events;
    }

    public void setEvents(List<DealsEvent> events) {
        this.events = events;
    }

    @Override
    public TreasuryBank getBank() {
        return bankDetails.getTreasuryBank();
    }

    @Override
    public void updateCurrency(com.tradecloud.domain.common.Currency dtoCurrency) {
        this.amount = Money.valueOf(this.amount.getValue(), Currency.getInstance(dtoCurrency.getCode()));
        this.unlinkedAmount = Money.valueOf(this.unlinkedAmount.getValue(), Currency.getInstance(dtoCurrency.getCode()));
    }
}