ialbert 2026-07-01 15:31:56 +03:00
parent 8b643ced96
commit 031d09d7e3
11 changed files with 507 additions and 13 deletions

View file

@ -0,0 +1,61 @@
package ru.spcex.clearing.backendapi.controller.test;
import io.swagger.annotations.ApiModelProperty;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonProperty;
public class EditImdgObjectAction {
@ApiModelProperty(
value = "IMDG map name",
example = "Map_Registry"
)
@JsonProperty
private String mapName;
@ApiModelProperty(
value = "Full class name of IMDG object",
example = "ru.clearing.classes.statics.data.registry.Registry"
)
@JsonProperty
private String fullClassName;
@ApiModelProperty(value = "IMDG object id")
@JsonProperty
private Long objId;
@ApiModelProperty(value = "Fields and replacement values")
@JsonProperty
private Map<String, Object> fields;
public String getMapName() {
return mapName;
}
public void setMapName(String mapName) {
this.mapName = mapName;
}
public String getFullClassName() {
return fullClassName;
}
public void setFullClassName(String fullClassName) {
this.fullClassName = fullClassName;
}
public Long getObjId() {
return objId;
}
public void setObjId(Long objId) {
this.objId = objId;
}
public Map<String, Object> getFields() {
return fields;
}
public void setFields(Map<String, Object> fields) {
this.fields = fields;
}
}

View file

@ -0,0 +1,131 @@
package ru.spcex.clearing.backendapi.controller.test;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.lang.reflect.Field;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.util.ClassUtils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import ru.spcex.clearing.backendapi.errors.BackEndError;
import ru.spcex.platform.classes.base.SpcexObjectBase;
import ru.spcex.platform.imdg.api.Imdg;
import ru.spcex.platform.imdg.api.ImdgProvider;
import ru.spcex.platform.utils.enumeration.EnumMessage;
import ru.spcex.platform.utils.error.ValidationException;
import ru.spcex.platform.utils.text.TextUtil;
@Profile("dev")
@Controller
@RequestMapping("/anonymous/imdg-api")
public class EditObjectController {
private final Logger log = LoggerFactory.getLogger(getClass());
private static final ObjectMapper json = new ObjectMapper();
private final ImdgProvider imdgProvider;
@Autowired
public EditObjectController(ImdgProvider imdgProvider) {
this.imdgProvider = imdgProvider;
}
@ApiOperation(value = "Edit any object.")
@ApiResponses(value = {@ApiResponse(code = 200, message = "OK", response = String.class)})
@RequestMapping(method = RequestMethod.POST, path = "/edit/object", produces = MediaType.TEXT_PLAIN_VALUE)
@ResponseBody
public String updateObjectById(@ApiParam(value = "Command params in JSON format.", required = true)
@RequestBody EditImdgObjectAction action) throws ClassNotFoundException, ValidationException, IllegalAccessException {
log.info("Edit any object method");
validate(action);
Class<?> rawParameterType = ClassUtils.forName(action.getFullClassName(), ClassUtils.getDefaultClassLoader());
if (!SpcexObjectBase.class.isAssignableFrom(rawParameterType)) {
throw new ValidationException(new EnumMessage(BackEndError.ValidationError, "fullClassName"));
}
Class<? extends SpcexObjectBase> parameterType = rawParameterType.asSubclass(SpcexObjectBase.class);
Long id = action.getObjId();
String mapName = action.getMapName();
Imdg<SpcexObjectBase> imdg = (Imdg<SpcexObjectBase>) imdgProvider.getImdg(mapName, parameterType);
SpcexObjectBase objectToUpdate = imdg.getSingleObjectByID(id);
if (objectToUpdate == null) {
throw new ValidationException(new EnumMessage(BackEndError.ResourceNotFound, "id"));
}
setFields(objectToUpdate, action.getFields());
imdg.update(objectToUpdate);
return "success: object.id = " + id;
}
private void validate(EditImdgObjectAction action) throws ValidationException {
if (TextUtil.isEmpty(action.getMapName())) {
throw new ValidationException(new EnumMessage(BackEndError.ValidationError, "mapName"));
}
if (TextUtil.isEmpty(action.getFullClassName())) {
throw new ValidationException(new EnumMessage(BackEndError.ValidationError, "fullClassName"));
}
if (action.getObjId() == null) {
throw new ValidationException(new EnumMessage(BackEndError.ValidationError, "objId"));
}
if (action.getFields() == null || action.getFields().isEmpty()) {
throw new ValidationException(new EnumMessage(BackEndError.ValidationError, "fields"));
}
}
private void setFields(SpcexObjectBase objectToUpdate, Map<String, Object> fieldsToChange) throws ValidationException, IllegalAccessException {
for (Map.Entry<String, Object> fieldToChange : fieldsToChange.entrySet()) {
Field field = findField(objectToUpdate.getClass(), fieldToChange.getKey());
field.setAccessible(true);
field.set(objectToUpdate, toFieldValue(field, fieldToChange.getValue()));
}
}
private Field findField(Class<?> objectClass, String fieldName) throws ValidationException {
Class<?> currentClass = objectClass;
while (currentClass != null) {
try {
return currentClass.getDeclaredField(fieldName);
} catch (NoSuchFieldException ignored) {
currentClass = currentClass.getSuperclass();
}
}
throw new ValidationException(new EnumMessage(BackEndError.ValidationError, "fields." + fieldName));
}
private Object toFieldValue(Field field, Object value) throws ValidationException {
if (value == null) {
if (field.getType().isPrimitive()) {
throw new ValidationException(new EnumMessage(BackEndError.ValidationError, field.getName()));
}
return null;
}
Class<?> fieldType = field.getType();
if (fieldType.isAssignableFrom(value.getClass())) {
return value;
}
if (fieldType.isEnum() && value instanceof String) {
return Enum.valueOf((Class<Enum>) fieldType.asSubclass(Enum.class), (String) value);
}
if (fieldType == LocalDate.class && value instanceof String) {
return LocalDate.parse((String) value);
}
if (fieldType == LocalDateTime.class && value instanceof String) {
return LocalDateTime.parse((String) value);
}
if (fieldType == LocalTime.class && value instanceof String) {
return LocalTime.parse((String) value);
}
return json.convertValue(value, json.getTypeFactory().constructType(field.getGenericType()));
}
}

View file

@ -0,0 +1,32 @@
package ru.spcex.clearing.service;
import java.util.Collection;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import ru.clearing.classes.statics.data.misc.Session;
import ru.spcex.clearing.imdg.IMDGDistributedNames;
import ru.spcex.platform.enumeration.WorkflowStatus;
import ru.spcex.platform.imdg.api.Imdg;
import ru.spcex.platform.imdg.api.ImdgProvider;
@Service
public class IsActiveSessionRunningService {
private final Logger log = LoggerFactory.getLogger(getClass());
private final Imdg<Session> ssnImdg;
public IsActiveSessionRunningService(ImdgProvider imdgProvider) {
this.ssnImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Session, Session.class);
}
public boolean isThereAnActiveSession() {
Collection<Session> activeSessions = ssnImdg.getCollectionObjectsByFieldValues(Map.of(
"workflowStatus", WorkflowStatus.Active.getKey()
));
activeSessions.stream().findFirst().ifPresent(
session -> log.debug("found session.id {}", session.getId())
);
return !activeSessions.isEmpty();
}
}

View file

@ -1,5 +1,14 @@
package ru.spcex.clearing.service.executors;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@ -25,12 +34,24 @@ import ru.spcex.clearing.platform.messaging.domain.cud.gateway.AssetOperationApp
import ru.spcex.clearing.platform.messaging.domain.cud.gateway.SingleAssetResponse;
import ru.spcex.clearing.platform.messaging.domain.cud.importexport.SwtExporterRequest;
import ru.spcex.clearing.platform.messaging.service.sender.KafkaSender;
import ru.spcex.clearing.service.IsActiveSessionRunningService;
import ru.spcex.clearing.service.registry.AssetTBFProcessing;
import ru.spcex.clearing.service.registry.RegistryManager;
import ru.spcex.clearing.service.schedule.TasksDelayedBySessionServiceV2;
import ru.spcex.clearing.service.schedule.TradingTimeService;
import ru.spcex.clearing.service.validation.ValidationStored;
import ru.spcex.clearing.session.stage.util.RegistryUtil;
import ru.spcex.platform.enumeration.*;
import ru.spcex.platform.enumeration.InOutDirection;
import ru.spcex.platform.enumeration.InOutSDfType;
import ru.spcex.platform.enumeration.OperationStatus;
import ru.spcex.platform.enumeration.RegistryDesignation;
import ru.spcex.platform.enumeration.RegistryStatus;
import ru.spcex.platform.enumeration.RegistryTradingParams;
import ru.spcex.platform.enumeration.RegistryUnit;
import ru.spcex.platform.enumeration.SdfTable;
import ru.spcex.platform.enumeration.Sender;
import ru.spcex.platform.enumeration.StatementType;
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;
@ -39,18 +60,8 @@ import ru.spcex.platform.imdg.api.predicate.specific.SecuritySelector;
import ru.spcex.platform.utils.enumeration.EnumMessage;
import ru.spcex.platform.utils.enumeration.IEnumId;
import ru.spcex.platform.utils.enumeration.IMessageResolver;
import ru.spcex.platform.utils.validation.IValidator;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import static ru.spcex.platform.utils.number.BigDecimalUtil.safeBD;
import ru.spcex.platform.utils.validation.IValidator;
@Service
public class Sdf10Executor {
@ -70,6 +81,8 @@ public class Sdf10Executor {
private final KafkaSender kafkaSender;
private final SecuritySelector<Security> scrtSlct;
private final AssetTBFProcessing assets;
private final TasksDelayedBySessionServiceV2 delayedService;
private final IsActiveSessionRunningService sessionChecker;
private final static String OK = "OK";
private final static String SYNTAX_ERROR = "Синтаксическая ошибка (файл сформирован неверно)";
@ -90,7 +103,7 @@ public class Sdf10Executor {
public Sdf10Executor(ImdgProvider imdgProvider,
RegistryManager rgsMng, IMessageResolver messageResolver,
@Qualifier("sdf10Validator") Function<SDf10, IValidator> sDf10Validator,
TradingTimeService tradingTimeService, KafkaSender kafkaSender, AssetTBFProcessing assets) {
TradingTimeService tradingTimeService, KafkaSender kafkaSender, AssetTBFProcessing assets, TasksDelayedBySessionServiceV2 delayedService, IsActiveSessionRunningService sessionChecker) {
this.imdgProvider = imdgProvider;
this.statementImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Statement, Statement.class);
this.registryImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Registry, Registry.class);
@ -102,9 +115,11 @@ public class Sdf10Executor {
this.kafkaSender = kafkaSender;
this.sdf10Imdg = imdgProvider.getImdg(IMDGDistributedNames.Map_SDf10, SDf10.class);
this.sdf11Imdg = imdgProvider.getImdg(IMDGDistributedNames.Map_SDf11, SDf11.class);
this.sessionChecker = sessionChecker;
this.plannerAllTodayImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_PlannerAllToday, PlannerAllToday.class);
this.scrtSlct = new SecuritySelector<>(imdgProvider, Security.class);
this.assets = assets;
this.delayedService = delayedService;
}
public void execute(BaseRequest<StatementRequest> systemRequest) {
@ -125,9 +140,21 @@ public class Sdf10Executor {
sendToExporter(sdf11GroupId);
return;
}
if (sessionChecker.isThereAnActiveSession()) {
log.info("delaying SDF10 execution for groupId={} - session is active", groupId);
pauseAll(sdfs);
delayedService.subscribe(groupId);
return;
}
Collection<AssetOperationRequest> requests = new ArrayList<>();
boolean sdf11WasCreated = false;
for (SDf10 sDf10 : sdfs) {
if (systemRequest.getRequestPayload().isDelayed() && !WorkflowStatus.Pause.equalsByKey(sDf10.getWorkflowStatus())) {
log.warn("got delayed SDF10 request groupId: {}. sdf10.id {} is not in {} status.", groupId, sDf10.getId(), WorkflowStatus.Pause.getKey());
continue;
}
sDf10.setWorkflowStatus(WorkflowStatus.Blocked.getKey());
sdf10Imdg.update(sDf10);
IValidator validator = sDf10Validator.apply(sDf10);
Optional<EnumMessage> err = validator.tillFirstError();
Security security = validator.getStored(ValidationStored.SecurityBySecurityCode);
@ -188,6 +215,18 @@ public class Sdf10Executor {
}
}
private void pauseAll(Collection<SDf10> sdfs) {
Map<Long, SDf10> sdfById = sdfs.stream()
.collect(Collectors.toMap(
SDf10::getId,
sdf -> {
sdf.setWorkflowStatus(WorkflowStatus.Pause.getKey());
return sdf;
}
));
sdf10Imdg.putAll(sdfById);
}
private void createDs_iResponseFromGateway(Long accountId, String securitySymbol,
BigDecimal summ, String outDocument) {
Optional<Registry> as_t = rgsMng.searchByAccSec(accountId, securitySymbol, RegistryTradingParams.AS_T);

View file

@ -0,0 +1,143 @@
package ru.spcex.clearing.service.schedule;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import ru.clearing.classes.statics.data.misc.Session;
import ru.clearing.classes.statics.data.sdf.SDf10;
import ru.spcex.clearing.imdg.IMDGDistributedNames;
import ru.spcex.clearing.platform.messaging.domain.Consts;
import ru.spcex.clearing.platform.messaging.domain.cud.balance.StatementRequest;
import ru.spcex.clearing.platform.messaging.service.sender.KafkaSender;
import ru.spcex.clearing.service.IsActiveSessionRunningService;
import ru.spcex.platform.enumeration.SdfTable;
import ru.spcex.platform.enumeration.WorkflowStatus;
import ru.spcex.platform.imdg.api.Imdg;
import ru.spcex.platform.imdg.api.ImdgEntryListener;
import ru.spcex.platform.imdg.api.ImdgProvider;
import ru.spcex.platform.imdg.api.listener.ImdgEntryEvent;
import ru.spcex.platform.imdg.api.predicate.ImdgPredicateBuilder;
import ru.spcex.platform.utils.log.ExceptionUtils;
import ru.spcex.platform.utils.time.TimeUtil;
@Service
public class TasksDelayedBySessionServiceV2 implements ImdgEntryListener<Session>,
DisposableBean, InitializingBean {
private final Logger log = LoggerFactory.getLogger(getClass());
private final Set<Long> groupIds;
private final ExecutorService singleThreadExecutor;
private final KafkaSender kafka;
private final Imdg<SDf10> sdf10Imdg;
private final ImdgProvider imdgProvider;
private final IsActiveSessionRunningService sessionChecker;
@Autowired
public TasksDelayedBySessionServiceV2(@Qualifier("kafkaSenderWithoutRequestInfo")
KafkaSender kafka, ImdgProvider imdgProvider, IsActiveSessionRunningService sessionChecker) {
this.kafka = kafka;
this.imdgProvider = imdgProvider;
this.sdf10Imdg = imdgProvider.getImdg(IMDGDistributedNames.Map_SDf10, SDf10.class);
this.sessionChecker = sessionChecker;
this.groupIds = new LinkedHashSet<>();
this.singleThreadExecutor = Executors.newSingleThreadExecutor();
}
@Override
public void afterPropertiesSet() throws Exception {
submitWrap(this::checkDelayedStatusesOnStartup);
this.imdgProvider.waitAvailable();
Imdg<Session> ssnImdg = this.imdgProvider.getImdg(IMDGDistributedNames.Map_Session, Session.class);
ssnImdg.addListener(this);
}
private void checkDelayedStatusesOnStartup() {
imdgProvider.waitAvailable();
ImdgPredicateBuilder pb = sdf10Imdg.predicateBuilder();
Collection<SDf10> sdfs = sdf10Imdg.getCollectionObjectsByPredicate(
pb.and(
pb.greatEqual("generationTime", TimeUtil.localDateToInstant(LocalDate.now())),
pb.equals("workflowStatus", WorkflowStatus.Pause.getKey())
)
);
List<Long> groupIds = sdfs.stream()
.map(SDf10::getGenerationId)
.distinct()
.toList();
log.info("{} sdf10 on startup; {} groups", sdfs.size(), groupIds.size());
if (sessionChecker.isThereAnActiveSession()) {
log.info("there is an active session: sdf10 stay delayed");
synchronized (this.groupIds) {
groupIds.forEach(this::subscribe);
}
} else {
groupIds.forEach(this::notifyEventReceiver);
}
}
public void subscribe(Long groupId) {
synchronized (groupIds) {
groupIds.add(groupId);
}
log.info("sdf10 groupId={} delayed", groupId);
}
@Override
public void innerEntryUpdated(ImdgEntryEvent<Session> event) {
if (notSessionEndEvent(event.getOldValue(), event.getValue())) {
log.trace("not a session end event.");
return;
}
synchronized (groupIds) {
List<Long> filtered = new ArrayList<>(groupIds);
groupIds.clear();
submitWrap(() -> filtered.forEach(this::notifyEventReceiver));
}
}
private boolean notSessionEndEvent(Session sessionOldValue, Session sessionNewValue) {
if (sessionOldValue == null || sessionNewValue == null) {
log.warn("old value null {}, new value null {}",
sessionOldValue == null, sessionNewValue == null);
return true;
}
String oldWS = sessionOldValue.getWorkflowStatus();
String newWS = sessionNewValue.getWorkflowStatus();
log.trace("workflow statuses: {} -> {}", oldWS, newWS);
return !(WorkflowStatus.Active.equalsByKey(oldWS) && !WorkflowStatus.Active.equalsByKey(newWS));
}
private void notifyEventReceiver(Long groupId) {
StatementRequest request = new StatementRequest();
request.setGroupId(groupId);
request.setTable(SdfTable.SDF_10);
request.setDelayed(true);
kafka.sendRequestToQueue(Consts.STATEMENT_PROCESS, request);
}
private void submitWrap(Runnable task) {
singleThreadExecutor.submit(() -> {
try {
task.run();
} catch (Exception e) {
log.error(ExceptionUtils.getStackTrace(e));
}
});
}
@Override
public void destroy() {
this.singleThreadExecutor.close();
}
}

View file

@ -20,6 +20,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.spcex.platform.classes.base.SpcexObjectBase;
import ru.spcex.platform.imdg.api.Imdg;
import ru.spcex.platform.imdg.api.ImdgEntryListener;
import ru.spcex.platform.imdg.api.predicate.ImdgPredicate;
import ru.spcex.platform.imdg.api.predicate.ImdgPredicateBuilder;
import ru.spcex.platform.imdg.iml.hazelcast.adapter.predicate.ImdgPredicateBuilderHazelcast;
@ -350,4 +351,9 @@ public class ImdgHazelcast<T extends SpcexObjectBase> implements Imdg<T> {
public ImdgPredicateBuilder predicateBuilder() {
return ImdgPredicateBuilderHazelcast.instance();
}
@Override
public void addListener(ImdgEntryListener<T> listener) {
map.addEntryListener(new ImdgHazelcastListener<>(listener), true);
}
}

View file

@ -0,0 +1,29 @@
package ru.spcex.platform.imdg.iml.hazelcast.adapter;
import com.hazelcast.core.EntryEvent;
import com.hazelcast.map.listener.EntryAddedListener;
import com.hazelcast.map.listener.EntryUpdatedListener;
import ru.spcex.platform.imdg.api.ImdgEntryListener;
import ru.spcex.platform.imdg.api.listener.ImdgEntryEvent;
public class ImdgHazelcastListener<T> implements
EntryUpdatedListener<Long, T>,
EntryAddedListener<Long, T>
{
private final ImdgEntryListener<T> delegate;
public ImdgHazelcastListener(ImdgEntryListener<T> delegate) {
this.delegate = delegate;
}
@Override
public void entryUpdated(EntryEvent<Long, T> event) {
this.delegate.innerEntryUpdated(new ImdgEntryEvent<>(event.getValue(), event.getOldValue(), event.getKey()));
}
@Override
public void entryAdded(EntryEvent<Long, T> event) {
this.delegate.innerEntryAdded(new ImdgEntryEvent<>(event.getValue(), event.getOldValue(), event.getKey()));
}
}

View file

@ -133,4 +133,8 @@ public interface Imdg<T extends SpcexObjectBase> {
default ImdgPredicateBuilder predicateBuilder() {
throw new UnsupportedOperationException("not implemented predicateBuilder API");
}
default void addListener(ImdgEntryListener<T> listener) {
throw new UnsupportedOperationException("not implemented addListener");
}
}

View file

@ -0,0 +1,14 @@
package ru.spcex.platform.imdg.api;
import ru.spcex.platform.imdg.api.listener.ImdgEntryEvent;
public interface ImdgEntryListener<T> {
default void innerEntryAdded(ImdgEntryEvent<T> event) {
}
default void innerEntryUpdated(ImdgEntryEvent<T> event) {
}
}

View file

@ -0,0 +1,25 @@
package ru.spcex.platform.imdg.api.listener;
public class ImdgEntryEvent<T> {
private final T value;
private final T oldValue;
private final Long key;
public ImdgEntryEvent(T value, T oldValue, Long key) {
this.value = value;
this.oldValue = oldValue;
this.key = key;
}
public T getValue() {
return value;
}
public T getOldValue() {
return oldValue;
}
public Long getKey() {
return key;
}
}

View file

@ -20,6 +20,8 @@ public class StatementRequest {
private Long childGenerationId;
@JsonProperty
private boolean continueSdf = false;
@JsonProperty
private boolean delayed = false;
public Long getGroupId() {
return groupId;
@ -68,4 +70,12 @@ public class StatementRequest {
public void setFromAccount(boolean fromAccount) {
this.fromAccount = fromAccount;
}
public boolean isDelayed() {
return delayed;
}
public void setDelayed(boolean delayed) {
this.delayed = delayed;
}
}