This commit is contained in:
parent
0a9af63689
commit
6f6ace106d
29 changed files with 479 additions and 66 deletions
|
|
@ -8,14 +8,13 @@ import java.util.Locale;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
public class MessagesConfig {
|
public class MessagesConfig {
|
||||||
@Bean
|
@Bean("validation-error-messages")
|
||||||
public ResourceBundleMessageSource messages() {
|
public ResourceBundleMessageSource messages() {
|
||||||
ResourceBundleMessageSource source = new ResourceBundleMessageSource();
|
ResourceBundleMessageSource source = new ResourceBundleMessageSource();
|
||||||
source.setBasenames("messages/response");
|
source.setBasenames("messages/error");
|
||||||
source.setUseCodeAsDefaultMessage(true);
|
source.setUseCodeAsDefaultMessage(true);
|
||||||
source.setDefaultEncoding("utf8");
|
source.setDefaultEncoding("utf8");
|
||||||
source.setDefaultLocale(Locale.ROOT);
|
source.setDefaultLocale(Locale.ROOT);
|
||||||
|
|
||||||
return source;
|
return source;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,95 +1,88 @@
|
||||||
package ru.spcex.clearing.backendapi.controller.cud;
|
package ru.spcex.clearing.backendapi.controller.cud;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import io.swagger.annotations.*;
|
import io.swagger.annotations.ApiOperation;
|
||||||
import org.apache.kafka.clients.producer.Producer;
|
import io.swagger.annotations.ApiParam;
|
||||||
import org.apache.kafka.clients.producer.ProducerRecord;
|
import io.swagger.annotations.ApiResponse;
|
||||||
import org.apache.kafka.clients.producer.RecordMetadata;
|
import io.swagger.annotations.ApiResponses;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import ru.spcex.clearing.backendapi.controller.response.BasicSpcexResponse;
|
import ru.spcex.clearing.backendapi.controller.response.BasicSpcexResponse;
|
||||||
|
import ru.spcex.clearing.backendapi.controller.response.cud.CudResponse;
|
||||||
import ru.spcex.clearing.backendapi.domain.actions.IAction;
|
import ru.spcex.clearing.backendapi.domain.actions.IAction;
|
||||||
import ru.spcex.clearing.backendapi.meta.CudMetaService;
|
import ru.spcex.clearing.backendapi.meta.CudMetaService;
|
||||||
|
import ru.spcex.clearing.backendapi.service.IOperator;
|
||||||
|
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.Future;
|
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
@RequestMapping("/cud")
|
@RequestMapping("/cud")
|
||||||
public class CudController {
|
public class CudController {
|
||||||
private final Producer<String, Object> kafka;
|
private final IOperator operator;
|
||||||
private final ObjectMapper json;
|
private final ObjectMapper json;
|
||||||
private final CudMetaService meta;
|
private final CudMetaService meta;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public CudController(Producer<String, Object> kafka, CudMetaService meta) {
|
public CudController(IOperator operator, CudMetaService meta) {
|
||||||
this.kafka = kafka;
|
|
||||||
this.meta = meta;
|
this.meta = meta;
|
||||||
this.json = new ObjectMapper();
|
this.json = new ObjectMapper();
|
||||||
|
this.operator = operator;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOperation(value = "create/update/delete business objects. See meta.xml for field descriptions.")
|
@ApiOperation(value = "create/update/delete business objects. See meta.xml for field descriptions.")
|
||||||
@ApiResponses(value = {@ApiResponse(code = 200, message = "OK", response = MetaDataResponse.class)})
|
@ApiResponses(value = {@ApiResponse(code = 200, message = "OK", response = CudResponse.class), @ApiResponse(code = 400, message = "Ошибка валидации", response = BasicSpcexResponse.class)})
|
||||||
@RequestMapping(value = "/{destination}", method = RequestMethod.POST)
|
@RequestMapping(value = "/{destination}", method = RequestMethod.POST)
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public MetaDataResponse add(
|
public CudResponse add(
|
||||||
@ApiParam(value = "Последняя часть URL определяет 'направление', по которому пойдет запрос. " +
|
@ApiParam(value = "Последняя часть URL определяет 'направление', по которому пойдет запрос. " +
|
||||||
"Должно биться с форматом запроса.", required = true, example = "money-market-security-new")
|
"Должно биться с форматом запроса.", required = true, example = "money-market-security-new")
|
||||||
@PathVariable("destination")
|
@PathVariable("destination")
|
||||||
String destination,
|
String destination,
|
||||||
@ApiParam(value = "Параметры команды в JSON формате, поля см. в meta.xml.", required = true)
|
@ApiParam(value = "Параметры команды в JSON формате, поля см. в meta.xml.", required = true)
|
||||||
@RequestBody String body) throws JsonProcessingException, ExecutionException, InterruptedException {
|
@RequestBody String body) throws JsonProcessingException, ExecutionException, InterruptedException {
|
||||||
|
return processRequest(destination, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation(value = "create/update/delete business objects. See meta.xml for field descriptions.")
|
||||||
|
@ApiResponses(value = {@ApiResponse(code = 200, message = "OK", response = CudResponse.class), @ApiResponse(code = 400, message = "Ошибка валидации", response = BasicSpcexResponse.class)})
|
||||||
|
@RequestMapping(value = "/{destination}", method = RequestMethod.PUT)
|
||||||
|
@ResponseBody
|
||||||
|
public CudResponse update(
|
||||||
|
@ApiParam(value = "Последняя часть URL определяет 'направление', по которому пойдет запрос. " +
|
||||||
|
"Должно биться с форматом запроса.", required = true, example = "money-market-security-new")
|
||||||
|
@PathVariable("destination")
|
||||||
|
String destination,
|
||||||
|
@ApiParam(value = "Параметры команды в JSON формате, поля см. в meta.xml.", required = true)
|
||||||
|
@RequestBody String body) throws JsonProcessingException, ExecutionException, InterruptedException {
|
||||||
|
return processRequest(destination, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation(value = "delete business objects. See meta.xml for field descriptions.")
|
||||||
|
@ApiResponses(value = {@ApiResponse(code = 200, message = "OK", response = CudResponse.class), @ApiResponse(code = 400, message = "Ошибка валидации", response = BasicSpcexResponse.class)})
|
||||||
|
@RequestMapping(value = "/{destination}", method = RequestMethod.DELETE)
|
||||||
|
@ResponseBody
|
||||||
|
public CudResponse delete(
|
||||||
|
@ApiParam(value = "Последняя часть URL определяет 'направление', по которому пойдет запрос. " +
|
||||||
|
"Должно биться с форматом запроса.", required = true, example = "money-market-security-new")
|
||||||
|
@PathVariable("destination")
|
||||||
|
String destination,
|
||||||
|
@ApiParam(value = "Параметры команды в JSON формате, поля см. в meta.xml.", required = true)
|
||||||
|
@RequestBody String body) throws JsonProcessingException, ExecutionException, InterruptedException {
|
||||||
|
return processRequest(destination, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CudResponse processRequest(String destination, String body) throws ExecutionException, InterruptedException, JsonProcessingException {
|
||||||
Class<IAction<?>> actionClazz = meta.byDestination(destination);
|
Class<IAction<?>> actionClazz = meta.byDestination(destination);
|
||||||
if (actionClazz == null) {
|
if (actionClazz == null) {
|
||||||
throw new UnsupportedOperationException("unsupported destination " + destination);
|
throw new UnsupportedOperationException("unsupported destination " + destination);
|
||||||
}
|
}
|
||||||
IAction<?> iAction = json.readValue(body, actionClazz);
|
IAction<?> iAction = json.readValue(body, actionClazz);
|
||||||
Future<RecordMetadata> send = kafka.send(new ProducerRecord<>(destination, iAction.toRequest()));
|
CudResponse responseToClient = new CudResponse();
|
||||||
RecordMetadata kafkaMetaData = send.get();
|
responseToClient.setResponse(operator.sendRequestToQueue(destination, iAction));
|
||||||
MetaDataResponse responseToClient = new MetaDataResponse();
|
responseToClient.setCode(0);
|
||||||
responseToClient.setOffset(kafkaMetaData.offset());
|
responseToClient.setMessage("success");
|
||||||
responseToClient.setPartition(kafkaMetaData.partition());
|
|
||||||
responseToClient.setTopic(kafkaMetaData.topic());
|
|
||||||
return responseToClient;
|
return responseToClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiModel(description="Ответ в результате отправки операции в топик Kafka.")
|
|
||||||
private static class MetaDataResponse extends BasicSpcexResponse {
|
|
||||||
@ApiModelProperty(value="Offset положенного сообщения")
|
|
||||||
@JsonProperty
|
|
||||||
private Long offset;
|
|
||||||
@ApiModelProperty(value="Partition положенного сообщения")
|
|
||||||
@JsonProperty
|
|
||||||
private Integer partition;
|
|
||||||
@ApiModelProperty(value="Название топика, в который было положено сообщение")
|
|
||||||
@JsonProperty
|
|
||||||
private String topic;
|
|
||||||
|
|
||||||
public Long getOffset() {
|
|
||||||
return offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOffset(Long offset) {
|
|
||||||
this.offset = offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getPartition() {
|
|
||||||
return partition;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPartition(Integer partition) {
|
|
||||||
this.partition = partition;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTopic() {
|
|
||||||
return topic;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTopic(String topic) {
|
|
||||||
this.topic = topic;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
package ru.spcex.clearing.backendapi.controller.cud;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
|
||||||
|
import ru.spcex.clearing.backendapi.controller.response.BasicSpcexResponse;
|
||||||
|
import ru.spcex.clearing.backendapi.errors.ActionValidationException;
|
||||||
|
import ru.spcex.platform.utils.enumeration.EnumMessage;
|
||||||
|
import ru.spcex.platform.utils.enumeration.IMessageResolver;
|
||||||
|
import ru.spcex.platform.utils.log.ExceptionUtils;
|
||||||
|
|
||||||
|
@ControllerAdvice("ru.spcex.clearing.backendapi.controller.cud")
|
||||||
|
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||||
|
public class CudExceptionHandler extends ResponseEntityExceptionHandler {
|
||||||
|
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||||
|
private final IMessageResolver errorResolver;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public CudExceptionHandler(@Qualifier("errorResolver") IMessageResolver errorResolver) {
|
||||||
|
this.errorResolver = errorResolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(value = ActionValidationException.class)
|
||||||
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||||
|
@ResponseBody
|
||||||
|
public BasicSpcexResponse handleValidationException(ActionValidationException e) {
|
||||||
|
log.error("Default scoring exception handler: {}", ExceptionUtils.getStackTrace(e));
|
||||||
|
EnumMessage firstError = e.getErrors().iterator().next();
|
||||||
|
BasicSpcexResponse errorResponse = new BasicSpcexResponse();
|
||||||
|
errorResponse.setCode(firstError.getSubject().getId());
|
||||||
|
errorResponse.setMessage(errorResolver.resolve(firstError));
|
||||||
|
return errorResponse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,10 +4,15 @@ import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
import ru.spcex.clearing.backendapi.domain.actions.IAction;
|
import ru.spcex.clearing.backendapi.domain.actions.IAction;
|
||||||
|
import ru.spcex.clearing.backendapi.errors.BackEndError;
|
||||||
import ru.spcex.clearing.platform.messaging.domain.cud.securitites.MoneyMarketSecurityNewRequest;
|
import ru.spcex.clearing.platform.messaging.domain.cud.securitites.MoneyMarketSecurityNewRequest;
|
||||||
import ru.spcex.clearing.platform.messaging.domain.json.deserialize.InstantDeserializer;
|
import ru.spcex.clearing.platform.messaging.domain.json.deserialize.InstantDeserializer;
|
||||||
|
import ru.spcex.platform.utils.enumeration.EnumMessage;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class MoneyMarketSecurityNewAction implements IAction<MoneyMarketSecurityNewRequest> {
|
public class MoneyMarketSecurityNewAction implements IAction<MoneyMarketSecurityNewRequest> {
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "Europe/Moscow")
|
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "Europe/Moscow")
|
||||||
|
|
@ -31,6 +36,13 @@ public class MoneyMarketSecurityNewAction implements IAction<MoneyMarketSecurity
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
public Double lotSize;
|
public Double lotSize;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<EnumMessage> validate() {
|
||||||
|
if (this.startDate == null)
|
||||||
|
return List.of(new EnumMessage(BackEndError.ValidationError, "startDate"));
|
||||||
|
else return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MoneyMarketSecurityNewRequest toRequest() {
|
public MoneyMarketSecurityNewRequest toRequest() {
|
||||||
var req = new MoneyMarketSecurityNewRequest();
|
var req = new MoneyMarketSecurityNewRequest();
|
||||||
|
|
|
||||||
|
|
@ -6,15 +6,15 @@ import io.swagger.annotations.ApiModelProperty;
|
||||||
@ApiModel(description="Базовый формат ответа")
|
@ApiModel(description="Базовый формат ответа")
|
||||||
public class BasicSpcexResponse {
|
public class BasicSpcexResponse {
|
||||||
@ApiModelProperty(value="Код ответа (успешный 0)", required = true)
|
@ApiModelProperty(value="Код ответа (успешный 0)", required = true)
|
||||||
private int code;
|
private long code;
|
||||||
@ApiModelProperty(value="Сообщение ответа")
|
@ApiModelProperty(value="Сообщение ответа")
|
||||||
private String message;
|
private String message;
|
||||||
|
|
||||||
public int getCode() {
|
public long getCode() {
|
||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCode(int code) {
|
public void setCode(long code) {
|
||||||
this.code = code;
|
this.code = code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package ru.spcex.clearing.backendapi.controller.response.cud;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import ru.spcex.clearing.backendapi.controller.response.BasicSpcexResponse;
|
||||||
|
|
||||||
|
@ApiModel(description = "Ответ в результате отправки операции в топик Kafka.")
|
||||||
|
public class CudResponse extends BasicSpcexResponse {
|
||||||
|
@ApiModelProperty(value = "Информация о принятом запросе")
|
||||||
|
QueueSuccessResponse response;
|
||||||
|
|
||||||
|
public QueueSuccessResponse getResponse() {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResponse(QueueSuccessResponse response) {
|
||||||
|
this.response = response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package ru.spcex.clearing.backendapi.controller.response.cud;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import ru.spcex.clearing.platform.messaging.domain.ActionType;
|
||||||
|
|
||||||
|
public class QueueSuccessResponse {
|
||||||
|
@ApiModelProperty(value = "Тип запроса")
|
||||||
|
@JsonProperty
|
||||||
|
private final ActionType action;
|
||||||
|
@ApiModelProperty(value = "Идентификатор запроса")
|
||||||
|
@JsonProperty
|
||||||
|
private final Long id;
|
||||||
|
|
||||||
|
public QueueSuccessResponse(ActionType action, Long id) {
|
||||||
|
this.action = action;
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActionType getAction() {
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,12 @@
|
||||||
package ru.spcex.clearing.backendapi.domain.actions;
|
package ru.spcex.clearing.backendapi.domain.actions;
|
||||||
|
|
||||||
public interface IAction<T> {
|
import ru.spcex.clearing.platform.messaging.domain.BaseRequest;
|
||||||
public T toRequest();
|
import ru.spcex.platform.utils.enumeration.EnumMessage;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
public interface IAction<T extends BaseRequest> {
|
||||||
|
T toRequest();
|
||||||
|
default Collection<EnumMessage> validate() {return Collections.emptyList();}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
package ru.spcex.clearing.backendapi.errors;
|
||||||
|
|
||||||
|
import ru.spcex.platform.utils.enumeration.EnumMessage;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ActionValidationException extends RuntimeException {
|
||||||
|
protected Collection<EnumMessage> errors;
|
||||||
|
|
||||||
|
public ActionValidationException(Collection<EnumMessage> errors) {
|
||||||
|
this.errors = errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActionValidationException(EnumMessage error) {
|
||||||
|
this.errors = List.of(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<EnumMessage> getErrors() {
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package ru.spcex.clearing.backendapi.errors;
|
||||||
|
|
||||||
|
import ru.spcex.platform.utils.enumeration.IEnumId;
|
||||||
|
|
||||||
|
public enum BackEndError implements IEnumId {
|
||||||
|
ValidationError(3000L);
|
||||||
|
private final Long id;
|
||||||
|
|
||||||
|
BackEndError(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package ru.spcex.clearing.backendapi.errors;
|
||||||
|
|
||||||
|
import org.springframework.context.support.ResourceBundleMessageSource;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import ru.spcex.platform.utils.enumeration.EnumMessage;
|
||||||
|
import ru.spcex.platform.utils.enumeration.IMessageResolver;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
@Service("errorResolver")
|
||||||
|
public class ErrorResolver implements IMessageResolver {
|
||||||
|
private final ResourceBundleMessageSource errorMessages;
|
||||||
|
|
||||||
|
public ErrorResolver(ResourceBundleMessageSource errorMessages) {
|
||||||
|
this.errorMessages = errorMessages;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String resolve(EnumMessage errorMessage) {
|
||||||
|
Long id = errorMessage.getSubject().getId();
|
||||||
|
String messageTemplate = errorMessages.getMessage(id.toString(), null, Locale.ENGLISH);
|
||||||
|
if (errorMessage.getArgs().length > 0)
|
||||||
|
return String.format(messageTemplate, errorMessage.getArgs());
|
||||||
|
else {
|
||||||
|
return messageTemplate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
package ru.spcex.clearing.backendapi.service;
|
||||||
|
|
||||||
|
import ru.spcex.clearing.backendapi.controller.response.cud.QueueSuccessResponse;
|
||||||
|
import ru.spcex.clearing.backendapi.domain.actions.IAction;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
|
public interface IOperator {
|
||||||
|
QueueSuccessResponse sendRequestToQueue(String destination, IAction<?> iAction) throws ExecutionException, InterruptedException;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
package ru.spcex.clearing.backendapi.service.impl;
|
||||||
|
|
||||||
|
import org.apache.kafka.clients.producer.Producer;
|
||||||
|
import org.apache.kafka.clients.producer.ProducerRecord;
|
||||||
|
import org.apache.kafka.clients.producer.RecordMetadata;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import ru.spcex.clearing.backendapi.controller.response.cud.QueueSuccessResponse;
|
||||||
|
import ru.spcex.clearing.backendapi.domain.actions.IAction;
|
||||||
|
import ru.spcex.clearing.backendapi.errors.ActionValidationException;
|
||||||
|
import ru.spcex.clearing.backendapi.service.IOperator;
|
||||||
|
import ru.spcex.clearing.platform.messaging.domain.BaseRequest;
|
||||||
|
import ru.spcex.platform.imdg.api.ImdgId;
|
||||||
|
import ru.spcex.platform.imdg.api.ImdgProvider;
|
||||||
|
import ru.spcex.platform.utils.enumeration.EnumMessage;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class OperatorImpl implements IOperator {
|
||||||
|
private final Producer<String, Object> kafka;
|
||||||
|
private final ImdgId idGenerator;
|
||||||
|
|
||||||
|
public OperatorImpl(Producer<String, Object> kafka, ImdgProvider imdgProvider) {
|
||||||
|
this.kafka = kafka;
|
||||||
|
this.idGenerator = imdgProvider.getImdgIdGenerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QueueSuccessResponse sendRequestToQueue(String destination, IAction<?> iAction) throws ExecutionException, InterruptedException {
|
||||||
|
throwValidate(iAction);
|
||||||
|
BaseRequest request = iAction.toRequest();
|
||||||
|
request.setId(idGenerator.nextId());
|
||||||
|
Future<RecordMetadata> send = kafka.send(new ProducerRecord<>(destination, request));
|
||||||
|
send.get();
|
||||||
|
return new QueueSuccessResponse(request.getActionType(), request.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void throwValidate(IAction<?> iAction) {
|
||||||
|
Collection<EnumMessage> validationErrors = iAction.validate();
|
||||||
|
if (validationErrors.size() > 0) {
|
||||||
|
throw new ActionValidationException(validationErrors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
1=This is error example 1.
|
||||||
|
3000=Validation error field: %s
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
1=Пример ошибки с идентификатором 1.
|
||||||
|
3000=Ошибка валидации, поле: %s
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
backend-api.response.test1=This is response example 1.
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
backend-api.response.test1=Это пример ответа 1.
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package ru.spcex.platform.imdg.iml.hazelcast.adapter;
|
||||||
|
|
||||||
|
import com.hazelcast.core.IdGenerator;
|
||||||
|
import ru.spcex.platform.imdg.api.ImdgId;
|
||||||
|
|
||||||
|
public class ImdgIdGeneratorHazelcast implements ImdgId {
|
||||||
|
private IdGenerator idGenerator;
|
||||||
|
|
||||||
|
public IdGenerator getIdGenerator() {
|
||||||
|
return idGenerator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIdGenerator(IdGenerator idGenerator) {
|
||||||
|
this.idGenerator = idGenerator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long nextId() {
|
||||||
|
return idGenerator.newId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,8 +12,10 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
import ru.spcex.clearing.imdg.IMDGDistributedNames;
|
import ru.spcex.clearing.imdg.IMDGDistributedNames;
|
||||||
import ru.spcex.platform.classes.base.SpcexObjectBase;
|
import ru.spcex.platform.classes.base.SpcexObjectBase;
|
||||||
import ru.spcex.platform.imdg.api.Imdg;
|
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.ImdgProvider;
|
||||||
import ru.spcex.platform.imdg.iml.hazelcast.adapter.ImdgHazelcast;
|
import ru.spcex.platform.imdg.iml.hazelcast.adapter.ImdgHazelcast;
|
||||||
|
import ru.spcex.platform.imdg.iml.hazelcast.adapter.ImdgIdGeneratorHazelcast;
|
||||||
import ru.spcex.platform.imdg.iml.hazelcast.config.HazelcastClientParams;
|
import ru.spcex.platform.imdg.iml.hazelcast.config.HazelcastClientParams;
|
||||||
import ru.spcex.platform.imdg.iml.hazelcast.util.HazelcastHelper;
|
import ru.spcex.platform.imdg.iml.hazelcast.util.HazelcastHelper;
|
||||||
import ru.spcex.platform.utils.log.ExceptionUtils;
|
import ru.spcex.platform.utils.log.ExceptionUtils;
|
||||||
|
|
@ -231,6 +233,23 @@ public abstract class HazelcastServiceBase
|
||||||
return imdg;
|
return imdg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImdgId getImdgIdGenerator() {
|
||||||
|
ImdgIdGeneratorHazelcast imdgId = new ImdgIdGeneratorHazelcast();
|
||||||
|
statusSubscribe(new IHazelcastClusterStatus() {
|
||||||
|
@Override
|
||||||
|
public void getAvailable(HazelcastInstance hazelcastNotInited) {
|
||||||
|
IdGenerator generator = hazelcastInstance.getIdGenerator(IMDGDistributedNames.MAP_SEQUENCE_NAME);
|
||||||
|
imdgId.setIdGenerator(generator);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void getUnavailable(HazelcastInstance hazelcastNotInited) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return imdgId;
|
||||||
|
}
|
||||||
|
|
||||||
// @Override
|
// @Override
|
||||||
public void waitTillReadyState() throws InterruptedException {
|
public void waitTillReadyState() throws InterruptedException {
|
||||||
boolean done = false;
|
boolean done = false;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package ru.spcex.platform.imdg.api;
|
||||||
|
|
||||||
|
public interface ImdgId {
|
||||||
|
Long nextId();
|
||||||
|
}
|
||||||
|
|
@ -8,4 +8,6 @@ public interface ImdgProvider {
|
||||||
*/
|
*/
|
||||||
public <T extends SpcexObjectBase> Imdg<T> getImdg(String key, Class<T> clazz);
|
public <T extends SpcexObjectBase> Imdg<T> getImdg(String key, Class<T> clazz);
|
||||||
|
|
||||||
|
public ImdgId getImdgIdGenerator();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,11 @@
|
||||||
<artifactId>platform-utils</artifactId>
|
<artifactId>platform-utils</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.spcex.platform</groupId>
|
||||||
|
<artifactId>platform-classes-base</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package ru.spcex.clearing.platform.messaging.domain;
|
||||||
|
|
||||||
|
public enum ActionType {
|
||||||
|
NEW
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package ru.spcex.clearing.platform.messaging.domain;
|
||||||
|
|
||||||
|
import ru.spcex.platform.classes.base.interfaces.WithId;
|
||||||
|
|
||||||
|
public interface BaseRequest extends WithId {
|
||||||
|
void setId(Long id);
|
||||||
|
ActionType getActionType();
|
||||||
|
}
|
||||||
|
|
@ -3,12 +3,16 @@ package ru.spcex.clearing.platform.messaging.domain.cud.securitites;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
|
import ru.spcex.clearing.platform.messaging.domain.ActionType;
|
||||||
|
import ru.spcex.clearing.platform.messaging.domain.BaseRequest;
|
||||||
import ru.spcex.clearing.platform.messaging.domain.json.deserialize.InstantDeserializer;
|
import ru.spcex.clearing.platform.messaging.domain.json.deserialize.InstantDeserializer;
|
||||||
import ru.spcex.clearing.platform.messaging.domain.json.serialize.InstantSerializer;
|
import ru.spcex.clearing.platform.messaging.domain.json.serialize.InstantSerializer;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
|
||||||
public class MoneyMarketSecurityNewRequest {
|
public class MoneyMarketSecurityNewRequest implements BaseRequest {
|
||||||
|
@JsonProperty
|
||||||
|
public Long id;
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
@JsonSerialize(using = InstantSerializer.class)
|
@JsonSerialize(using = InstantSerializer.class)
|
||||||
@JsonDeserialize(using = InstantDeserializer.class)
|
@JsonDeserialize(using = InstantDeserializer.class)
|
||||||
|
|
@ -30,6 +34,21 @@ public class MoneyMarketSecurityNewRequest {
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
public Double lotSize;
|
public Double lotSize;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ActionType getActionType() {
|
||||||
|
return ActionType.NEW;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
public Instant getStartDate() {
|
public Instant getStartDate() {
|
||||||
return startDate;
|
return startDate;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
package ru.spcex.platform.utils.enumeration;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
public class EnumMessage {
|
||||||
|
private final IEnumId subject;
|
||||||
|
/**
|
||||||
|
* Параметры для подстановки в текст сообщения. Все Serializable.
|
||||||
|
* args not null.
|
||||||
|
*/
|
||||||
|
private Object[] args;
|
||||||
|
|
||||||
|
public EnumMessage(IEnumId subject) {
|
||||||
|
this.subject = subject;
|
||||||
|
this.args = Collections.emptyList().toArray(new Object[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public EnumMessage(IEnumId subject, Object... args) {
|
||||||
|
this.subject = subject;
|
||||||
|
this.args = args;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumId getSubject() {
|
||||||
|
return subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object[] getArgs() {
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setArgs(Object[] args) {
|
||||||
|
this.args = args;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
package ru.spcex.platform.utils.enumeration;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public interface IEnumId extends Serializable {
|
||||||
|
Long getId();
|
||||||
|
|
||||||
|
default boolean equalsById(Long id) {
|
||||||
|
return id != null && getId().equals(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверяет что среди данного набора Enum, присутствует элемент с данным id
|
||||||
|
* id может быть null
|
||||||
|
*/
|
||||||
|
static <T extends Enum<T> & IEnumId> boolean contains(java.lang.Long id, T... enumSet) {
|
||||||
|
for (T e : enumSet) {
|
||||||
|
if (Objects.equals(id, e.getId()))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T extends Enum<T> & IEnumId> boolean contains(T e, T... enumSet) {
|
||||||
|
for (T enumEl : enumSet) {
|
||||||
|
if (enumEl.equals(e))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверяет что среди всех Enum данного класса, присутствует элемент с данным id
|
||||||
|
* id может быть null
|
||||||
|
*/
|
||||||
|
static <T extends Enum<T> & IEnumId> 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> & IEnumId> 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(IEnumId enumVal) {
|
||||||
|
return enumVal == null ? null : enumVal.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
Long UNDEFINED_VALUE = Long.MIN_VALUE;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package ru.spcex.platform.utils.enumeration;
|
||||||
|
|
||||||
|
public interface IMessageResolver {
|
||||||
|
String resolve(EnumMessage errorMessage);
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
docker build -t securities-service:1.0.0 ../modules/securities-service/
|
docker build -t securities-service:1.0.0 ../modules/securities-service/
|
||||||
docker run -p 8050:8080 -v /opt/clearing/logs:/opt/clearing/bin/logs securities-service:1.0.0 &
|
docker run -v /opt/clearing/logs:/opt/clearing/bin/logs securities-service:1.0.0 &
|
||||||
Loading…
Add table
Reference in a new issue