From 53a574f34f7e3687efc2d5c5aa57e8f2d9d6895d Mon Sep 17 00:00:00 2001 From: AKurakin Date: Wed, 31 May 2023 16:52:39 +0300 Subject: [PATCH] =?UTF-8?q?securities-service=20http://jira.mfd.msk:8088/b?= =?UTF-8?q?rowse/CLS-326=20Listing-=D0=B2=D0=B0=D0=BB=D0=B8=D0=B4=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D1=8F.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/rules/SpecialIdPresentRule.java | 77 ++++++++ .../securities/errors/SecuritiesError.java | 5 +- .../service/cud/ListingService.java | 81 +++----- .../cud/MoneyMarketSecurityService.java | 105 +++++++---- .../validation/ListingValidationProvider.java | 173 ++++++++++++++++++ .../service/AbstractServiceTest.java | 1 + 6 files changed, 343 insertions(+), 99 deletions(-) create mode 100644 clearing-parent/clearing-validation/src/main/java/ru/spcex/clearing/validation/common/rules/SpecialIdPresentRule.java create mode 100644 clearing-parent/securities-service/src/main/java/ru/spcex/clearing/securities/validation/ListingValidationProvider.java diff --git a/clearing-parent/clearing-validation/src/main/java/ru/spcex/clearing/validation/common/rules/SpecialIdPresentRule.java b/clearing-parent/clearing-validation/src/main/java/ru/spcex/clearing/validation/common/rules/SpecialIdPresentRule.java new file mode 100644 index 000000000..21642e761 --- /dev/null +++ b/clearing-parent/clearing-validation/src/main/java/ru/spcex/clearing/validation/common/rules/SpecialIdPresentRule.java @@ -0,0 +1,77 @@ +package ru.spcex.clearing.validation.common.rules; + +import ru.clearing.platform.dictionary.AbstractDictionary; +import ru.spcex.platform.classes.base.SpcexObjectBase; +import ru.spcex.platform.imdg.api.Imdg; +import ru.spcex.platform.imdg.api.predicate.ImdgPredicate; +import ru.spcex.platform.imdg.api.predicate.ImdgPredicateBuilder; +import ru.spcex.platform.imdg.validation.ImdgValidationContext; +import ru.spcex.platform.utils.enumeration.EnumMessage; +import ru.spcex.platform.utils.enumeration.IErrorEnumId; +import ru.spcex.platform.utils.validation.IValidationRule; + +import java.util.Collection; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; + +/** + * Проверка поля с кодом из таблицы (аналог IdPresentRule / DictionaryPresentRule) + * + * @param Класс проверяемого объекта + * @param Класс искомого объекта (mapName) + */ +public record SpecialIdPresentRule(String fieldName, + Function getter, + String keyName, + String mapName, + Class mapClass, + IErrorEnumId errorEmptyRequiredValue, + IErrorEnumId errorWrongDictionaryValue, + boolean required, + Function[] additionalChecks) implements IValidationRule> { + + + @SafeVarargs + public static SpecialIdPresentRule instance(String fieldName, + Function getter, + String keyName, + String mapName, + Class mapClass, + IErrorEnumId errorEmptyRequiredValue, + IErrorEnumId errorWrongDictionaryValue, + boolean required, + Function... additionalChecks) { + return new SpecialIdPresentRule<>(fieldName, getter, + keyName, mapName, mapClass, + errorEmptyRequiredValue, errorWrongDictionaryValue, + required, additionalChecks); + } + + @Override + public Optional validate(ImdgValidationContext context) { + R validatedObject = context.getValidatedObject(); + String kValue = getter.apply(validatedObject); + if (kValue == null) return required ? of(errorEmptyRequiredValue, fieldName) : empty(); + Imdg dictImdg = context.obtainMap(mapName, mapClass); + Collection fromMap = dictImdg.getCollectionObjectsByFieldValues(Map.of(keyName, kValue)); + if (fromMap.isEmpty()) { + return of(errorWrongDictionaryValue, fieldName); + } + T oFromMap = fromMap.iterator().next(); + if (oFromMap == null) { + return of(errorWrongDictionaryValue, fieldName); + } + for (Function additionalCheck : additionalChecks) { + IErrorEnumId validationError = additionalCheck.apply(oFromMap); + if (validationError != null) + return of(validationError, fieldName); + } + return empty(); + } + + @Override + public String ruleName() { + return getClass().getSimpleName() + "{" + fieldName + " - " + mapName + "}"; + } +} diff --git a/clearing-parent/securities-service/src/main/java/ru/spcex/clearing/securities/errors/SecuritiesError.java b/clearing-parent/securities-service/src/main/java/ru/spcex/clearing/securities/errors/SecuritiesError.java index cbcdfb91d..836333d1b 100644 --- a/clearing-parent/securities-service/src/main/java/ru/spcex/clearing/securities/errors/SecuritiesError.java +++ b/clearing-parent/securities-service/src/main/java/ru/spcex/clearing/securities/errors/SecuritiesError.java @@ -14,7 +14,10 @@ public enum SecuritiesError implements IErrorEnumId { CouponPeriodNotFound(1014L), CurrencyAlreadyExists(1015L), CurrencyNotFound(1016L), - + ListingAlreadyExist(1017L), // Инструмент на режиме %s уже существует + ListingNotFound(1017L), // Инструмент на режиме %s не найден + MarketNotFound(1020L), // Режим %s не найден + ListingOnMMSCreatedBySystem(1021L), // Информиция об инструментах Денежного рынка на режимах добавляется автоматически ; private final Long id; diff --git a/clearing-parent/securities-service/src/main/java/ru/spcex/clearing/securities/service/cud/ListingService.java b/clearing-parent/securities-service/src/main/java/ru/spcex/clearing/securities/service/cud/ListingService.java index 237da46f3..e49db2a5c 100644 --- a/clearing-parent/securities-service/src/main/java/ru/spcex/clearing/securities/service/cud/ListingService.java +++ b/clearing-parent/securities-service/src/main/java/ru/spcex/clearing/securities/service/cud/ListingService.java @@ -8,10 +8,7 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import ru.clearing.classes.statics.data.instrument.issue.EquitySecurity; -import ru.clearing.classes.statics.data.instrument.issue.FixedIncomeSecurity; import ru.clearing.classes.statics.data.misc.Listing; -import ru.clearing.classes.statics.data.security.MoneyMarketSecurity; import ru.clearing.classes.statics.data.security.Security; import ru.spcex.clearing.imdg.IMDGDistributedNames; import ru.spcex.clearing.platform.messaging.domain.BaseRequest; @@ -21,7 +18,8 @@ import ru.spcex.clearing.platform.messaging.domain.cud.securitites.ListingNewReq import ru.spcex.clearing.platform.messaging.domain.cud.securitites.ListingUpdateRequest; import ru.spcex.clearing.platform.messaging.service.QueueConsumer; import ru.spcex.clearing.platform.messaging.service.RequestInfoUpdate; -import ru.spcex.clearing.securities.validation.ValidationProvider; +import ru.spcex.clearing.util.security.SecuritySelector; +import ru.spcex.clearing.securities.validation.ListingValidationProvider; import ru.spcex.clearing.util.security.UserRoleVerification; import ru.spcex.clearing.validation.common.ValidationHelper; import ru.spcex.platform.enumeration.UserRole; @@ -29,7 +27,6 @@ import ru.spcex.platform.enumeration.WorkflowStatus; import ru.spcex.platform.imdg.api.Imdg; import ru.spcex.platform.imdg.api.ImdgId; import ru.spcex.platform.imdg.api.ImdgProvider; -import ru.spcex.platform.imdg.api.ImdgTransaction; import java.time.Instant; @@ -37,29 +34,25 @@ import java.time.Instant; public class ListingService extends QueueConsumer implements InitializingBean { private final Logger log = LoggerFactory.getLogger(getClass()); - private final Imdg fixedIncomeSecurityImdg; - private final Imdg moneyMarketSecurityImdg; - private final Imdg equitySecurity; - private final Imdg listingImdg; private final ImdgProvider imdgProvider; private final ImdgId idGenerator; - private final ValidationProvider validation; + private final ListingValidationProvider validation; private final UserRoleVerification userRoleVerification; private final ValidationHelper validationHelper; + private final SecuritySelector securitySelector; @Autowired public ListingService(Consumer kafkaQueue, Producer kafkaProducer, ImdgProvider imdgProvider, - ValidationProvider validation, + ListingValidationProvider validation, UserRoleVerification userRoleVerification, - ValidationHelper validationHelper) { + ValidationHelper validationHelper, + SecuritySelector securitySelector) { super(kafkaQueue, kafkaProducer); this.validation = validation; - this.fixedIncomeSecurityImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_FixedIncomeSecurity, FixedIncomeSecurity.class); - this.moneyMarketSecurityImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_MoneyMarketSecurity, MoneyMarketSecurity.class); - this.equitySecurity = imdgProvider.getImdg(IMDGDistributedNames.Map_EquitySecurity, EquitySecurity.class); + this.securitySelector = securitySelector; this.listingImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Listing, Listing.class); this.imdgProvider = imdgProvider; this.idGenerator = imdgProvider.getImdgIdGenerator(); @@ -82,42 +75,18 @@ public class ListingService extends QueueConsumer implements InitializingBean { init(); } - protected Security selectSecurityById(Long securityId) { - Security security = fixedIncomeSecurityImdg.getSingleObjectByID(securityId); - if (security != null) return security; - security = moneyMarketSecurityImdg.getSingleObjectByID(securityId); - if (security != null) return security; - security = equitySecurity.getSingleObjectByID(securityId); - if (security != null) return security; - - return security; - } - - private RequestInfoUpdate newListing(BaseRequest userRequest) { - ImdgTransaction transaction = imdgProvider.newTransaction(); + private synchronized RequestInfoUpdate newListing(BaseRequest userRequest) { ListingNewRequest req = userRequest.getRequestPayload(); + log.debug("newListing received, id={}", userRequest.getId()); RequestInfoUpdate requestInfoUpdate = userRoleVerification.validateRoleAndGetResult(userRequest); if (requestInfoUpdate != null) return requestInfoUpdate; -// RequestInfoUpdate requestInfoUpdate = validationHelper.validateTillFirstError(userRequest, validation.listingNewValidator()); -// if (requestInfoUpdate != null) return requestInfoUpdate; - /* - todo -3. Проверяем значения полученных параметров: - 3.1. Необходимо выполнить ряд проверок, стандартных для всех методов (проверка на обязательность параметров исключена). - 3.2. Проверить, что в таблице security есть инструмент, для которого нужно добавить листинг по security.id = securityId из очереди. Если такой записи нет, вернуть ошибку (1011) «Инструмент %s не найден». - 3.3. Проверить, что в таблице security тип инструмента instrumentType ≠ RATE (см. справочник instrumentType). Если тип инструмента другой, вернуть ошибку (1021) «Информиция об инструментах Денежного рынка на режимах добавляется автоматически». - 3.4. Проверить, что в таблице market есть режим, с которым нужно добавить листинг по market.code = market из очереди. Если такой записи нет, вернуть ошибку (1020) «Режим %s не найден». - 3.5. Проверить, что еще нет такой же записи в таблице listing по ключу securityId И market И со статусом listing.workflowStatus=ACTV (см. справочник workflowStatus). Если запись существует, вернуть ошибку (1017) «Инструмент на режиме %s уже существует». - */ + requestInfoUpdate = validationHelper.validateTillFirstError(userRequest, validation.listingNewValidator()); + if (requestInfoUpdate != null) return requestInfoUpdate; - log.debug("newListing received"); - - Security security = selectSecurityById(req.getSecurityId()); - if (security == null) { + Security security = securitySelector.selectSecurityById(req.getSecurityId()); + if (security == null) { // never log.error("Security not found {}", req.getSecurityId()); - //return validationHelper.getMessageResolver() - //todo error! } Listing listing = new Listing(); @@ -141,28 +110,24 @@ public class ListingService extends QueueConsumer implements InitializingBean { return null; // default success } - private RequestInfoUpdate updateListing(BaseRequest userRequest) { + private synchronized RequestInfoUpdate updateListing(BaseRequest userRequest) { ListingUpdateRequest req = userRequest.getRequestPayload(); log.debug("updateListing received id = {}", req.getId()); RequestInfoUpdate requestInfoUpdate = userRoleVerification.validateRoleAndGetResult(userRequest); if (requestInfoUpdate != null) return requestInfoUpdate; -// RequestInfoUpdate requestInfoUpdate = userRoleVerification.validateRoleAndGetResult(userRequest); -// if (requestInfoUpdate != null) return requestInfoUpdate; + requestInfoUpdate = validationHelper.validateTillFirstError(userRequest, validation.listingUpdateValidator()); + if (requestInfoUpdate != null) return requestInfoUpdate; Listing listing = listingImdg.getSingleObjectByID(req.getId()); - Security security = selectSecurityById(req.getSecurityId()); - if (security == null) { + Security security = securitySelector.selectSecurityById(req.getSecurityId()); + if (security == null) { // never log.error("Security not found {}", req.getSecurityId()); - //return validationHelper.getMessageResolver() - //todo error! } - if (listing == null) { + if (listing == null) { //never log.error("listing not found {}", req.getId()); - //return validationHelper.getMessageResolver() - //todo error! } listing.setUpdated(Instant.now()); @@ -178,14 +143,14 @@ public class ListingService extends QueueConsumer implements InitializingBean { return null; } - private RequestInfoUpdate deleteListing(BaseRequest userRequest) { + private synchronized RequestInfoUpdate deleteListing(BaseRequest userRequest) { CommonDeleteRequest req = userRequest.getRequestPayload(); log.debug("CommonDeleteRequest received id = {}", req.getId()); RequestInfoUpdate requestInfoUpdate = userRoleVerification.validateRoleAndGetResult(userRequest); if (requestInfoUpdate != null) return requestInfoUpdate; -// requestInfoUpdate = validationHelper.validateTillFirstError(userRequest, validation.listingDeleteValidator()); -// if (requestInfoUpdate != null) return requestInfoUpdate; + requestInfoUpdate = validationHelper.validateTillFirstError(userRequest, validation.listingDeleteValidator()); + if (requestInfoUpdate != null) return requestInfoUpdate; Instant updateTime = Instant.now(); Listing listing = listingImdg.getSingleObjectByID(req.getId()); diff --git a/clearing-parent/securities-service/src/main/java/ru/spcex/clearing/securities/service/cud/MoneyMarketSecurityService.java b/clearing-parent/securities-service/src/main/java/ru/spcex/clearing/securities/service/cud/MoneyMarketSecurityService.java index 5fef092b0..5fbf1afc2 100644 --- a/clearing-parent/securities-service/src/main/java/ru/spcex/clearing/securities/service/cud/MoneyMarketSecurityService.java +++ b/clearing-parent/securities-service/src/main/java/ru/spcex/clearing/securities/service/cud/MoneyMarketSecurityService.java @@ -51,6 +51,7 @@ public class MoneyMarketSecurityService extends QueueConsumer implements Initial private final UserRoleVerification userRoleVerification; private final ValidationHelper validationHelper; private final IMessageResolver messageResolver; + private final ListingService listingService; // для синхронизации @Autowired public MoneyMarketSecurityService(Consumer kafkaQueue, @@ -59,7 +60,8 @@ public class MoneyMarketSecurityService extends QueueConsumer implements Initial ValidationProvider validation, UserRoleVerification userRoleVerification, ValidationHelper validationHelper, - IMessageResolver messageResolver) { + IMessageResolver messageResolver, + ListingService listingService) { super(kafkaQueue, kafkaProducer); this.userRoleVerification = userRoleVerification.setRoleForVerification(UserRole.Admin); this.validationHelper = validationHelper; @@ -69,6 +71,7 @@ public class MoneyMarketSecurityService extends QueueConsumer implements Initial this.imdgProvider = imdgProvider; this.idGenerator = imdgProvider.getImdgIdGenerator(); this.messageResolver = messageResolver; + this.listingService = listingService; } @Override @@ -139,24 +142,26 @@ public class MoneyMarketSecurityService extends QueueConsumer implements Initial mms.setWorkflowStatus(req.getWorkflowStatus()); } ImdgTransaction transaction = imdgProvider.newTransaction(); - try { - transaction.beginTransaction(); - Imdg moneyMarketSecurityTxMap = transaction.getImdg(IMDGDistributedNames.Map_MoneyMarketSecurity, MoneyMarketSecurity.class); - Imdg listingTxMap = transaction.getImdg(IMDGDistributedNames.Map_Listing, Listing.class); - //одним скопом выполняем реквест - moneyMarketSecurityTxMap.insert(mms); - Listing listing = ListingBuilder.builder() - .append(mms).append(req).build(); - listingTxMap.insert(listing); - transaction.commitTransaction(); - } catch (Throwable e) { - log.error("Error at create security, rollback transaction. {}", ExceptionUtils.getStackTrace(e)); - transaction.rollbackTransaction(); - String errorMsg = messageResolver.resolve(new EnumMessage(SecuritiesError.GeneralError)); - return new RequestInfoUpdate() - .setId(userRequest.getId()) - .setStatus(Status.Error) - .setMessage(errorMsg); + synchronized (listingService) { + try { + transaction.beginTransaction(); + Imdg moneyMarketSecurityTxMap = transaction.getImdg(IMDGDistributedNames.Map_MoneyMarketSecurity, MoneyMarketSecurity.class); + Imdg listingTxMap = transaction.getImdg(IMDGDistributedNames.Map_Listing, Listing.class); + //одним скопом выполняем реквест + moneyMarketSecurityTxMap.insert(mms); + Listing listing = ListingBuilder.builder() + .append(mms).append(req).build(); + listingTxMap.insert(listing); + transaction.commitTransaction(); + } catch (Throwable e) { + log.error("Error at create security, rollback transaction. {}", ExceptionUtils.getStackTrace(e)); + transaction.rollbackTransaction(); + String errorMsg = messageResolver.resolve(new EnumMessage(SecuritiesError.GeneralError)); + return new RequestInfoUpdate() + .setId(userRequest.getId()) + .setStatus(Status.Error) + .setMessage(errorMsg); + } } log.debug("successfully processed, request id {}, new object id {}", userRequest.getId(), mms.getId()); return null; // default success @@ -205,18 +210,20 @@ public class MoneyMarketSecurityService extends QueueConsumer implements Initial mms.setWorkflowStatus(req.getWorkflowStatus()); } - transaction.beginTransaction(); - Imdg moneyMarketSecurityTxMap = transaction.getImdg(IMDGDistributedNames.Map_MoneyMarketSecurity, MoneyMarketSecurity.class); - Imdg listingTxMap = transaction.getImdg(IMDGDistributedNames.Map_Listing, Listing.class); + synchronized (listingService) { + transaction.beginTransaction(); + Imdg moneyMarketSecurityTxMap = transaction.getImdg(IMDGDistributedNames.Map_MoneyMarketSecurity, MoneyMarketSecurity.class); + Imdg listingTxMap = transaction.getImdg(IMDGDistributedNames.Map_Listing, Listing.class); - moneyMarketSecurityTxMap.update(mms); - Listing listing = listingTxMap.getSingleObjectByFieldValues(Map.of("securityId", mms.getId())); - if (listing == null) { - log.error("MoneyMarketSecurityUpdateRequest id {} couldn't find listing with securityId {}", req.getId(), mms.getId()); - return null; + moneyMarketSecurityTxMap.update(mms); + Listing listing = listingTxMap.getSingleObjectByFieldValues(Map.of("securityId", mms.getId())); + if (listing == null) { + log.error("MoneyMarketSecurityUpdateRequest id {} couldn't find listing with securityId {}", req.getId(), mms.getId()); + return null; + } + listing = updateListingByMms(mms, listing); + listingTxMap.update(listing); } - listing = updateListingByMms(mms, listing); - listingTxMap.update(listing); transaction.commitTransaction(); } catch (Throwable e) { log.error("Error at update security {}, rollback transaction. {}", req.getId(), ExceptionUtils.getStackTrace(e)); @@ -251,17 +258,35 @@ public class MoneyMarketSecurityService extends QueueConsumer implements Initial Instant updateTime = Instant.now(); MoneyMarketSecurity mms = validator.getStored(Stored.PresentById); if (mms == null) log.error("Validator return null stored mms object."); - mms.setWorkflowStatus(ru.spcex.platform.enumeration.Status.Blocked.getKey()); - mms.setUpdated(updateTime); - moneyMarketSecurityMap.update(mms); -// Listing listing = listingImdg.getSingleObjectByFieldValues(Map.of("securityId", mms.getId())); -// if (listing == null) { -// log.error("CommonDeleteRequest id {} couldn't find listing with securityId {}", req.getId(), mms.getId()); -// return null; -// } -// listing.setWorkflowStatus(ru.spcex.platform.enumeration.Status.Blocked.getKey()); -// listing.setUpdated(updateTime); -// listingImdg.update(listing); + ImdgTransaction transaction = imdgProvider.newTransaction(); + try { + synchronized (listingService) { + transaction.beginTransaction(); + Imdg moneyMarketSecurityTxMap = transaction.getImdg(IMDGDistributedNames.Map_MoneyMarketSecurity, MoneyMarketSecurity.class); + Imdg listingTxMap = transaction.getImdg(IMDGDistributedNames.Map_Listing, Listing.class); + + mms.setWorkflowStatus(WorkflowStatus.Blocked.getKey()); + mms.setUpdated(updateTime); + moneyMarketSecurityTxMap.update(mms); + Listing listing = listingTxMap.getSingleObjectByFieldValues(Map.of("securityId", mms.getId())); + if (listing == null) { + log.error("CommonDeleteRequest id {} couldn't find listing with securityId {}", req.getId(), mms.getId()); + return null; + } + listing.setWorkflowStatus(WorkflowStatus.Blocked.getKey()); + listing.setUpdated(updateTime); + listingTxMap.update(listing); + } + transaction.commitTransaction(); + } catch (Throwable e) { + log.error("Error at block security {}, rollback transaction. {}", req.getId(), ExceptionUtils.getStackTrace(e)); + transaction.rollbackTransaction(); + String errorMsg = messageResolver.resolve(new EnumMessage(SecuritiesError.GeneralError)); + return new RequestInfoUpdate() + .setId(userRequest.getId()) + .setStatus(Status.Error) + .setMessage(errorMsg); + } return null; } diff --git a/clearing-parent/securities-service/src/main/java/ru/spcex/clearing/securities/validation/ListingValidationProvider.java b/clearing-parent/securities-service/src/main/java/ru/spcex/clearing/securities/validation/ListingValidationProvider.java new file mode 100644 index 000000000..12f2a529c --- /dev/null +++ b/clearing-parent/securities-service/src/main/java/ru/spcex/clearing/securities/validation/ListingValidationProvider.java @@ -0,0 +1,173 @@ +package ru.spcex.clearing.securities.validation; + +import org.springframework.stereotype.Component; +import ru.clearing.classes.statics.data.misc.Listing; +import ru.clearing.classes.statics.data.security.Security; +import ru.spcex.clearing.imdg.IMDGDistributedNames; +import ru.spcex.clearing.platform.messaging.domain.cud.common.CommonDeleteRequest; +import ru.spcex.clearing.platform.messaging.domain.cud.securitites.*; +import ru.spcex.clearing.util.security.SecuritySelector; +import ru.spcex.clearing.securities.errors.SecuritiesError; +import ru.spcex.clearing.validation.common.rules.SpecialIdPresentRule; +import ru.spcex.platform.classes.base.SpcexObjectBase; +import ru.spcex.platform.enumeration.InstrumentType; +import ru.spcex.platform.enumeration.WorkflowStatus; +import ru.spcex.platform.imdg.api.Imdg; +import ru.spcex.platform.imdg.api.ImdgProvider; +import ru.spcex.platform.imdg.validation.ImdgValidationContext; +import ru.spcex.platform.imdg.validation.rule.PresentById; +import ru.spcex.platform.utils.enumeration.EnumMessage; +import ru.spcex.platform.utils.enumeration.IErrorEnumId; +import ru.spcex.platform.utils.validation.IValidationRule; +import ru.spcex.platform.utils.validation.IValidator; +import ru.spcex.platform.utils.validation.ValidatorImpl; + +import java.util.*; +import java.util.function.Function; + +@Component +public class ListingValidationProvider { + final Imdg workflowStatusDictionaryImdg; + final Imdg listingImdg; + final SecuritySelector securitySelector; + + + public ListingValidationProvider(ImdgProvider imdgProvider, SecuritySelector securitySelector) { + this.securitySelector = securitySelector; + this.workflowStatusDictionaryImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_WorkflowStatusDictionary, SpcexObjectBase.class); + this.listingImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Listing, Listing.class); + } + + + public Function listingNewValidator() { + return listingRequest -> { + ImdgValidationContext context = new ImdgValidationContext<>(); + context.setValidatedObject(listingRequest); + context.addImdg(IMDGDistributedNames.Map_WorkflowStatusDictionary, workflowStatusDictionaryImdg); + context.addImdg(IMDGDistributedNames.Map_Listing, listingImdg); + return new ValidatorImpl>(context, + new SecurityIdPresentRule("securityId", ListingNewRequest::getSecurityId, + (Security security) -> + InstrumentType.RATE.equalsByKey(security.getInstrumentType()) ? SecuritiesError.ListingOnMMSCreatedBySystem : null + ), + SpecialIdPresentRule.instance("market", (ListingNewRequest l) -> l.getMarket(), + "market", IMDGDistributedNames.Map_Market, + ru.clearing.classes.statics.data.misc.Market.class, + SecuritiesError.RequiredFieldIsEmpty, SecuritiesError.MarketNotFound, + true + ), + new NotSameActiveListing<>(ListingNewRequest::getSecurityId, ListingNewRequest::getMarket) + ); + }; + } + + public Function listingUpdateValidator() { + return mmsRequest -> { + ImdgValidationContext context = new ImdgValidationContext<>(); + context.setValidatedObject(mmsRequest); + context.addImdg(IMDGDistributedNames.Map_WorkflowStatusDictionary, workflowStatusDictionaryImdg); + context.addImdg(IMDGDistributedNames.Map_Listing, listingImdg); + return new ValidatorImpl<>(context, + new PresentById(IMDGDistributedNames.Map_Listing, SecuritiesError.ListingNotFound, true), + new SecurityIdPresentRule("securityId", ListingNewRequest::getSecurityId, + (Security security) -> + InstrumentType.RATE.equalsByKey(security.getInstrumentType()) ? SecuritiesError.ListingOnMMSCreatedBySystem : null + ) + ); + }; + } + + public Function listingDeleteValidator() { + return listingRequest -> { + ImdgValidationContext context = new ImdgValidationContext<>(); + context.setValidatedObject(listingRequest); + context.addImdg(IMDGDistributedNames.Map_Listing, listingImdg); + return new ValidatorImpl<>(context, + new PresentById(IMDGDistributedNames.Map_Listing, SecuritiesError.ListingNotFound, true), + new SecurityIdPresentRule("securityId", ListingNewRequest::getSecurityId, + (Security security) -> + InstrumentType.RATE.equalsByKey(security.getInstrumentType()) ? SecuritiesError.ListingOnMMSCreatedBySystem : null + ) + ); + }; + } + + class SecurityIdPresentRule implements IValidationRule> { + final String fieldName; + final Function idGetter; + final boolean required; + final IErrorEnumId errorEmptyField = SecuritiesError.RequiredFieldIsEmpty; + final IErrorEnumId errorIdNotPresent = SecuritiesError.InstrumentNotFound; + List> additionalChecks = new ArrayList<>(); + + public SecurityIdPresentRule(String fieldName, Function idGetter) { + this.fieldName = fieldName; + this.idGetter = idGetter; + this.required = true; + } + + public SecurityIdPresentRule(String fieldName, Function idGetter, Function... additional) { + this.fieldName = fieldName; + this.idGetter = idGetter; + this.required = true; + additionalChecks = Arrays.asList(additional); + } + + @Override + public Optional validate(ImdgValidationContext context) { + R validatedObject = context.getValidatedObject(); + Long id = idGetter.apply(validatedObject); + if (id == null) return required ? of(errorEmptyField, fieldName) : empty(); + Security fromMap = securitySelector.selectSecurityById(id); + if (fromMap == null) { + return of(errorIdNotPresent, fieldName); + } + for (Function additionalCheck : additionalChecks) { + IErrorEnumId validationError = additionalCheck.apply(fromMap); + if (validationError != null) + return of(validationError, fieldName); + } + return empty(); + } + + @Override + public String ruleName() { + return getClass().getSimpleName() + "{" + fieldName + "}"; + } + } + + static class NotSameActiveListing implements IValidationRule> { + final Function securityIdGetter; + final Function marketGetter; + + public NotSameActiveListing(Function securityIdGetter, Function marketGetter) { + this.securityIdGetter = securityIdGetter; + this.marketGetter = marketGetter; + } + + @Override + public Optional validate(ImdgValidationContext context) { + R validatedObject = context.getValidatedObject(); + Long securityId = securityIdGetter.apply(validatedObject); + if (securityId == null) return empty(); + String market = marketGetter.apply(validatedObject); + if (market == null) return empty(); + Imdg listingImdg = context.obtainMap(IMDGDistributedNames.Map_Listing, Listing.class); + Listing fromMap = listingImdg.getSingleObjectByFieldValues(Map.of( + "securityId", securityId, + "market", market, + "workflowStatus", WorkflowStatus.Active.getKey() + )); + if (fromMap == null) { + return empty(); + } else { + return of(SecuritiesError.ListingAlreadyExist, market, securityId); + } + } + + @Override + public String ruleName() { + return getClass().getSimpleName(); + } + } +} diff --git a/clearing-parent/securities-service/src/test/java/ru/spcex/clearing/securities/service/AbstractServiceTest.java b/clearing-parent/securities-service/src/test/java/ru/spcex/clearing/securities/service/AbstractServiceTest.java index b7f895ea1..85df3f211 100644 --- a/clearing-parent/securities-service/src/test/java/ru/spcex/clearing/securities/service/AbstractServiceTest.java +++ b/clearing-parent/securities-service/src/test/java/ru/spcex/clearing/securities/service/AbstractServiceTest.java @@ -37,6 +37,7 @@ import static ru.spcex.clearing.test.config.ImdgTestConfig.waitAvailableImdgProv EquitySecurityService.class, FixedIncomeSecurityService.class, MoneyMarketSecurityService.class, + ListingService.class, ImdgTestConfig.class, KafkaTestConfig.class, ValidationProvider.class,