Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
commit
7a564fce21
11 changed files with 712 additions and 4 deletions
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>clearing-parent</artifactId>
|
||||
|
|
@ -40,6 +40,22 @@
|
|||
<groupId>ru.spcex.platform</groupId>
|
||||
<artifactId>platform-enum</artifactId>
|
||||
</dependency>
|
||||
<!-- TEST -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
@ -67,6 +83,23 @@
|
|||
<finalName>${project.artifactId}</finalName>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.21.0</version>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.junit.platform</groupId>
|
||||
<artifactId>junit-platform-surefire-provider</artifactId>
|
||||
<version>1.2.0-M1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<version>5.2.0-M1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
|
@ -23,7 +23,7 @@ public enum AccountBalanceValidationRule implements IValidationRule<ImdgValidati
|
|||
Imdg<Company> companyImdg = context.obtainMap(IMDGDistributedNames.Map_Company, Company.class);
|
||||
Company company = companyImdg.getSingleObjectByID(validatedObject.addresseeId());
|
||||
if (company == null) {
|
||||
return of( BalanceError.CompanyNotFound);
|
||||
return of(BalanceError.CompanyNotFound);
|
||||
}
|
||||
context.storeObject(ValidationStored.Company, company);
|
||||
return empty();
|
||||
|
|
@ -50,6 +50,6 @@ public enum AccountBalanceValidationRule implements IValidationRule<ImdgValidati
|
|||
|
||||
@Override
|
||||
public String ruleName() {
|
||||
return "Sdf01ValidationRule." + name();
|
||||
return "AccountBalanceValidationRule." + name();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
package ru.spcex.clearing.balance.config;
|
||||
|
||||
import com.hazelcast.config.*;
|
||||
import com.hazelcast.core.Hazelcast;
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import ru.spcex.platform.imdg.api.ImdgProvider;
|
||||
import ru.spcex.platform.imdg.iml.hazelcast.config.HazelcastClientParams;
|
||||
import ru.spcex.platform.imdg.iml.hazelcast.service.HazelcastService;
|
||||
import ru.spcex.platform.imdg.iml.hazelcast.util.HazelcastHelper;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
@Configuration
|
||||
public class BalanceImdgTestConfig {
|
||||
|
||||
private HazelcastInstance hazelcastInstance;
|
||||
|
||||
private static ThreadPoolTaskExecutor createThreadPoolTestTaskExecutor(int maxPoolSz, boolean waitForCompletion) {
|
||||
ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor();
|
||||
if (maxPoolSz > 2) {
|
||||
pool.setKeepAliveSeconds(60);
|
||||
pool.setAllowCoreThreadTimeOut(true);
|
||||
}
|
||||
pool.setCorePoolSize(maxPoolSz);
|
||||
pool.setWaitForTasksToCompleteOnShutdown(waitForCompletion);
|
||||
return pool;
|
||||
}
|
||||
|
||||
@Bean(name = "taskExecutorHazelcastTestClientInitializer")
|
||||
public ThreadPoolTaskExecutor taskExecutorHazelcastTestClientInitializer() {
|
||||
return createThreadPoolTestTaskExecutor(1, true);
|
||||
}
|
||||
|
||||
@Bean(name = "taskExecutorTestIdGeneratorAwaiter")
|
||||
public ThreadPoolTaskExecutor taskExecutorTestIdGeneratorAwaiter() {
|
||||
return createThreadPoolTestTaskExecutor(1, false);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
@Bean(name = "hazelcastServiceTest")
|
||||
public ImdgProvider imdgTestProvider(
|
||||
@Qualifier("taskExecutorHazelcastTestClientInitializer") ThreadPoolTaskExecutor taskExecutorHazelcastClientInitializer,
|
||||
@Qualifier("taskExecutorTestIdGeneratorAwaiter") ThreadPoolTaskExecutor taskExecutorIdGeneratorAwaiter,
|
||||
HazelcastClientParams params) {
|
||||
Config cfg = new Config();
|
||||
cfg.setInstanceName("localhost");
|
||||
|
||||
NetworkConfig networkConfig = new NetworkConfig();
|
||||
JoinConfig joinConfig = new JoinConfig();
|
||||
joinConfig.setMulticastConfig(new MulticastConfig().setEnabled(false));
|
||||
joinConfig.setTcpIpConfig(new TcpIpConfig().setEnabled(true).setMembers(List.of("127.0.0.1")));
|
||||
networkConfig.setJoin(joinConfig);
|
||||
cfg.setNetworkConfig(networkConfig);
|
||||
hazelcastInstance = Hazelcast.newHazelcastInstance(cfg);
|
||||
HazelcastHelper.otcSystem_setStorageState(true, hazelcastInstance);
|
||||
return new HazelcastService(taskExecutorHazelcastClientInitializer, taskExecutorIdGeneratorAwaiter, params);
|
||||
}
|
||||
|
||||
@Bean(name = "hazelcastClientParams")
|
||||
public HazelcastClientParams getHazelcastClientParams() {
|
||||
HazelcastClientParams params = new HazelcastClientParams();
|
||||
params.setLogin("dev");
|
||||
params.setPassword("dev-pass");
|
||||
params.setClusterMembers("127.0.0.1");
|
||||
params.setInstanceName("hzTestClient" + new Random().nextInt());
|
||||
params.setNearCacheConfig(new NearCacheConfig());
|
||||
return params;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package ru.spcex.clearing.balance.config;
|
||||
|
||||
import org.apache.kafka.clients.consumer.Consumer;
|
||||
import org.apache.kafka.clients.consumer.MockConsumer;
|
||||
import org.apache.kafka.clients.consumer.OffsetResetStrategy;
|
||||
import org.apache.kafka.clients.producer.MockProducer;
|
||||
import org.apache.kafka.clients.producer.Producer;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
|
||||
@Configuration
|
||||
public class KafkaTestConfig {
|
||||
|
||||
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
|
||||
@Bean
|
||||
public Consumer<String, Object> createTestConsumer() {
|
||||
return new MockConsumer<>(OffsetResetStrategy.EARLIEST);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Producer<String, Object> createTestProducer() {
|
||||
return new MockProducer<>();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package ru.spcex.clearing.balance.service;
|
||||
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import ru.spcex.clearing.balance.config.BalanceImdgTestConfig;
|
||||
import ru.spcex.clearing.balance.config.KafkaSenderConfig;
|
||||
import ru.spcex.clearing.balance.config.KafkaTestConfig;
|
||||
import ru.spcex.clearing.balance.config.ValidationConfig;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration(classes = {
|
||||
AccountBalanceService.class,
|
||||
ValidationConfig.class,
|
||||
BalanceImdgTestConfig.class,
|
||||
KafkaSenderConfig.class,
|
||||
KafkaTestConfig.class})
|
||||
public abstract class AbstractServiceTest {
|
||||
}
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
package ru.spcex.clearing.balance.service;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import ru.clearing.classes.statics.data.account.Account;
|
||||
import ru.clearing.classes.statics.data.account.AccountBalance;
|
||||
import ru.clearing.classes.statics.data.company.Company;
|
||||
import ru.spcex.clearing.balance.errors.BalanceError;
|
||||
import ru.spcex.clearing.balance.utils.MatcherFactory.Matcher;
|
||||
import ru.spcex.clearing.imdg.IMDGDistributedNames;
|
||||
import ru.spcex.platform.enumeration.AccountType;
|
||||
import ru.spcex.platform.enumeration.BalanceAccountType;
|
||||
import ru.spcex.platform.enumeration.Status;
|
||||
import ru.spcex.platform.imdg.api.Imdg;
|
||||
import ru.spcex.platform.imdg.api.ImdgProvider;
|
||||
import ru.spcex.platform.utils.enumeration.EnumMessage;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import static ru.spcex.clearing.balance.utils.MatcherFactory.usingIgnoringFieldsComparator;
|
||||
|
||||
|
||||
class AccountBalanceServiceTest extends AbstractServiceTest {
|
||||
|
||||
public static final Matcher<AccountResult> ACCOUNT_BALANCE_MATCHER = usingIgnoringFieldsComparator("account.created", "account.updated", "account.clearingDate");
|
||||
private final Long id = 0L;
|
||||
private final Long accountIdNew = 0L;
|
||||
private final Long addresseeIdNew = 0L;
|
||||
private final BigDecimal amountNew = new BigDecimal(1000);
|
||||
private final String cashMovementCurrencyCodeNew = "cash";
|
||||
private final Long accountIdUpdate = accountIdNew;
|
||||
private final Long addresseeIdUpdate = addresseeIdNew;
|
||||
private final BigDecimal amountUpdate = new BigDecimal(100000);
|
||||
private final String cashMovementCurrencyCodeUpdate = "cash";
|
||||
@Autowired
|
||||
AccountBalanceService accountBalanceService;
|
||||
@Autowired
|
||||
@Qualifier("hazelcastServiceTest")
|
||||
private ImdgProvider hazelcast;
|
||||
|
||||
private Imdg<Company> companyImdg;
|
||||
private Imdg<Account> accountImdg;
|
||||
|
||||
@PostConstruct
|
||||
void init() {
|
||||
companyImdg = hazelcast.getImdg(IMDGDistributedNames.Map_Company, Company.class);
|
||||
accountImdg = hazelcast.getImdg(IMDGDistributedNames.Map_Account, Account.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createAccountBalance() {
|
||||
hazelcast.waitAvailable();
|
||||
Company company = new Company();
|
||||
company.setId(addresseeIdNew);
|
||||
company.setTradingCode("code");
|
||||
company.setShortName("ShortName");
|
||||
company.setFullName("FullName");
|
||||
companyImdg.insert(company);
|
||||
Account account = new Account();
|
||||
account.setId(accountIdNew);
|
||||
account.setAccount("123456789");
|
||||
account.setAccountType(AccountType.Clrn.getKey());
|
||||
account.setAccountStatus(Status.Active.getKey());
|
||||
accountImdg.insert(account);
|
||||
|
||||
AccountBalance accountBalanceNew = new AccountBalance();
|
||||
accountBalanceNew.setId(id);
|
||||
accountBalanceNew.setCompanyId(addresseeIdNew);
|
||||
accountBalanceNew.setAccountId(accountIdNew);
|
||||
accountBalanceNew.setAccountType(account.getAccountType());
|
||||
accountBalanceNew.setAccount(account.getAccount());
|
||||
accountBalanceNew.setOpenBalanceAmount(amountNew);
|
||||
accountBalanceNew.setFreeBalanceAmount(amountNew);
|
||||
accountBalanceNew.setBalanceAmount(amountNew);
|
||||
accountBalanceNew.setBalanceAccountType(BalanceAccountType.Active.getKey());
|
||||
accountBalanceNew.setCurrencyCode(cashMovementCurrencyCodeNew);
|
||||
accountBalanceNew.setTradingCode(company.getTradingCode());
|
||||
accountBalanceNew.setShortName(company.getShortName());
|
||||
accountBalanceNew.setFullName(company.getFullName());
|
||||
AccountResult predictableNewResult = new AccountResult(accountBalanceNew);
|
||||
AccountResult resultNew = accountBalanceService.createAccountBalance(addresseeIdNew, accountIdNew, amountNew, cashMovementCurrencyCodeNew);
|
||||
resultNew.getAccount().setId(id);
|
||||
|
||||
AccountBalance accountBalanceUpdate = new AccountBalance();
|
||||
accountBalanceUpdate.setId(id);
|
||||
accountBalanceUpdate.setCompanyId(addresseeIdUpdate);
|
||||
accountBalanceUpdate.setAccountId(accountIdUpdate);
|
||||
accountBalanceUpdate.setAccountType(account.getAccountType());
|
||||
accountBalanceUpdate.setAccount(account.getAccount());
|
||||
accountBalanceUpdate.setOpenBalanceAmount(amountUpdate);
|
||||
accountBalanceUpdate.setFreeBalanceAmount(amountUpdate);
|
||||
accountBalanceUpdate.setBalanceAmount(amountUpdate);
|
||||
accountBalanceUpdate.setBalanceAccountType(BalanceAccountType.Active.getKey());
|
||||
accountBalanceUpdate.setCurrencyCode(cashMovementCurrencyCodeUpdate);
|
||||
accountBalanceUpdate.setTradingCode(company.getTradingCode());
|
||||
accountBalanceUpdate.setShortName(company.getShortName());
|
||||
accountBalanceUpdate.setFullName(company.getFullName());
|
||||
AccountResult resultUpdate = accountBalanceService.createAccountBalance(addresseeIdUpdate, accountIdUpdate, amountUpdate, cashMovementCurrencyCodeUpdate);
|
||||
resultUpdate.getAccount().setId(id);
|
||||
AccountResult predictableUpdateResult = new AccountResult(accountBalanceUpdate);
|
||||
|
||||
ACCOUNT_BALANCE_MATCHER.assertMatch(resultNew, predictableNewResult);
|
||||
ACCOUNT_BALANCE_MATCHER.assertMatch(resultUpdate, predictableUpdateResult);
|
||||
}
|
||||
|
||||
@Test
|
||||
void validatedCreateAccountBalance() {
|
||||
AccountResult predictableResult;
|
||||
hazelcast.waitAvailable();
|
||||
predictableResult = new AccountResult(new EnumMessage(BalanceError.CompanyNotFound));
|
||||
|
||||
AccountResult result = accountBalanceService.createAccountBalance(addresseeIdNew, accountIdNew, amountNew, cashMovementCurrencyCodeNew);
|
||||
ACCOUNT_BALANCE_MATCHER.assertMatch(result, predictableResult);
|
||||
|
||||
Company company = new Company();
|
||||
company.setId(addresseeIdNew);
|
||||
companyImdg.insert(company);
|
||||
|
||||
Account account = new Account();
|
||||
// account.setId(accountIdNew);
|
||||
account.setAccountType(AccountType.Clrn.getKey());
|
||||
account.setAccountStatus(Status.Active.getKey());
|
||||
accountImdg.insert(account);
|
||||
predictableResult = new AccountResult(new EnumMessage(BalanceError.AccountNotPresent));
|
||||
|
||||
result = accountBalanceService.createAccountBalance(addresseeIdNew, accountIdNew, amountNew, cashMovementCurrencyCodeNew);
|
||||
ACCOUNT_BALANCE_MATCHER.assertMatch(result, predictableResult);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package ru.spcex.clearing.balance.utils;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Factory for creating test matchers.
|
||||
* <p>
|
||||
* Comparing actual and expected objects via AssertJ
|
||||
*/
|
||||
public class MatcherFactory {
|
||||
|
||||
public static <T> Matcher<T> usingIgnoringFieldsComparator(String... fieldsToIgnore) {
|
||||
return new Matcher<>(fieldsToIgnore);
|
||||
}
|
||||
|
||||
public static class Matcher<T> {
|
||||
private final String[] fieldsToIgnore;
|
||||
|
||||
private Matcher(String... fieldsToIgnore) {
|
||||
this.fieldsToIgnore = fieldsToIgnore;
|
||||
}
|
||||
|
||||
public void assertMatch(T actual, T expected) {
|
||||
assertThat(actual).usingRecursiveComparison().ignoringFields(fieldsToIgnore).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public final void assertMatch(Iterable<T> actual, T... expected) {
|
||||
assertMatch(actual, Arrays.asList(expected));
|
||||
}
|
||||
|
||||
public void assertMatch(Iterable<T> actual, Iterable<T> expected) {
|
||||
assertThat(actual).usingRecursiveFieldByFieldElementComparatorIgnoringFields(fieldsToIgnore).isEqualTo(expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
package ru.spcex.clearing.scheduler.enums;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Удобный интерфейс для enum при использовании TextErrorService.
|
||||
*/
|
||||
public interface IEnumWithLongValue extends Serializable {
|
||||
|
||||
|
||||
/**
|
||||
* Проверяет что среди данного набора Enum, присутствует элемент с данным id
|
||||
* id может быть null
|
||||
*/
|
||||
static <T extends Enum<T> & IEnumWithLongValue> boolean contains(Long id, T... enumSet) {
|
||||
for (T e : enumSet) {
|
||||
if (Objects.equals(id, e.getId()))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static <T extends Enum<T> & IEnumWithLongValue> boolean contains(T e, T... enumSet) {
|
||||
for (T enumEl : enumSet) {
|
||||
if (enumEl.equals(e))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static <T extends Enum<T> & IEnumWithLongValue> Long[] toLongArray(T... enumSet) {
|
||||
Long[] enumId = new Long[enumSet.length];
|
||||
for (int i = 0; i < enumSet.length; i++)
|
||||
enumId[i] = enumSet[i].getId();
|
||||
return enumId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет что среди всех Enum данного класса, присутствует элемент с данным id
|
||||
* id может быть null
|
||||
*/
|
||||
static <T extends Enum<T> & IEnumWithLongValue> boolean contains(Class<T> enumClass, Long id) {
|
||||
for (T e : enumClass.getEnumConstants()) {
|
||||
if (Objects.equals(id, e.getId()))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Возвращает Enum по id, если в заданном классе такой определен
|
||||
* id может быть null
|
||||
*
|
||||
* @return Enum если нашел, иначе <tt>null</tt>
|
||||
*/
|
||||
static <T extends Enum<T> & IEnumWithLongValue> T getEnumById(Class<T> enumClass, Long id) {
|
||||
for (T e : enumClass.getEnumConstants()) {
|
||||
if (Objects.equals(id, e.getId()))
|
||||
return e;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static Long getIdOrNull(IEnumWithLongValue enumVal) {
|
||||
return enumVal == null ? null : enumVal.getId();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* errorCode
|
||||
**/
|
||||
Long getId();
|
||||
|
||||
default boolean equalsById(Long id) {
|
||||
return id != null && getId().equals(id);
|
||||
}
|
||||
|
||||
String elementName();
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package ru.spcex.clearing.scheduler.enums;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public enum TaskStatuses {
|
||||
ACTIVE("ACTV"),
|
||||
BLOCKED("CNCL"),
|
||||
CANCEL("BLKD");
|
||||
|
||||
private final String name;
|
||||
|
||||
TaskStatuses(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public static TaskStatuses getEnumByName(String name) {
|
||||
for (TaskStatuses e : TaskStatuses.values()) {
|
||||
if (Objects.equals(name, e.name()))
|
||||
return e;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Boolean equalsByName(String name) {
|
||||
return this.name.equalsIgnoreCase(name);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public String elementName() {
|
||||
return this.name();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package ru.spcex.clearing.scheduler.enums;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public enum Tasks {
|
||||
GBAL("GBAL"),//Зачисление остатков
|
||||
ABLK("ABLK"),//Блокировка счета
|
||||
GALB("GALB"),//Запрос остатков по всем счетам
|
||||
ADBL("ADBL"),//Дозачисление/списание остатков
|
||||
CORD("CORD"),//Формирование сводного платежного поручения
|
||||
CORC("CORC"),//Получение подтверждения переводов
|
||||
GTRD("GTRD"),//Получение сделок из Торговой системы
|
||||
GVER("GVER"),// Запуск сверки
|
||||
GBLD("GBLD"),// Поступление средств
|
||||
SCLR("SCLR"),// Запуск клиринговой сессии
|
||||
SPRC("SPRC"),// Запуск преклиринга
|
||||
SPOC("SPOC");// Запуск постклиринга
|
||||
|
||||
private String name;
|
||||
|
||||
Tasks(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public static Tasks getEnumByName(String name) {
|
||||
for (Tasks e : Tasks.values()) {
|
||||
if (Objects.equals(name, e.name()))
|
||||
return e;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Boolean equalsByName(String name) {
|
||||
return this.name.equalsIgnoreCase(name);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public String elementName() {
|
||||
return this.name();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,224 @@
|
|||
package ru.spcex.clearing.scheduler.service;
|
||||
|
||||
import com.hazelcast.core.EntryEvent;
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
import com.hazelcast.core.IMap;
|
||||
import com.hazelcast.map.listener.EntryAddedListener;
|
||||
import com.hazelcast.map.listener.EntryRemovedListener;
|
||||
import com.hazelcast.map.listener.EntryUpdatedListener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import ru.clearing.classes.statics.data.scheduler.PlannerAllToday;
|
||||
import ru.spcex.clearing.scheduler.enums.TaskStatuses;
|
||||
import ru.spcex.clearing.scheduler.enums.Tasks;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static ru.spcex.clearing.imdg.IMDGDistributedNames.Map_PlannerAllToday;
|
||||
import static ru.spcex.clearing.scheduler.enums.TaskStatuses.*;
|
||||
|
||||
/**
|
||||
* Планировщик задач, расписание берёт из Hazelcast map.
|
||||
* <p>
|
||||
*/
|
||||
public abstract class TaskManager implements EntryAddedListener<Long, PlannerAllToday>,
|
||||
EntryUpdatedListener<Long, PlannerAllToday>, EntryRemovedListener<Long, PlannerAllToday>,
|
||||
InitializingBean {
|
||||
private static final Logger log = LoggerFactory.getLogger(TaskManager.class);
|
||||
|
||||
protected HazelcastInstance hazelcastInstance;
|
||||
protected IMap<Long, PlannerAllToday> plannerAllTodayMapStore;
|
||||
protected TaskScheduler taskScheduler;
|
||||
|
||||
protected ConcurrentHashMap<LocalTime, ScheduledFuture> scheduledJobs;
|
||||
|
||||
protected TaskManager(TaskScheduler taskScheduler, HazelcastInstance hazelcastInstance) {
|
||||
this.taskScheduler = taskScheduler;
|
||||
this.hazelcastInstance = hazelcastInstance;
|
||||
}
|
||||
|
||||
private static LocalDateTime dateOldTypeConvert(Date oldDate) {
|
||||
return LocalDateTime.ofInstant(oldDate.toInstant(), ZoneId.systemDefault());
|
||||
}
|
||||
|
||||
private static LocalDate dateTypeConvert(Date oldDate) {
|
||||
return dateOldTypeConvert(oldDate).toLocalDate();
|
||||
}
|
||||
|
||||
private static LocalTime timeTypeConvert(Date oldDate) {
|
||||
return dateOldTypeConvert(oldDate).toLocalTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
plannerAllTodayMapStore = hazelcastInstance.getMap(Map_PlannerAllToday);
|
||||
scheduledJobs = new ConcurrentHashMap<>();
|
||||
updateScheduler();
|
||||
}
|
||||
|
||||
// --- Слушатели Hazelcast Map ---
|
||||
@Override
|
||||
public void entryAdded(EntryEvent<Long, PlannerAllToday> event) {
|
||||
PlannerAllToday task = event.getValue();
|
||||
|
||||
Tasks taskType = Tasks.getEnumByName(task.getTask());
|
||||
if (taskType == null) {
|
||||
log.warn("Task skipped, {} task type not recognized", task.getTask());
|
||||
return;
|
||||
}
|
||||
processTask(task);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void entryUpdated(EntryEvent<Long, PlannerAllToday> event) {
|
||||
PlannerAllToday task = event.getValue();
|
||||
PlannerAllToday oldTask = event.getOldValue();
|
||||
|
||||
LocalTime oldTime = oldTask.getTaskTime();
|
||||
// TaskStatuses oldStatus
|
||||
//если таск относится к другому обработчику, пропускаем
|
||||
if (!getTask().equalsByName(task.getTask()) && !getTask().equalsByName(oldTask.getTask())) {
|
||||
return;
|
||||
}
|
||||
if (!Objects.equals(task.getTask(), oldTask.getTask())) {
|
||||
throw new IllegalStateException("changed taskId for SchedulerAllToday in core");
|
||||
}
|
||||
if (ACTIVE.equalsByName(oldTask.getTaskStatus())) { //&& ACTIVE.equalsById(task.getTaskStatusId())
|
||||
if (!removeTask(oldTime)) {
|
||||
log.debug("cannot cancel task with type {}, time {}", getTask().name(), oldTime.toString());
|
||||
} else {
|
||||
log.debug("task with type {}, time {} execution cancelled, adding altered task...", getTask().name(), oldTime.toString());
|
||||
}
|
||||
processTask(task);
|
||||
} else if (CANCEL.equalsByName(oldTask.getTaskStatus())) { //&& TaskStatuses.CANCEL.equalsById(task.getTaskStatusId())
|
||||
restorePreviouslyRemovedTask(oldTime, oldTask);
|
||||
processTask(task);
|
||||
} else if (BLOCKED.equalsByName(oldTask.getTaskStatus())) {
|
||||
processTask(task);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void entryRemoved(EntryEvent<Long, PlannerAllToday> event) {
|
||||
PlannerAllToday taskToRemove = event.getOldValue();
|
||||
if (taskToRemove == null) {
|
||||
log.debug("no task in removed event");
|
||||
return;
|
||||
}
|
||||
LocalTime removedTaskTime = taskToRemove.getTaskTime();
|
||||
|
||||
if (ACTIVE.equalsByName(taskToRemove.getTaskStatus())) {
|
||||
if (removeTask(removedTaskTime))
|
||||
log.debug("task successfully canceled");
|
||||
} else if (CANCEL.equalsByName(taskToRemove.getTaskStatus())) {
|
||||
restorePreviouslyRemovedTask(removedTaskTime, taskToRemove);
|
||||
} else if (BLOCKED.equalsByName(taskToRemove.getTaskStatus())) {
|
||||
log.debug("BLOCKED task removed; do nothing");
|
||||
}
|
||||
}
|
||||
|
||||
private void restorePreviouslyRemovedTask(LocalTime oldTime, PlannerAllToday oldTask) {
|
||||
ScheduledFuture cancelledFuture = scheduledJobs.get(oldTime);
|
||||
if (cancelledFuture != null && cancelledFuture.isCancelled()) {
|
||||
PlannerAllToday schedulerAllToday = new PlannerAllToday();
|
||||
schedulerAllToday.setTask(getTask().getName());
|
||||
schedulerAllToday.setTaskStatus(ACTIVE.getName());
|
||||
schedulerAllToday.setTaskTime(oldTask.getTaskTime());
|
||||
log.debug("CANCEL task updated/removed; restoring previously cancelled task");
|
||||
processTask(schedulerAllToday);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Планирование задач ---
|
||||
protected void updateScheduler() {
|
||||
Collection<PlannerAllToday> schedulerAllTodays = plannerAllTodayMapStore.values();
|
||||
Collection<PlannerAllToday> sortedSchedulers =
|
||||
schedulerAllTodays.stream().sorted((o1, o2) -> (ACTIVE.equalsByName(o1.getTaskStatus()) && CANCEL.equalsByName(o2.getTaskStatus())) ? -1 : 0)
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
for (PlannerAllToday schedulerAllToday : sortedSchedulers) {
|
||||
processTask(schedulerAllToday);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Работа с задачами ---
|
||||
private void processTask(PlannerAllToday task) {
|
||||
LocalTime taskTime = task.getTaskTime();
|
||||
Tasks taskType = Tasks.getEnumByName(task.getTask());
|
||||
TaskStatuses taskStatus = TaskStatuses.getEnumByName(task.getTaskStatus());
|
||||
if (taskStatus == null) throw new IllegalStateException("task status from core can't be null");
|
||||
if (!getTask().equals(taskType)) {
|
||||
log.debug("Task skipped - taskTime {}, is not of acceptable type {}", taskTime.toString(), taskType != null ? taskType.name() : "");
|
||||
return;
|
||||
}
|
||||
if (taskTime.isBefore(LocalTime.now())) {
|
||||
log.debug("Task skipped - taskTime {} is before now, taskType {} ok", taskTime, getTask().toString());
|
||||
return;
|
||||
}
|
||||
if (taskStatus.equals(BLOCKED)) {
|
||||
log.debug("Task skipped - timeTime {} with status {}", taskTime.toString(), BLOCKED.toString());
|
||||
return;
|
||||
}
|
||||
{
|
||||
ScheduledFuture future = scheduledJobs.get(taskTime);
|
||||
if (future != null) {
|
||||
if (TaskStatuses.CANCEL.equals(taskStatus)) {
|
||||
//случай когда пришел cancel, пытаемся отменить зарегистрированный ранее таск
|
||||
future.cancel(false);
|
||||
log.debug("cancelling task time {}", taskTime);
|
||||
return;
|
||||
} else if (TaskStatuses.ACTIVE.equals(taskStatus)) {
|
||||
//случай когда пришел активный таск, и уже был на это время неотмененный
|
||||
if (!future.isCancelled()) {
|
||||
log.debug("such task time {} has already been registered", taskTime);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (TaskStatuses.CANCEL.equals(taskStatus)) {
|
||||
log.debug("task type {} time {} with status CANCEL - no tasks to cancel found", taskType.name(), taskTime);
|
||||
return;
|
||||
}
|
||||
//пришел активный таск
|
||||
log.debug("adding task type {}, time {}", taskType.name(), taskTime);
|
||||
ScheduledFuture future = taskScheduler.schedule(() -> doJob(task), LocalDateTime.of(LocalDate.now(), taskTime).atZone(ZoneId.systemDefault()).toInstant());
|
||||
ScheduledFuture oldFuture = scheduledJobs.put(taskTime, future);
|
||||
if (oldFuture != null && !oldFuture.isCancelled()) { //for synchronization, never
|
||||
log.warn("tasks were added simultaneously, cancel former one");
|
||||
boolean success = oldFuture.cancel(false);
|
||||
log.warn("cancelling task " + (success ? "success" : "fail"));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean removeTask(LocalTime taskTime) {
|
||||
ScheduledFuture future = scheduledJobs.remove(taskTime);
|
||||
if (future == null) {
|
||||
log.debug("can't cancel task type {}, time {}, not found", getTask().name(), taskTime);
|
||||
return false;
|
||||
}
|
||||
return future.cancel(false);
|
||||
}
|
||||
|
||||
// --- Реализация выполнения задач ---
|
||||
|
||||
/**
|
||||
* Рабочий тип запланированных задач.
|
||||
*
|
||||
* @return идентификатор для фильтра типов планировщика задачь. Планировать задачи только этого типа.
|
||||
*/
|
||||
protected abstract Tasks getTask();
|
||||
|
||||
protected abstract void doJob(PlannerAllToday taskInfo);
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue