/* 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; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import nl.strohalm.cyclos.dao.accounts.ARateParametersDAO; import nl.strohalm.cyclos.dao.accounts.CurrencyDAO; import nl.strohalm.cyclos.dao.accounts.DRateParametersDAO; import nl.strohalm.cyclos.dao.accounts.IRateParametersDAO; import nl.strohalm.cyclos.dao.accounts.RateParametersDAO; import nl.strohalm.cyclos.entities.accounts.ARateParameters; import nl.strohalm.cyclos.entities.accounts.Account; import nl.strohalm.cyclos.entities.accounts.AccountType; import nl.strohalm.cyclos.entities.accounts.Currency; import nl.strohalm.cyclos.entities.accounts.DRateParameters; import nl.strohalm.cyclos.entities.accounts.IRateParameters; import nl.strohalm.cyclos.entities.accounts.InitializableRateParameters; import nl.strohalm.cyclos.entities.accounts.RateParameters; import nl.strohalm.cyclos.entities.exceptions.EntityNotFoundException; import nl.strohalm.cyclos.entities.groups.MemberGroup; import nl.strohalm.cyclos.entities.members.Member; import nl.strohalm.cyclos.entities.settings.LocalSettings; import nl.strohalm.cyclos.services.accounts.rates.RateServiceLocal; import nl.strohalm.cyclos.services.accounts.rates.ReinitializeRatesDTO; import nl.strohalm.cyclos.services.accounts.rates.WhatRate; import nl.strohalm.cyclos.services.fetch.FetchServiceLocal; import nl.strohalm.cyclos.services.settings.SettingsServiceLocal; import nl.strohalm.cyclos.utils.ClassHelper; import nl.strohalm.cyclos.utils.RelationshipHelper; import nl.strohalm.cyclos.utils.cache.Cache; import nl.strohalm.cyclos.utils.cache.CacheCallback; import nl.strohalm.cyclos.utils.cache.CacheManager; import nl.strohalm.cyclos.utils.validation.GeneralValidation; import nl.strohalm.cyclos.utils.validation.PropertyValidation; import nl.strohalm.cyclos.utils.validation.ValidationError; import nl.strohalm.cyclos.utils.validation.Validator; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.builder.EqualsBuilder; /** * Service implementation for currencies. * * @author luis * @author Rinke */ public class CurrencyServiceImpl implements CurrencyServiceLocal { /** * handles the saving of RateParameters * @author rinke * @param <R> the subType of RateParameters to be saved (so: ARateParametes, DRateParameters or IRateParameters) */ private class RateParameterSaver<R extends RateParameters> { private final Class<R> rateParametersClass; @SuppressWarnings("rawtypes") private RateParametersDAO dao; private RateParameterSaver(final Class<R> rateParametersClass) { this.rateParametersClass = rateParametersClass; if (rateParametersClass.equals(ARateParameters.class)) { dao = aRateParametersDao; } if (rateParametersClass.equals(DRateParameters.class)) { dao = dRateParametersDao; } // rates if (rateParametersClass.equals(IRateParameters.class)) { dao = iRateParametersDao; } } @SuppressWarnings("unchecked") private void disableOldRate(final Currency oldCurrency, final Currency newCurrency, final Situation situation) { if (situation == Situation.DISABLED || situation == Situation.CHANGED) { R oldRate = getRateParameters(oldCurrency); oldRate.setDisabledSince(Calendar.getInstance()); dao.update(oldRate); if (situation == Situation.DISABLED) { this.setRateParameters(newCurrency, null); } } } @SuppressWarnings("unchecked") private R getRateParameters(final Currency currency) { if (rateParametersClass.equals(ARateParameters.class)) { return (R) currency.getaRateParameters(); } if (rateParametersClass.equals(DRateParameters.class)) { return (R) currency.getdRateParameters(); } if (rateParametersClass.equals(IRateParameters.class)) { return (R) currency.getiRateParameters(); } return null; } private Situation getSituation(final boolean enabled, final Currency currency, final Currency old) { if (currency.isTransient()) { if (enabled) { return Situation.NEW; } return Situation.UNCHANGED; } R oldRate = getRateParameters(old); R newRate = (enabled) ? this.getRateParameters(currency) : null; return this.hasChanged(oldRate, newRate); } private Situation hasChanged(final R oldRate, final R newRate) { if (newRate != null && oldRate == null) { return Situation.NEW; } else if (newRate == null && oldRate != null) { return Situation.DISABLED; } else if (newRate != null && oldRate != null && ClassHelper.isInstance(InitializableRateParameters.class, newRate) && ClassHelper.isInstance(InitializableRateParameters.class, oldRate)) { final LocalSettings localSettings = settingsService.getLocalSettings(); final EqualsBuilder eb = new EqualsBuilder(); InitializableRateParameters oldInitializableRate = (InitializableRateParameters) oldRate; InitializableRateParameters newInitializableRate = (InitializableRateParameters) newRate; eb.append(localSettings.round(newInitializableRate.getInitValue()), localSettings.round(oldInitializableRate.getInitValue())); eb.append(newInitializableRate.getInitDate(), oldInitializableRate.getInitDate()); eb.append(localSettings.round(newRate.getCreationValue()), localSettings.round(oldRate.getCreationValue())); if (oldRate instanceof DRateParameters && newRate instanceof DRateParameters) { final DRateParameters oldDRate = (DRateParameters) oldRate; final DRateParameters newDRate = (DRateParameters) newRate; eb.append(localSettings.roundHighPrecision(newDRate.getInterest()), localSettings.roundHighPrecision(oldDRate.getInterest())); eb.append(localSettings.round(newDRate.getBaseMalus()), localSettings.round(oldDRate.getBaseMalus())); eb.append(localSettings.round(newDRate.getMinimalD()), localSettings.round(oldDRate.getMinimalD())); } return (eb.isEquals()) ? Situation.UNCHANGED : Situation.CHANGED; } return Situation.UNCHANGED; } private void setRateParameters(final Currency currency, final R rateParameters) { if (rateParametersClass.equals(ARateParameters.class)) { currency.setaRateParameters((ARateParameters) rateParameters); } if (rateParametersClass.equals(DRateParameters.class)) { currency.setdRateParameters((DRateParameters) rateParameters); } if (rateParametersClass.equals(IRateParameters.class)) { currency.setiRateParameters((IRateParameters) rateParameters); } } @SuppressWarnings("unchecked") private void storeNewRate(final Situation situation, final Currency currency) { if (situation == Situation.NEW || situation == Situation.CHANGED) { R rate = this.getRateParameters(currency); if (situation == Situation.CHANGED) { rate = (R) rate.clone(); } Calendar now = Calendar.getInstance(); rate.setEnabledSince(now); rate.setDate(now); rate.setCurrency(currency); rate.setId(null); rate.setDisabledSince(null); rate = (R) dao.insert(rate); setRateParameters(currency, rate); } } } /** * the situation for any rateParameter. * @author rinke */ private enum Situation { DISABLED, NEW, CHANGED, UNCHANGED } private static final String ALL_KEY = "_ALL_"; private CurrencyDAO currencyDao; private ARateParametersDAO aRateParametersDao; private DRateParametersDAO dRateParametersDao; private IRateParametersDAO iRateParametersDao; private RateServiceLocal rateService; private FetchServiceLocal fetchService; private SettingsServiceLocal settingsService; private AccountServiceLocal accountService; private CacheManager cacheManager; @Override public List<Currency> listAll() { return getCache().get(ALL_KEY, new CacheCallback() { @Override public Object retrieve() { return currencyDao.listAll(Currency.Relationships.A_RATE_PARAMETERS, Currency.Relationships.D_RATE_PARAMETERS, Currency.Relationships.I_RATE_PARAMETERS); } }); } @Override public List<Currency> listByMember(final Member member) { final List<Currency> currencies = new ArrayList<Currency>(); final List<? extends Account> accounts = accountService.getAccounts(member, RelationshipHelper.nested(Account.Relationships.TYPE, AccountType.Relationships.CURRENCY)); for (final Account account : accounts) { final Currency currency = account.getType().getCurrency(); if (!currencies.contains(currency)) { currencies.add(currency); } } return currencies; } @Override public List<Currency> listByMemberGroup(final MemberGroup group) { List<Currency> currencies = currencyDao.listByMemberGroup(group); if (CollectionUtils.isEmpty(currencies)) { currencies = currencyDao.listAll(); } return currencies; } @Override public List<Currency> listDRatedCurrencies() { final List<Currency> currencies = currencyDao.listAll(); final List<Currency> ratedCurrencies = new ArrayList<Currency>(currencies.size()); for (final Currency currency : currencies) { if (currency.isEnableDRate()) { ratedCurrencies.add(currency); } } return ratedCurrencies; } @Override public Currency load(final Long id) { return getCache().get(id, new CacheCallback() { @Override public Object retrieve() { return currencyDao.load(id, Currency.Relationships.A_RATE_PARAMETERS, Currency.Relationships.D_RATE_PARAMETERS); } }); } @Override public Currency loadBySymbolOrId(final String symbolOrId) { return getCache().get(symbolOrId, new CacheCallback() { @Override public Object retrieve() { Long id; try { id = Long.parseLong(symbolOrId); } catch (final Exception e) { id = null; } if (id != null) { // Try by id try { return currencyDao.load(id, Currency.Relationships.A_RATE_PARAMETERS, Currency.Relationships.D_RATE_PARAMETERS); } catch (EntityNotFoundException e) { // Ignore. Will try by symbol } } // Try by symbol return currencyDao.loadBySymbol(symbolOrId, Currency.Relationships.A_RATE_PARAMETERS, Currency.Relationships.D_RATE_PARAMETERS); } }); } @Override public int remove(final Long... ids) { getCache().clear(); return currencyDao.delete(ids); } @Override public Currency save(Currency currency, final WhatRate whatRate) { RateParameterSaver<ARateParameters> aRateSaver = new RateParameterSaver<ARateParameters>(ARateParameters.class); RateParameterSaver<DRateParameters> dRateSaver = new RateParameterSaver<DRateParameters>(DRateParameters.class); RateParameterSaver<IRateParameters> iRateSaver = new RateParameterSaver<IRateParameters>(IRateParameters.class); Currency old = null; if (currency.isPersistent()) { old = currencyDao.load(currency.getId(), Currency.Relationships.A_RATE_PARAMETERS, Currency.Relationships.D_RATE_PARAMETERS, Currency.Relationships.I_RATE_PARAMETERS); } Situation aRateSituation = aRateSaver.getSituation(whatRate.isaRate(), currency, old); Situation dRateSituation = dRateSaver.getSituation(whatRate.isdRate(), currency, old); Situation iRateSituation = iRateSaver.getSituation(whatRate.isiRate(), currency, old); validate(currency, aRateSituation, dRateSituation, iRateSituation); try { if (!whatRate.isaRate()) { currency.setaRateParameters(null); } if (!whatRate.isdRate()) { currency.setdRateParameters(null); } if (!whatRate.isiRate()) { currency.setiRateParameters(null); } if (currency.isTransient()) { ARateParameters aRate = currency.getaRateParameters(); currency.setaRateParameters(null); DRateParameters dRate = currency.getdRateParameters(); currency.setdRateParameters(null); IRateParameters iRate = currency.getiRateParameters(); currency.setiRateParameters(null); currency = currencyDao.insert(currency, true); // Each storeNewRate() must be invoked before setting other rates, or there will be a TransientObjectException currency.setaRateParameters(aRate); aRateSaver.storeNewRate(aRateSituation, currency); currency.setdRateParameters(dRate); dRateSaver.storeNewRate(dRateSituation, currency); currency.setiRateParameters(iRate); iRateSaver.storeNewRate(iRateSituation, currency); } else { // Disable the old rates aRateSaver.disableOldRate(old, currency, aRateSituation); dRateSaver.disableOldRate(old, currency, dRateSituation); iRateSaver.disableOldRate(old, currency, iRateSituation); // Store the new situations aRateSaver.storeNewRate(aRateSituation, currency); dRateSaver.storeNewRate(dRateSituation, currency); iRateSaver.storeNewRate(iRateSituation, currency); } currencyDao.update(currency, false); // Now force flushing the 1st level cache fetchService.clearCache(); if (iRateSituation == Situation.NEW) { // we shouldn't redo any other rate except i whatRate.setaRate(false); whatRate.setdRate(false); ReinitializeRatesDTO reinitDto = new ReinitializeRatesDTO(); reinitDto.setCurrencyId(currency.getId()); reinitDto.setWhatRate(whatRate); reinitDto.setMaintainPastSettings(true); reinitDto.setRequestURI("/cyclos/do/admin/editCurrency"); rateService.reinitializeRate(reinitDto); } } finally { getCache().clear(); } return currency; } public void setAccountServiceLocal(final AccountServiceLocal accountService) { this.accountService = accountService; } public void setaRateParametersDao(final ARateParametersDAO aRateParametersDao) { this.aRateParametersDao = aRateParametersDao; } public void setCacheManager(final CacheManager cacheManager) { this.cacheManager = cacheManager; } public void setCurrencyDao(final CurrencyDAO currencyDao) { this.currencyDao = currencyDao; } public void setdRateParametersDao(final DRateParametersDAO dRateParametersDao) { this.dRateParametersDao = dRateParametersDao; } public void setFetchServiceLocal(final FetchServiceLocal fetchService) { this.fetchService = fetchService; } public void setiRateParametersDao(final IRateParametersDAO iRateParametersDao) { this.iRateParametersDao = iRateParametersDao; } public void setRateServiceLocal(final RateServiceLocal rateService) { this.rateService = rateService; } public void setSettingsServiceLocal(final SettingsServiceLocal settingsService) { this.settingsService = settingsService; } @Override public void validate(final Currency currency, final WhatRate whatRate) { getValidator(whatRate).validate(currency); } private Validator getBaseValidator() { final Validator validator = new Validator("currency"); validator.property("name").required().maxLength(100); validator.property("description").maxLength(2000); validator.property("symbol").required().maxLength(20); validator.property("pattern").required().maxLength(30).add(new PropertyValidation() { private static final long serialVersionUID = 455899399346626634L; @Override public ValidationError validate(final Object object, final Object name, final Object value) { final String pattern = (String) value; // Check if units pattern contains #amount# if (!StringUtils.isEmpty(pattern) && !pattern.contains("#amount#")) { return new ValidationError("currency.error.pattern"); } return null; } }); validator.general(new GeneralValidation() { private static final long serialVersionUID = 6441662788591991447L; @Override public ValidationError validate(final Object object) { final Currency currency = (Currency) object; if (rateService.checkPendingRateInitializations(currency) != null) { return new ValidationError("rates.error.currency.noEditDuringRateReinit"); } return null; } }); return validator; } private Cache getCache() { return cacheManager.getCache("cyclos.Currencies"); } private Validator getValidator(final WhatRate whatRate) { Validator validator = getBaseValidator(); validator = rateService.getRateParametersValidator(validator, whatRate); return validator; } private void validate(final Currency currency, final Situation aRateSituation, final Situation dRateSituation, final Situation iRateSituation) { WhatRate whatRate = new WhatRate(); whatRate.setaRate(aRateSituation == Situation.CHANGED || aRateSituation == Situation.NEW); whatRate.setdRate(dRateSituation == Situation.CHANGED || dRateSituation == Situation.NEW); whatRate.setiRate(iRateSituation == Situation.CHANGED || iRateSituation == Situation.NEW); getValidator(whatRate).validate(currency); } }