/**
* Revenue Settlement and Sharing System GE
* Copyright (C) 2011-2014, Javier Lucio - lucio@tid.es
* Telefonica Investigacion y Desarrollo, S.A.
*
* Copyright (C) 2015, CoNWeT Lab., Universidad Politécnica de Madrid
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package es.upm.fiware.rss.expenditureLimit.processing;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import es.upm.fiware.rss.common.Constants;
import es.upm.fiware.rss.dao.DbeAppProviderDao;
import es.upm.fiware.rss.exception.RSSException;
import es.upm.fiware.rss.exception.UNICAExceptionType;
import es.upm.fiware.rss.expenditureLimit.dao.DbeExpendControlDao;
import es.upm.fiware.rss.expenditureLimit.dao.DbeExpendLimitDao;
import es.upm.fiware.rss.expenditureLimit.model.DbeExpendControl;
import es.upm.fiware.rss.expenditureLimit.model.DbeExpendLimit;
import es.upm.fiware.rss.model.DbeTransaction;
/**
*
*
*/
@Service("processLimitService")
@Transactional
public class ProcessingLimitService {
/**
* Variable to print the trace.
*/
private static Logger logger = LoggerFactory.getLogger(ProcessingLimitService.class);
public static String DAY_PERIOD_TYPE = "daily";
public static String MONTH_PERIOD_TYPE = "monthly";
public static String WEEK_TYPE = "weekly";
public static String TRANSACTION_TYPE = "perTransaction";
public static String EXCCED_PARAMETER_NAME = "%levelExcceded%";
@Autowired
private DbeExpendLimitDao expendLimitDao;
@Autowired
private DbeExpendControlDao expendControlDao;
/**
*
*/
@Autowired
private DbeAppProviderDao appProviderDao;
/**
*
* Check that a charge do not exceed control limit.
*
* @param tx
* @param update
* @throws RSSException
*/
public void proccesLimit(DbeTransaction tx) throws RSSException {
ProcessingLimitService.logger.debug("=========== Process limit for tx:" + tx.getTxTransactionId());
// transaction to take into account: charge, capture, refund
String operationType = tx.getTcTransactionType();
if (operationType.equalsIgnoreCase(Constants.CAPTURE_TYPE)
|| operationType.equalsIgnoreCase(Constants.CHARGE_TYPE)
|| operationType.equalsIgnoreCase(Constants.REFUND_TYPE)) {
ProcessingLimitService.logger.debug("===== IF");
// Obtain accumulated
List<DbeExpendControl> controls = getControls(tx);
DbeExpendControl control;
BigDecimal levelExceded = new BigDecimal(0);
List<DbeExpendLimit> limits = getLimits(tx);
if (null != limits && !limits.isEmpty()) {
boolean notification = false;
for (DbeExpendLimit limit : limits) {
if (ProcessingLimitService.TRANSACTION_TYPE.equalsIgnoreCase(limit.getId().getTxElType())) {
ProcessingLimitUtil utils = new ProcessingLimitUtil();
BigDecimal total = utils.getValueToAddFromTx(tx);
checkMaxAmountExceed(total, limit, tx);
} else {
// Get control to check against the limit
control = getExpendControlToCheck(controls, limit, tx);
// check end period do not exceeded
// reset control limit if done
// Add amount.
// Obtain the limits and accumulates of an user.
// check if there is notifications
BigDecimal controlLevelExceded = checkLimit(control, limit, tx);
if (controlLevelExceded.compareTo(levelExceded) > 0) {
ProcessingLimitService.logger.debug("level exceeded: " + controlLevelExceded);
levelExceded = controlLevelExceded;
notification = true;
}
}
}
if (notification) {
ProcessingLimitService.logger.warn("Notification limit " + levelExceded + " exceed for tx:"
+ tx.getTxTransactionId());
}
}
}
}
/**
* Get the Control limit to check. If not exist generate it.
*
* @param controls
* @param type
* @param tx
* @return
*/
@Transactional
private DbeExpendControl getExpendControlToCheck(List<DbeExpendControl> controls, DbeExpendLimit limit,
DbeTransaction tx)
throws RSSException {
// Obtain accumulated --> if not exist --> create it.
if (null != controls && controls.size() > 0) {
for (DbeExpendControl control : controls) {
if (limit.getId().getTxElType().equalsIgnoreCase(control.getId().getTxElType())) {
return control;
}
}
}
ProcessingLimitUtil utils = new ProcessingLimitUtil();
// Create control
DbeExpendControl control = utils.createControl(tx, limit);
// save control.
expendControlDao.createOrUpdate(control);
// Add control to the list to take into account
if (controls == null) {
controls = new ArrayList<>();
}
controls.add(control);
return control;
}
/**
* Update control limit after a charge.
*
* @param tx
* @param update
* @throws RSSException
*/
@Transactional
public void updateLimit(DbeTransaction tx) throws RSSException {
ProcessingLimitService.logger.debug("Update limit for tx: " + tx.getTxTransactionId());
// transaction to take into account: charge, capture, refund
String operationType = tx.getTcTransactionType();
if ((operationType.equalsIgnoreCase(Constants.CAPTURE_TYPE)
|| operationType.equalsIgnoreCase(Constants.CHARGE_TYPE)
|| operationType.equalsIgnoreCase(Constants.REFUND_TYPE))) {
ProcessingLimitUtil utils = new ProcessingLimitUtil();
List<DbeExpendControl> controls = getControls(tx);
// always update limits to take into account two charges at the same time
if (null != controls && controls.size() > 0) {
for (DbeExpendControl control : controls) {
// check the period in order to reset it.
ckeckPeriodLimit(control);
// update the quantity.
BigDecimal total = utils.updateAcccumalateValue(control, tx);
control.setFtExpensedAmount(total);
expendControlDao.createOrUpdate(control);
}
}
}
}
/**
* Get Controls.
*
* @param tx
* @throws RSSException
*/
private List<DbeExpendControl> getControls(DbeTransaction tx) throws RSSException {
// could be end user or globlaUser
List<DbeExpendControl> controls = expendControlDao.
getExpendDataForUserAppProvCurrency(
tx.getTxEndUserId(),
tx.getAppProvider().getId().getAggregator().getTxEmail(),
tx.getAppProvider().getId().getTxAppProviderId(),
tx.getBmCurrency());
return controls;
}
/**
* Get Limits.
*
* @param tx
* @throws RSSException
*/
private List<DbeExpendLimit> getLimits(DbeTransaction tx) throws RSSException {
// could be end user or globlaUser
HashMap<String, List<DbeExpendLimit>> limitsHash = expendLimitDao.
getOrdExpLimitsForUserAppProvCurrency(
tx.getTxEndUserId(),
tx.getAppProvider().getId().getAggregator().getTxEmail(),
tx.getAppProvider().getId().getTxAppProviderId(),
tx.getBmCurrency());
List<DbeExpendLimit> limits = new ArrayList<>();
limits.addAll(limitsHash.get(DbeExpendLimitDao.USER_APP_PROV_KEY));
limits.addAll(limitsHash.get(DbeExpendLimitDao.USER_KEY));
limits.addAll(limitsHash.get(DbeExpendLimitDao.APP_PROV_KEY));
limits.addAll(limitsHash.get(DbeExpendLimitDao.ALL_GENERIC_KEY));
return limits;
}
/**
* Check that once limit is not exceed
*
* @param limit
* @param tx
* @throws RSSException
* @return boolean
*/
private BigDecimal checkLimit(DbeExpendControl control, DbeExpendLimit limit, DbeTransaction tx)
throws RSSException {
ProcessingLimitUtil utils = new ProcessingLimitUtil();
// check end period do not exceed
// reset control limit if done
// check limit and update if needed.
ckeckPeriodLimit(control);
// Update value.
BigDecimal total = utils.updateAcccumalateValue(control, tx);
// check that the limit is not exceed
checkMaxAmountExceed(total, limit, tx);
// check notifications
return checkNotification(control, limit, total);
}
/**
* Check amounts and generate exceptions.
*
* @param total
* @param limit
* @param tx
* @throws RSSException
*/
private void checkMaxAmountExceed(BigDecimal total, DbeExpendLimit limit, DbeTransaction tx) throws RSSException {
// check that the limit is not exceed
if (total.compareTo(limit.getFtMaxAmount()) > 0) {
ProcessingLimitService.logger.error("Credit limit " + limit.getFtMaxAmount() + " exceeded "
+ limit.getId().getTxElType() + " for tx:" + tx.getTxTransactionId());
String[] args = {
"Limit " + limit.getId().getTxElType() + " exceeded by transactionId:" + tx.getTxTransactionId() };
throw new RSSException(UNICAExceptionType.INSUFFICIENT_MOP_BALANCE, args);
}
}
/**
* Check notifications to sent
*
* @param control
* @param limit
* @param total
* @return
*/
@Transactional
private BigDecimal checkNotification(DbeExpendControl control, DbeExpendLimit limit, BigDecimal total) {
ProcessingLimitUtil utils = new ProcessingLimitUtil();
List<BigDecimal> limits = utils.getLimitsFromString(limit.getTxNotifAmounts());
BigDecimal maxLevel = new BigDecimal(0);
if (null != limits && limits.size() > 0) {
for (BigDecimal value : limits) {
// the total is equal or greater to the threshold
if (total.compareTo(value) >= 0) {
// Obtain the maxLevel
if (value.compareTo(maxLevel) > 0) {
maxLevel = value;
}
}
}
}
ProcessingLimitService.logger.debug("MaxLevel reached: " + maxLevel);
// Compare against the notifications sent
BigDecimal lastNotificationSent = utils.getLastNotificationSent(control.getTxNotifications());
if (null == lastNotificationSent) {
lastNotificationSent = new BigDecimal(0);
}
ProcessingLimitService.logger.debug("Last notification sent: " + lastNotificationSent);
if (maxLevel.compareTo(lastNotificationSent) > 0) {
ProcessingLimitService.logger.debug("Add Notification for level: " + maxLevel);
// Add new notification and sent.
control.setTxNotifications(utils.addValueToLimits(maxLevel, control.getTxNotifications()));
expendControlDao.createOrUpdate(control);
return maxLevel;
}
return new BigDecimal(0);
}
/**
* Check that a period has not been exceeded in order to reset it.
*
* @param limit
* @throws RSSException
*/
@Transactional
private void ckeckPeriodLimit(DbeExpendControl control) throws RSSException {
if (null != control.getDtNextPeriodStart()
&& control.getDtNextPeriodStart().compareTo(new Date()) <= 0) {
ProcessingLimitUtil utils = new ProcessingLimitUtil();
ProcessingLimitService.logger.debug("Reset control " + control.getId().getTxElType());
// if period has ended.
// reset Amount
control.setFtExpensedAmount(new BigDecimal(0));
// reset notifications
control.setTxNotifications("");
// Update NextPeriodStart
utils.updateNextPeriodToStart(control);
expendControlDao.createOrUpdate(control);
}
}
}