mirror of
https://github.com/valitydev/hooker.git
synced 2024-11-06 00:05:17 +00:00
Ft/td 138/gh action (#1)
* TD-138: github actions, webhook-dispatcher sending * Fix table name * Added workflow, removed build_utils * Added tests * Removed event_id * Added test * Optimized imports, fix codacy warnings * fix codacy warnings * fix codestyle * fix codestyle * fix codacy * Review fixes * Fix readme * Fix readme(2) * Fix readme(3) * Feedback fix * Fix tests * Fix tests(1) * Fix tests(2) * Feedback fixes Co-authored-by: Inal Arsanukaev <inalarsanukaev@192.168.1.5> Co-authored-by: Inal Arsanukaev <inalarsanukaev@192.168.1.4>
This commit is contained in:
parent
a7b866b090
commit
aa94a879bb
0
.gitmodules
vendored
0
.gitmodules
vendored
16
Jenkinsfile
vendored
16
Jenkinsfile
vendored
@ -1,16 +0,0 @@
|
||||
#!groovy
|
||||
build('hooker', 'java-maven') {
|
||||
checkoutRepo()
|
||||
loadBuildUtils()
|
||||
|
||||
def javaServicePipeline
|
||||
runStage('load JavaService pipeline') {
|
||||
javaServicePipeline = load("build_utils/jenkins_lib/pipeJavaService.groovy")
|
||||
}
|
||||
|
||||
def serviceName = env.REPO_NAME
|
||||
def mvnArgs = '-DjvmArgs="-Xmx256m"'
|
||||
def useJava11 = true
|
||||
|
||||
javaServicePipeline(serviceName, useJava11, mvnArgs)
|
||||
}
|
26
README.md
26
README.md
@ -2,32 +2,16 @@
|
||||
|
||||
Сервис вебхуков
|
||||
|
||||
[![Build Status](http://ci.rbkmoney.com/buildStatus/icon?job=rbkmoney_private/hooker/master)](http://ci.rbkmoney.com/job/rbkmoney_private/job/hooker/job/master/)
|
||||
Сервис предоставляет интерфейс для CAPI для создания, удаления вебхуков, установки/редактирования опций вебхука (таких как url, public/private keys - они генерируются сервисом, типы событий).
|
||||
Сервис получает события инвойсинга и кастомеров из кафки и далее отправляет в webhook-dispatcher
|
||||
|
||||
1. Сервис предоставляет интерфейс для CAPI для создания, удаления вебхуков, установки/редактирования опций вебхука (
|
||||
таких как url, public/private keys - они генерируются сервисом, типы событий).
|
||||
2. Сервис поллит bustermaze на появление событий, по которым должны отправляться сообщения мерчантам
|
||||
|
||||
Интерфейс для capi доступен по пути /hook
|
||||
Интерфейс для CAPI доступен по пути /hook
|
||||
|
||||
Для более подробного ознакомления со структурой объектов можно воспользоваться ссылкой на сам
|
||||
объект [webhooker.thrift][1]
|
||||
|
||||
Подробная информация по протоколу отправки сообщений мерчантам описана в [swagger-спецификации][2]
|
||||
|
||||
#### Пример запроса к мерчанту
|
||||
[1]: https://github.com/valitydev/damsel/blob/master/proto/webhooker.thrift
|
||||
|
||||
```
|
||||
curl -v -X POST
|
||||
-H "Content-Type: application/json; charset=utf-8"
|
||||
-H "Content-Signature: alg=RS256; digest=QydtAwZ8jmmrX1wP87vAJIXVBiRZe3Zo5xRYnzMQIsDxxsVn26XGMBeD4op8_9LmXIAirYWd53gfX638ZR83D2pTSCXkNpzHyqiyzhPlH2asaC9qiiOOWaQ-kHk9xFlOcMa1Qtt0BaVst-tbGDsFhgjl6tvGuN4JHRq5khzl_iIJ_ZQniEuzGOYReWn8wCLmaspX0MPyACMTo8HDwyihOHB1_RQBliBFYyvw523xvSQ6WxWYjYhFsjglIg1wdKUMyaiScw0kmKm53OdxDAnjl4MPQmtryuANjbklN8_EatOrQAqGwRUp1ayR_3WMlayhpxaEHlG1sAHQaaO3ulI35g=="
|
||||
-d '{"eventID":27,"occuredAt":"2017-05-16T13:49:34.935099Z","topic":"InvoicesTopic","eventType":"PaymentCaptured","invoice":{"id":"qXMiygTqb2","shopID":1,"createdAt":"2017-05-16T13:49:32.753723Z","status":"unpaid","reason":null,"dueDate":"2017-05-16T13:59:32Z","amount":100000,"currency":"RUB","metadata":{"type":"application/json","data":"eyJpbnZvaWNlX2R1bW15X2NvbnRleHQiOiJ0ZXN0X3ZhbHVlIn0="},"product":"test_product","description":"test_invoice_description"},"payment":{"id":"1","createdAt":"2017-05-16T13:49:33.182195Z","status":"captured","error":null,"amount":100000,"currency":"RUB","paymentToolToken":"5Gz2nhE1eleFGBAcGe9SrA","paymentSession":"2nTYVgk6h85O7vIVV9j4pA","contactInfo":{"email":"bla@bla.ru","phoneNumber":null},"ip":"10.100.2.1","fingerprint":"test fingerprint"}}
|
||||
' https://{host}:{port}/{path}
|
||||
```
|
||||
|
||||
Мерчант, используя публичный ключ и имея в распоряжении тело запроса, подпись, алгоритм подписи и хэширования, может
|
||||
произвести проверку подписи
|
||||
|
||||
[1]: https://github.com/rbkmoney/damsel/blob/master/proto/webhooker.thrift
|
||||
|
||||
[2]: https://github.com/rbkmoney/swag-webhook-events/blob/master/spec/swagger.yaml
|
||||
[2]: https://github.com/valitydev/swag-webhook-events
|
||||
|
@ -1,8 +0,0 @@
|
||||
postgres:
|
||||
image: postgres:9.6
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: hook
|
||||
ports:
|
||||
- '5432:5432'
|
29
pom.xml
29
pom.xml
@ -4,7 +4,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>hooker</artifactId>
|
||||
<version>2.0.60-SNAPSHOT</version>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>hooker</name>
|
||||
@ -86,10 +86,6 @@
|
||||
<groupId>dev.vality</groupId>
|
||||
<artifactId>mamsel</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.vality.logback</groupId>
|
||||
<artifactId>nop-rolling</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.vality</groupId>
|
||||
<artifactId>shared-resources</artifactId>
|
||||
@ -101,15 +97,6 @@
|
||||
<dependency>
|
||||
<groupId>dev.vality</groupId>
|
||||
<artifactId>kafka-common-lib</artifactId>
|
||||
<version>0.0.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.vality</groupId>
|
||||
<artifactId>custom-metrics-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.vality</groupId>
|
||||
<artifactId>custom-actuator-endpoints</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.vality.woody</groupId>
|
||||
@ -143,7 +130,11 @@
|
||||
<artifactId>swag-webhook-events</artifactId>
|
||||
<version>1.1-67063f3-client</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>dev.vality</groupId>
|
||||
<artifactId>webhook-dispatcher-proto</artifactId>
|
||||
<version>1.12-49f714e</version>
|
||||
</dependency>
|
||||
<!--Thirdparty libs-->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
@ -219,6 +210,12 @@
|
||||
<artifactId>mockwebserver</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.vality</groupId>
|
||||
<artifactId>testcontainers-annotations</artifactId>
|
||||
<version>1.4.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
@ -286,7 +283,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-remote-resources-plugin</artifactId>
|
||||
<version>1.5</version>
|
||||
<version>1.7.0</version>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven.shared</groupId>
|
||||
|
@ -6,47 +6,12 @@ import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
|
||||
import dev.vality.hooker.dao.CustomerDao;
|
||||
import dev.vality.hooker.dao.HookDao;
|
||||
import dev.vality.hooker.dao.InvoicingMessageDao;
|
||||
import dev.vality.hooker.dao.impl.CustomerQueueDao;
|
||||
import dev.vality.hooker.dao.impl.CustomerTaskDao;
|
||||
import dev.vality.hooker.dao.impl.InvoicingQueueDao;
|
||||
import dev.vality.hooker.dao.impl.InvoicingTaskDao;
|
||||
import dev.vality.hooker.model.CustomerMessage;
|
||||
import dev.vality.hooker.model.CustomerQueue;
|
||||
import dev.vality.hooker.model.InvoicingMessage;
|
||||
import dev.vality.hooker.model.InvoicingQueue;
|
||||
import dev.vality.hooker.retry.RetryPoliciesService;
|
||||
import dev.vality.hooker.scheduler.MessageScheduler;
|
||||
import dev.vality.hooker.scheduler.MessageSender;
|
||||
import dev.vality.hooker.service.CustomerEventService;
|
||||
import dev.vality.hooker.service.FaultDetectorService;
|
||||
import dev.vality.hooker.service.InvoicingEventService;
|
||||
import dev.vality.hooker.service.MessageProcessor;
|
||||
import dev.vality.hooker.service.crypt.Signer;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
|
||||
@Configuration
|
||||
public class AppConfig {
|
||||
|
||||
@Value("${message.scheduler.invoicing.threadPoolSize}")
|
||||
private int invoicingThreadPoolSize;
|
||||
|
||||
@Value("${message.scheduler.customer.threadPoolSize}")
|
||||
private int customerThreadPoolSize;
|
||||
|
||||
@Value("${message.scheduler.delay}")
|
||||
private int delayMillis;
|
||||
|
||||
@Value("${merchant.callback.timeout}")
|
||||
private int timeout;
|
||||
|
||||
@Bean
|
||||
public ObjectMapper objectMapper() {
|
||||
return new ObjectMapper()
|
||||
@ -56,50 +21,4 @@ public class AppConfig {
|
||||
.setSerializationInclusion(JsonInclude.Include.NON_EMPTY)
|
||||
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MessageSender<InvoicingMessage, InvoicingQueue> invoicngMessageSender(
|
||||
Signer signer, InvoicingEventService eventService,
|
||||
ObjectMapper objectMapper, FaultDetectorService faultDetector) {
|
||||
return new MessageSender<>(invoicingThreadPoolSize, timeout, signer, eventService, objectMapper, faultDetector);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MessageSender<CustomerMessage, CustomerQueue> customerMessageSender(
|
||||
Signer signer, CustomerEventService eventService,
|
||||
ObjectMapper objectMapper, FaultDetectorService faultDetector) {
|
||||
return new MessageSender<>(customerThreadPoolSize, timeout, signer, eventService, objectMapper, faultDetector);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MessageProcessor<InvoicingMessage, InvoicingQueue> invoicingMessageProcessor(
|
||||
HookDao hookDao, InvoicingTaskDao taskDao, InvoicingQueueDao queueDao, InvoicingMessageDao messageDao,
|
||||
RetryPoliciesService retryPoliciesService, TransactionTemplate transactionTemplate,
|
||||
FaultDetectorService faultDetector, MessageSender<InvoicingMessage, InvoicingQueue> invoicngMessageSender) {
|
||||
return new MessageProcessor<>(hookDao, taskDao, queueDao, messageDao, retryPoliciesService, transactionTemplate,
|
||||
faultDetector, invoicngMessageSender);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MessageProcessor<CustomerMessage, CustomerQueue> customerMessageProcessor(
|
||||
HookDao hookDao, CustomerTaskDao taskDao, CustomerQueueDao queueDao, CustomerDao messageDao,
|
||||
RetryPoliciesService retryPoliciesService, TransactionTemplate transactionTemplate,
|
||||
FaultDetectorService faultDetector, MessageSender<CustomerMessage, CustomerQueue> customerMessageSender) {
|
||||
return new MessageProcessor<>(hookDao, taskDao, queueDao, messageDao, retryPoliciesService, transactionTemplate,
|
||||
faultDetector, customerMessageSender);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MessageScheduler<InvoicingMessage, InvoicingQueue> invoicingMessageScheduler(
|
||||
MessageProcessor<InvoicingMessage, InvoicingQueue> invoicingMessageProcessor,
|
||||
ThreadPoolTaskScheduler taskScheduler) {
|
||||
return new MessageScheduler<>(invoicingThreadPoolSize, delayMillis, invoicingMessageProcessor, taskScheduler);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MessageScheduler<CustomerMessage, CustomerQueue> cuustomerMessageScheduler(
|
||||
MessageProcessor<CustomerMessage, CustomerQueue> customerMessageProcessor,
|
||||
ThreadPoolTaskScheduler taskScheduler) {
|
||||
return new MessageScheduler<>(customerThreadPoolSize, delayMillis, customerMessageProcessor, taskScheduler);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
package dev.vality.hooker.configuration;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import dev.vality.hooker.model.InvoicingMessage;
|
||||
import dev.vality.hooker.model.InvoicingMessageKey;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class CacheConfig {
|
||||
|
||||
@Bean
|
||||
public Cache<InvoicingMessageKey, InvoicingMessage> invoiceDataCache(
|
||||
@Value("${cache.invoice.size}") int cacheSize) {
|
||||
return Caffeine.newBuilder()
|
||||
.maximumSize(cacheSize)
|
||||
.build();
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
package dev.vality.hooker.configuration;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
|
||||
@Configuration
|
||||
public class DaoConfiguration {
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
public TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager,
|
||||
@Value("${db.jdbc.tr_timeout}") int transactionTimeout) {
|
||||
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
|
||||
transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
|
||||
transactionTemplate.setTimeout(transactionTimeout);
|
||||
return transactionTemplate;
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
package dev.vality.hooker.configuration;
|
||||
|
||||
import dev.vality.damsel.fault_detector.FaultDetectorSrv;
|
||||
import dev.vality.woody.thrift.impl.http.THSpawnClientBuilder;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Configuration
|
||||
public class FaultDetectorConfig {
|
||||
|
||||
@Bean
|
||||
public FaultDetectorSrv.Iface faultDetectorClient(@Value("${service.fault-detector.url}") Resource resource,
|
||||
@Value("${service.fault-detector.networkTimeout}")
|
||||
int networkTimeout) throws IOException {
|
||||
|
||||
return new THSpawnClientBuilder()
|
||||
.withNetworkTimeout(networkTimeout)
|
||||
.withAddress(resource.getURI()).build(FaultDetectorSrv.Iface.class);
|
||||
}
|
||||
}
|
@ -4,26 +4,29 @@ import dev.vality.damsel.payment_processing.EventPayload;
|
||||
import dev.vality.hooker.configuration.properties.KafkaSslProperties;
|
||||
import dev.vality.hooker.serde.SinkEventDeserializer;
|
||||
import dev.vality.kafka.common.exception.handler.SeekToCurrentWithSleepBatchErrorHandler;
|
||||
import dev.vality.kafka.common.serialization.ThriftSerializer;
|
||||
import dev.vality.machinegun.eventsink.MachineEvent;
|
||||
import dev.vality.sink.common.parser.impl.MachineEventParser;
|
||||
import dev.vality.sink.common.parser.impl.PaymentEventPayloadMachineEventParser;
|
||||
import dev.vality.sink.common.serialization.BinaryDeserializer;
|
||||
import dev.vality.sink.common.serialization.impl.PaymentEventPayloadDeserializer;
|
||||
import dev.vality.webhook.dispatcher.WebhookMessage;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.kafka.clients.CommonClientConfigs;
|
||||
import org.apache.kafka.clients.consumer.ConsumerConfig;
|
||||
import org.apache.kafka.clients.producer.ProducerConfig;
|
||||
import org.apache.kafka.common.config.SslConfigs;
|
||||
import org.apache.kafka.common.security.auth.SecurityProtocol;
|
||||
import org.apache.kafka.common.serialization.StringDeserializer;
|
||||
import org.apache.kafka.common.serialization.StringSerializer;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
|
||||
import org.springframework.kafka.config.KafkaListenerContainerFactory;
|
||||
import org.springframework.kafka.core.ConsumerFactory;
|
||||
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
|
||||
import org.springframework.kafka.core.*;
|
||||
import org.springframework.kafka.listener.BatchErrorHandler;
|
||||
import org.springframework.kafka.listener.ConcurrentMessageListenerContainer;
|
||||
import org.springframework.kafka.listener.ContainerProperties;
|
||||
@ -141,4 +144,17 @@ public class KafkaConfig {
|
||||
return new PaymentEventPayloadMachineEventParser(paymentEventPayloadDeserializer);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public KafkaTemplate<String, WebhookMessage> kafkaTemplate() {
|
||||
return new KafkaTemplate<>(producerFactory());
|
||||
}
|
||||
|
||||
private ProducerFactory<String, WebhookMessage> producerFactory() {
|
||||
Map<String, Object> config = new HashMap<>();
|
||||
config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
|
||||
config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
|
||||
config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, ThriftSerializer.class);
|
||||
configureSsl(config);
|
||||
return new DefaultKafkaProducerFactory<>(config);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,24 @@
|
||||
package dev.vality.hooker.configuration;
|
||||
|
||||
import dev.vality.hooker.dao.rowmapper.WebhookModelRowMapper;
|
||||
import dev.vality.hooker.model.CustomerMessage;
|
||||
import dev.vality.hooker.model.InvoicingMessage;
|
||||
import dev.vality.hooker.model.WebhookMessageModel;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
|
||||
@Configuration
|
||||
public class RowMapperConfig {
|
||||
@Bean
|
||||
public RowMapper<WebhookMessageModel<CustomerMessage>> customerWebhookRowMapper(
|
||||
RowMapper<CustomerMessage> customerRowMapper) {
|
||||
return new WebhookModelRowMapper<>(customerRowMapper);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RowMapper<WebhookMessageModel<InvoicingMessage>> invoicingWebhookRowMapper(
|
||||
RowMapper<InvoicingMessage> invoicingRowMapper) {
|
||||
return new WebhookModelRowMapper<>(invoicingRowMapper);
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package dev.vality.hooker.configuration;
|
||||
|
||||
import dev.vality.hooker.converter.WebhookMessageBuilder;
|
||||
import dev.vality.hooker.dao.MessageDao;
|
||||
import dev.vality.hooker.model.CustomerMessage;
|
||||
import dev.vality.hooker.model.InvoicingMessage;
|
||||
import dev.vality.hooker.service.EventService;
|
||||
import dev.vality.hooker.service.MessageService;
|
||||
import dev.vality.hooker.service.WebhookKafkaProducerService;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class ServiceConfig {
|
||||
@Bean
|
||||
public MessageService<InvoicingMessage> invoicingService(MessageDao<InvoicingMessage> messageDao,
|
||||
EventService<InvoicingMessage> eventService,
|
||||
WebhookMessageBuilder webhookMessageBuilder,
|
||||
WebhookKafkaProducerService webhookKafkaProducerService) {
|
||||
return new MessageService<>(messageDao, eventService, webhookMessageBuilder, webhookKafkaProducerService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MessageService<CustomerMessage> customerService(MessageDao<CustomerMessage> messageDao,
|
||||
EventService<CustomerMessage> eventService,
|
||||
WebhookMessageBuilder webhookMessageBuilder,
|
||||
WebhookKafkaProducerService webhookKafkaProducerService) {
|
||||
return new MessageService<>(messageDao, eventService, webhookMessageBuilder, webhookKafkaProducerService);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package dev.vality.hooker.converter;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import dev.vality.geck.common.util.TypeUtil;
|
||||
import dev.vality.hooker.model.WebhookMessageModel;
|
||||
import dev.vality.hooker.service.AdditionalHeadersGenerator;
|
||||
import dev.vality.hooker.service.crypt.Signer;
|
||||
import dev.vality.swag_webhook_events.model.Event;
|
||||
import dev.vality.webhook.dispatcher.WebhookMessage;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Instant;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class WebhookMessageBuilder {
|
||||
|
||||
private final AdditionalHeadersGenerator additionalHeadersGenerator;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final Signer signer;
|
||||
|
||||
@SneakyThrows
|
||||
public WebhookMessage build(WebhookMessageModel webhookMessageModel, Event event, String sourceId, Long parentId) {
|
||||
final String messageJson = objectMapper.writeValueAsString(event);
|
||||
final String signature = signer.sign(messageJson, webhookMessageModel.getPrivateKey());
|
||||
return new WebhookMessage()
|
||||
.setWebhookId(webhookMessageModel.getHookId())
|
||||
.setSourceId(sourceId)
|
||||
.setEventId(event.getEventID())
|
||||
.setParentEventId(parentId)
|
||||
.setCreatedAt(TypeUtil.temporalToString(Instant.now()))
|
||||
.setUrl(webhookMessageModel.getUrl())
|
||||
.setContentType(ContentType.APPLICATION_JSON.getMimeType())
|
||||
.setAdditionalHeaders(additionalHeadersGenerator.generate(signature))
|
||||
.setRequestBody(messageJson.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
package dev.vality.hooker.dao;
|
||||
|
||||
import dev.vality.hooker.exception.DaoException;
|
||||
import dev.vality.hooker.model.Task;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.NestedRuntimeException;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Created by jeckep on 17.04.17.
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public abstract class AbstractTaskDao implements TaskDao {
|
||||
|
||||
public static RowMapper<Task> taskRowMapper = (rs, i) ->
|
||||
new Task(rs.getLong("message_id"), rs.getLong("queue_id"));
|
||||
protected final NamedParameterJdbcTemplate jdbcTemplate;
|
||||
|
||||
protected abstract String getMessageTopic();
|
||||
|
||||
@Override
|
||||
public void remove(long queueId, long messageId) throws DaoException {
|
||||
final String sql =
|
||||
"DELETE FROM hook.scheduled_task " +
|
||||
" where queue_id=:queue_id " +
|
||||
" and message_id=:message_id " +
|
||||
" and message_type=CAST(:message_type as hook.message_topic)";
|
||||
try {
|
||||
jdbcTemplate.update(sql, new MapSqlParameterSource("queue_id", queueId)
|
||||
.addValue("message_id", messageId)
|
||||
.addValue("message_type", getMessageTopic()));
|
||||
log.debug("Task with queueId {} messageId {} removed from hook.scheduled_task", queueId, messageId);
|
||||
} catch (NestedRuntimeException e) {
|
||||
log.warn("Fail to delete task by queue_id {} and message_id {}", queueId, messageId, e);
|
||||
throw new DaoException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAll(long queueId) throws DaoException {
|
||||
final String sql = "DELETE FROM hook.scheduled_task " +
|
||||
"where queue_id=:queue_id and message_type=CAST(:message_type as hook.message_topic)";
|
||||
try {
|
||||
jdbcTemplate.update(sql,
|
||||
new MapSqlParameterSource("queue_id", queueId).addValue("message_type", getMessageTopic()));
|
||||
} catch (NestedRuntimeException e) {
|
||||
log.warn("Fail to delete tasks for hook:" + queueId, e);
|
||||
throw new DaoException(e);
|
||||
}
|
||||
}
|
||||
|
||||
//should preserve order by message id
|
||||
protected Map<Long, List<Task>> splitByQueue(List<Task> orderedByMessageIdTasks) {
|
||||
return orderedByMessageIdTasks.stream().collect(Collectors.groupingBy(Task::getQueueId));
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
package dev.vality.hooker.dao;
|
||||
|
||||
import dev.vality.hooker.exception.DaoException;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface IdsGeneratorDao {
|
||||
List<Long> get(int size) throws DaoException;
|
||||
}
|
@ -1,10 +1,14 @@
|
||||
package dev.vality.hooker.dao;
|
||||
|
||||
import dev.vality.hooker.exception.DaoException;
|
||||
import dev.vality.hooker.model.Message;
|
||||
import dev.vality.hooker.model.WebhookMessageModel;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public interface MessageDao<M> {
|
||||
List<M> getBy(Collection<Long> messageIds) throws DaoException;
|
||||
}
|
||||
public interface MessageDao<M extends Message> {
|
||||
Long save(M message);
|
||||
|
||||
List<WebhookMessageModel<M>> getWebhookModels(Long messageId);
|
||||
|
||||
Long getParentId(Long hookId, String sourceId, Long messageId);
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
package dev.vality.hooker.dao;
|
||||
|
||||
import dev.vality.hooker.model.Queue;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by inalarsanukaev on 14.11.17.
|
||||
*/
|
||||
public interface QueueDao<Q extends Queue> {
|
||||
List<Q> getWithPolicies(Collection<Long> ids);
|
||||
|
||||
void disable(long id);
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
package dev.vality.hooker.dao;
|
||||
|
||||
import dev.vality.hooker.exception.DaoException;
|
||||
import dev.vality.hooker.retry.impl.simple.SimpleRetryPolicyRecord;
|
||||
|
||||
/**
|
||||
* Created by jeckep on 17.04.17.
|
||||
*/
|
||||
public interface SimpleRetryPolicyDao {
|
||||
void update(SimpleRetryPolicyRecord record) throws DaoException;
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
package dev.vality.hooker.dao;
|
||||
|
||||
|
||||
import dev.vality.hooker.exception.DaoException;
|
||||
import dev.vality.hooker.model.Task;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Created by jeckep on 13.04.17.
|
||||
*/
|
||||
public interface TaskDao {
|
||||
void remove(long queueId, long messageId);
|
||||
|
||||
void removeAll(long queueId) throws DaoException;
|
||||
|
||||
Map<Long, List<Task>> getScheduled() throws DaoException;
|
||||
}
|
@ -1,15 +1,8 @@
|
||||
package dev.vality.hooker.dao;
|
||||
|
||||
import dev.vality.hooker.model.EventType;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* Created by inalarsanukaev on 18.04.17.
|
||||
*/
|
||||
@Data
|
||||
@ToString
|
||||
@AllArgsConstructor
|
||||
|
@ -5,9 +5,6 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* Created by inalarsanukaev on 18.04.17.
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
|
@ -1,12 +1,15 @@
|
||||
package dev.vality.hooker.dao.impl;
|
||||
|
||||
import dev.vality.hooker.dao.CustomerDao;
|
||||
import dev.vality.hooker.dao.rowmapper.CustomerRowMapper;
|
||||
import dev.vality.hooker.exception.DaoException;
|
||||
import dev.vality.hooker.model.CustomerMessage;
|
||||
import dev.vality.hooker.model.CustomerMessageEnum;
|
||||
import dev.vality.hooker.model.EventType;
|
||||
import dev.vality.hooker.model.WebhookMessageModel;
|
||||
import dev.vality.swag_webhook_events.model.Event;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.NestedRuntimeException;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
@ -14,88 +17,60 @@ import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||
import org.springframework.jdbc.support.GeneratedKeyHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by inalarsanukaev on 13.10.17.
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class CustomerDaoImpl implements CustomerDao {
|
||||
|
||||
public static final String ID = "id";
|
||||
public static final String EVENT_ID = "event_id";
|
||||
public static final String TYPE = "type";
|
||||
public static final String OCCURED_AT = "occured_at";
|
||||
public static final String SEQUENCE_ID = "sequence_id";
|
||||
public static final String CHANGE_ID = "change_id";
|
||||
public static final String PARTY_ID = "party_id";
|
||||
public static final String EVENT_TYPE = "event_type";
|
||||
public static final String CUSTOMER_ID = "customer_id";
|
||||
public static final String CUSTOMER_SHOP_ID = "customer_shop_id";
|
||||
public static final String BINDING_ID = "binding_id";
|
||||
private static RowMapper<CustomerMessage> messageRowMapper = (rs, i) -> {
|
||||
CustomerMessage message = new CustomerMessage();
|
||||
message.setId(rs.getLong(ID));
|
||||
message.setEventId(rs.getLong(EVENT_ID));
|
||||
message.setPartyId(rs.getString(PARTY_ID));
|
||||
message.setEventTime(rs.getString(OCCURED_AT));
|
||||
message.setSequenceId(rs.getLong(SEQUENCE_ID));
|
||||
message.setChangeId(rs.getInt(CHANGE_ID));
|
||||
message.setType(CustomerMessageEnum.lookup(rs.getString(TYPE)));
|
||||
message.setEventType(EventType.valueOf(rs.getString(EVENT_TYPE)));
|
||||
message.setCustomerId(rs.getString(CUSTOMER_ID));
|
||||
message.setShopId(rs.getString(CUSTOMER_SHOP_ID));
|
||||
message.setBindingId(rs.getString(BINDING_ID));
|
||||
return message;
|
||||
};
|
||||
private final RowMapper<CustomerMessage> customerMessageRowMapper;
|
||||
private final RowMapper<WebhookMessageModel<CustomerMessage>> webhookMessageModelRowMapper;
|
||||
private final NamedParameterJdbcTemplate jdbcTemplate;
|
||||
|
||||
@Value("${parent.not.exist.id:-1}")
|
||||
private Long parentNotExistId;
|
||||
|
||||
@Override
|
||||
public CustomerMessage getAny(String customerId, CustomerMessageEnum type) throws DaoException {
|
||||
CustomerMessage result = null;
|
||||
final String sql = "SELECT * FROM hook.customer_message " +
|
||||
"WHERE customer_id =:customer_id AND type=CAST(:type as hook.customer_message_type) " +
|
||||
"ORDER BY id DESC LIMIT 1";
|
||||
MapSqlParameterSource params =
|
||||
new MapSqlParameterSource(CUSTOMER_ID, customerId).addValue(TYPE, type.getValue());
|
||||
MapSqlParameterSource params = new MapSqlParameterSource()
|
||||
.addValue(CustomerRowMapper.CUSTOMER_ID, customerId)
|
||||
.addValue(CustomerRowMapper.TYPE, type.getValue());
|
||||
try {
|
||||
result = jdbcTemplate.queryForObject(sql, params, messageRowMapper);
|
||||
return jdbcTemplate.queryForObject(sql, params, customerMessageRowMapper);
|
||||
} catch (EmptyResultDataAccessException e) {
|
||||
log.warn("CustomerMessage with customerId {}, type {} not exist!", customerId, type);
|
||||
return null;
|
||||
} catch (NestedRuntimeException e) {
|
||||
throw new DaoException("CustomerMessageDaoImpl.getAny error with customerId " + customerId, e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Long create(CustomerMessage message) throws DaoException {
|
||||
@Override
|
||||
public Long save(CustomerMessage message) throws DaoException {
|
||||
final String sql = "INSERT INTO hook.customer_message " +
|
||||
"(event_id, occured_at, sequence_id, change_id, type, " +
|
||||
"(occured_at, sequence_id, change_id, type, " +
|
||||
"party_id, event_type, customer_id, customer_shop_id, binding_id) " +
|
||||
"VALUES " +
|
||||
"(:event_id, :occured_at, :sequence_id, :change_id, CAST(:type as hook.customer_message_type), " +
|
||||
"(:occured_at, :sequence_id, :change_id, CAST(:type as hook.customer_message_type), " +
|
||||
":party_id, CAST(:event_type as hook.eventtype), :customer_id, :customer_shop_id, :binding_id) " +
|
||||
"ON CONFLICT (customer_id, sequence_id, change_id) DO NOTHING " +
|
||||
"RETURNING id";
|
||||
|
||||
MapSqlParameterSource params = new MapSqlParameterSource()
|
||||
.addValue(EVENT_ID, message.getEventId())
|
||||
.addValue(OCCURED_AT, message.getEventTime())
|
||||
.addValue(SEQUENCE_ID, message.getSequenceId())
|
||||
.addValue(CHANGE_ID, message.getChangeId())
|
||||
.addValue(TYPE, message.getType().getValue())
|
||||
.addValue(PARTY_ID, message.getPartyId())
|
||||
.addValue(EVENT_TYPE, message.getEventType().name())
|
||||
.addValue(CUSTOMER_ID, message.getCustomerId())
|
||||
.addValue(CUSTOMER_SHOP_ID, message.getShopId())
|
||||
.addValue(BINDING_ID, message.getBindingId());
|
||||
.addValue(CustomerRowMapper.OCCURED_AT, message.getEventTime())
|
||||
.addValue(CustomerRowMapper.SEQUENCE_ID, message.getSequenceId())
|
||||
.addValue(CustomerRowMapper.CHANGE_ID, message.getChangeId())
|
||||
.addValue(CustomerRowMapper.TYPE, message.getType().getValue())
|
||||
.addValue(CustomerRowMapper.PARTY_ID, message.getPartyId())
|
||||
.addValue(CustomerRowMapper.EVENT_TYPE, message.getEventType().name())
|
||||
.addValue(CustomerRowMapper.CUSTOMER_ID, message.getSourceId())
|
||||
.addValue(CustomerRowMapper.CUSTOMER_SHOP_ID, message.getShopId())
|
||||
.addValue(CustomerRowMapper.BINDING_ID, message.getBindingId());
|
||||
try {
|
||||
GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
|
||||
jdbcTemplate.update(sql, params, keyHolder);
|
||||
@ -106,32 +81,46 @@ public class CustomerDaoImpl implements CustomerDao {
|
||||
}
|
||||
return null;
|
||||
} catch (NestedRuntimeException e) {
|
||||
throw new DaoException("Couldn't create customerMessage with customerId " + message.getCustomerId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public Long getMaxEventId() {
|
||||
final String sql = "select max(event_id) from hook.customer_message ";
|
||||
try {
|
||||
return jdbcTemplate.queryForObject(sql, new MapSqlParameterSource(), Long.class);
|
||||
} catch (EmptyResultDataAccessException e) {
|
||||
return null;
|
||||
throw new DaoException("Couldn't create customerMessage with customerId " + message.getSourceId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CustomerMessage> getBy(Collection<Long> messageIds) throws DaoException {
|
||||
if (messageIds.isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
final String sql = "SELECT * FROM hook.customer_message WHERE id in (:ids)";
|
||||
public List<WebhookMessageModel<CustomerMessage>> getWebhookModels(Long messageId) {
|
||||
final String sql = "select m.*, w.id as hook_id, w.url, pk.priv_key" +
|
||||
" from hook.customer_message m" +
|
||||
" join hook.webhook w on m.party_id = w.party_id " +
|
||||
" and w.enabled and w.topic=CAST(:message_type as hook.message_topic)" +
|
||||
" join hook.webhook_to_events wte on wte.hook_id = w.id" +
|
||||
" join hook.party_data pk on w.party_id=pk.party_id" +
|
||||
" where m.id =:id " +
|
||||
" and m.event_type = wte.event_type " +
|
||||
" and (m.customer_shop_id = wte.invoice_shop_id or wte.invoice_shop_id is null) ";
|
||||
MapSqlParameterSource mapSqlParameterSource = new MapSqlParameterSource()
|
||||
.addValue("id", messageId)
|
||||
.addValue("message_type", Event.TopicEnum.CUSTOMERSTOPIC.getValue());
|
||||
return jdbcTemplate.query(sql, mapSqlParameterSource, webhookMessageModelRowMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getParentId(Long hookId, String customerId, Long messageId) {
|
||||
final String sql = "select m.id" +
|
||||
" from hook.customer_message m " +
|
||||
" join hook.webhook w on w.id=:hook_id" +
|
||||
" join hook.webhook_to_events wte on wte.hook_id = w.id" +
|
||||
" where m.customer_id =:customer_id" +
|
||||
" and m.id <:id " +
|
||||
" and m.event_type = wte.event_type " +
|
||||
" and (m.customer_shop_id = wte.invoice_shop_id or wte.invoice_shop_id is null) " +
|
||||
" order by id desc limit 1 ";
|
||||
MapSqlParameterSource mapSqlParameterSource = new MapSqlParameterSource()
|
||||
.addValue("hook_id", hookId)
|
||||
.addValue("customer_id", customerId)
|
||||
.addValue("id", messageId);
|
||||
try {
|
||||
List<CustomerMessage> messagesFromDb =
|
||||
jdbcTemplate.query(sql, new MapSqlParameterSource("ids", messageIds), messageRowMapper);
|
||||
log.debug("messagesFromDb {}", messagesFromDb);
|
||||
return messagesFromDb;
|
||||
} catch (NestedRuntimeException e) {
|
||||
throw new DaoException("CustomerMessageDaoImpl.getByIds error", e);
|
||||
return jdbcTemplate.queryForObject(sql, mapSqlParameterSource, Long.class);
|
||||
} catch (EmptyResultDataAccessException e) {
|
||||
return parentNotExistId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,106 +0,0 @@
|
||||
package dev.vality.hooker.dao.impl;
|
||||
|
||||
import dev.vality.hooker.dao.QueueDao;
|
||||
import dev.vality.hooker.exception.DaoException;
|
||||
import dev.vality.hooker.model.CustomerQueue;
|
||||
import dev.vality.hooker.model.Hook;
|
||||
import dev.vality.hooker.retry.RetryPolicyType;
|
||||
import dev.vality.swag_webhook_events.model.Event;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.NestedRuntimeException;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by inalarsanukaev on 14.11.17.
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class CustomerQueueDao implements QueueDao<CustomerQueue> {
|
||||
|
||||
public static RowMapper<CustomerQueue> queueWithPolicyRowMapper = (rs, i) -> {
|
||||
CustomerQueue queue = new CustomerQueue();
|
||||
queue.setId(rs.getLong("id"));
|
||||
queue.setCustomerId(rs.getString("customer_id"));
|
||||
Hook hook = new Hook();
|
||||
hook.setId(rs.getLong("hook_id"));
|
||||
hook.setPartyId(rs.getString("party_id"));
|
||||
hook.setTopic(rs.getString("message_type"));
|
||||
hook.setUrl(rs.getString("url"));
|
||||
hook.setPubKey(rs.getString("pub_key"));
|
||||
hook.setPrivKey(rs.getString("priv_key"));
|
||||
hook.setEnabled(rs.getBoolean("enabled"));
|
||||
RetryPolicyType retryPolicyType = RetryPolicyType.valueOf(rs.getString("retry_policy"));
|
||||
hook.setRetryPolicyType(retryPolicyType);
|
||||
queue.setHook(hook);
|
||||
queue.setRetryPolicyRecord(retryPolicyType.build(rs));
|
||||
return queue;
|
||||
};
|
||||
private final NamedParameterJdbcTemplate jdbcTemplate;
|
||||
|
||||
public void createWithPolicy(long messageId) throws DaoException {
|
||||
final String sql = "with queue as ( " +
|
||||
" insert into hook.customer_queue(hook_id, customer_id)" +
|
||||
" select w.id, m.customer_id" +
|
||||
" from hook.customer_message m" +
|
||||
" join hook.webhook w on m.party_id = w.party_id " +
|
||||
" and w.enabled and w.topic=CAST(:message_type as hook.message_topic)" +
|
||||
" where m.id = :id " +
|
||||
" on conflict(hook_id, customer_id) do nothing returning *) " +
|
||||
" insert into hook.simple_retry_policy(queue_id, message_type) " +
|
||||
" select id, CAST(:message_type as hook.message_topic) from queue";
|
||||
try {
|
||||
int count = jdbcTemplate.update(sql, new MapSqlParameterSource("id", messageId)
|
||||
.addValue("message_type", getMessagesTopic()));
|
||||
log.info("Created {} queues for messageId {}", count, messageId);
|
||||
} catch (NestedRuntimeException e) {
|
||||
log.error("Fail to createWithPolicy queue {}", messageId, e);
|
||||
throw new DaoException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CustomerQueue> getWithPolicies(Collection<Long> ids) throws DaoException {
|
||||
final String sql =
|
||||
" select q.id, q.hook_id, q.customer_id, wh.party_id, wh.url, k.pub_key, k.priv_key, wh.enabled, " +
|
||||
" wh.retry_policy, srp.fail_count, srp.last_fail_time, " +
|
||||
" srp.next_fire_time_ms, srp.message_type " +
|
||||
" from hook.customer_queue q " +
|
||||
" join hook.webhook wh on wh.id = q.hook_id " +
|
||||
" and wh.enabled and wh.topic=CAST(:message_type as hook.message_topic)" +
|
||||
" join hook.party_data k on k.party_id = wh.party_id " +
|
||||
" left join hook.simple_retry_policy srp on q.id = srp.queue_id " +
|
||||
" and srp.message_type=CAST(:message_type as hook.message_topic)" +
|
||||
" where q.id in (:ids) and q.enabled";
|
||||
final MapSqlParameterSource params = new MapSqlParameterSource("ids", ids)
|
||||
.addValue("message_type", getMessagesTopic());
|
||||
|
||||
try {
|
||||
return jdbcTemplate.query(sql, params, queueWithPolicyRowMapper);
|
||||
} catch (NestedRuntimeException e) {
|
||||
throw new DaoException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable(long id) throws DaoException {
|
||||
final String sql = " UPDATE hook.customer_queue SET enabled = FALSE where id=:id;";
|
||||
try {
|
||||
jdbcTemplate.update(sql, new MapSqlParameterSource("id", id));
|
||||
} catch (NestedRuntimeException e) {
|
||||
log.error("Fail to disable queue: {}", id, e);
|
||||
throw new DaoException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public String getMessagesTopic() {
|
||||
return Event.TopicEnum.CUSTOMERSTOPIC.getValue();
|
||||
}
|
||||
}
|
@ -1,112 +0,0 @@
|
||||
package dev.vality.hooker.dao.impl;
|
||||
|
||||
import dev.vality.hooker.dao.AbstractTaskDao;
|
||||
import dev.vality.hooker.exception.DaoException;
|
||||
import dev.vality.hooker.model.Task;
|
||||
import dev.vality.swag_webhook_events.model.Event;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.NestedRuntimeException;
|
||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class CustomerTaskDao extends AbstractTaskDao {
|
||||
|
||||
public CustomerTaskDao(NamedParameterJdbcTemplate jdbcTemplate) {
|
||||
super(jdbcTemplate);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMessageTopic() {
|
||||
return Event.TopicEnum.CUSTOMERSTOPIC.getValue();
|
||||
}
|
||||
|
||||
public void create(long messageId) throws DaoException {
|
||||
final String sql =
|
||||
" insert into hook.scheduled_task(message_id, queue_id, message_type)" +
|
||||
" select m.id, q.id, w.topic" +
|
||||
" from hook.customer_message m" +
|
||||
" join hook.webhook w on m.party_id = w.party_id " +
|
||||
" and w.enabled and w.topic=CAST(:message_type as hook.message_topic)" +
|
||||
" join hook.webhook_to_events wte on wte.hook_id = w.id" +
|
||||
" join hook.customer_queue q on q.hook_id=w.id and q.enabled and q.customer_id=m.customer_id" +
|
||||
" where m.id = :message_id " +
|
||||
" and m.event_type = wte.event_type " +
|
||||
" and (m.customer_shop_id = wte.invoice_shop_id or wte.invoice_shop_id is null) " +
|
||||
" ON CONFLICT (message_id, queue_id, message_type) DO NOTHING";
|
||||
try {
|
||||
jdbcTemplate.update(sql, new MapSqlParameterSource("message_id", messageId)
|
||||
.addValue("message_type", getMessageTopic()));
|
||||
} catch (NestedRuntimeException e) {
|
||||
log.error("Fail to create tasks for messages.", e);
|
||||
throw new DaoException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public int create(long hookId, String customerId) throws DaoException {
|
||||
final String sql =
|
||||
" insert into hook.scheduled_task(message_id, queue_id, message_type)" +
|
||||
" select m.id, q.id, w.topic" +
|
||||
" from hook.customer_message m" +
|
||||
" join hook.webhook w on m.party_id = w.party_id " +
|
||||
" and w.id = :hook_id " +
|
||||
" and w.enabled " +
|
||||
" and w.topic=CAST(:message_type as hook.message_topic)" +
|
||||
" join hook.webhook_to_events wte on wte.hook_id = w.id" +
|
||||
" join hook.customer_queue q on q.hook_id=w.id " +
|
||||
" and q.enabled " +
|
||||
" and q.customer_id=m.customer_id" +
|
||||
" where m.customer_id = :customer_id " +
|
||||
" and m.event_type = wte.event_type " +
|
||||
" and (m.customer_shop_id = wte.invoice_shop_id or wte.invoice_shop_id is null) " +
|
||||
" ON CONFLICT (message_id, queue_id, message_type) DO NOTHING";
|
||||
try {
|
||||
return jdbcTemplate.update(sql, new MapSqlParameterSource("hook_id", hookId)
|
||||
.addValue("customer_id", customerId)
|
||||
.addValue("message_type", getMessageTopic()));
|
||||
} catch (NestedRuntimeException e) {
|
||||
log.error("Fail to create tasks for messages.", e);
|
||||
throw new DaoException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Long, List<Task>> getScheduled() throws DaoException {
|
||||
final String sql = " WITH scheduled AS (" +
|
||||
"SELECT st.message_id, st.queue_id, cq.customer_id " +
|
||||
"FROM hook.scheduled_task st " +
|
||||
"JOIN hook.customer_queue cq ON st.queue_id=cq.id AND cq.enabled " +
|
||||
"JOIN hook.simple_retry_policy srp ON st.queue_id=srp.queue_id " +
|
||||
"AND st.message_type=srp.message_type " +
|
||||
"JOIN hook.webhook w ON cq.hook_id = w.id AND w.enabled " +
|
||||
"WHERE st.message_type = CAST(:message_type as hook.message_topic) " +
|
||||
"AND COALESCE(srp.next_fire_time_ms, 0) < :curr_time " +
|
||||
"ORDER BY w.availability ASC, st.message_id ASC " +
|
||||
"LIMIT 1 " +
|
||||
"FOR UPDATE OF cq SKIP LOCKED " +
|
||||
"), locked_customer_queue AS (" +
|
||||
" SELECT ciq.id FROM hook.customer_queue ciq " +
|
||||
" WHERE ciq.customer_id IN (SELECT DISTINCT schd.customer_id FROM scheduled schd) " +
|
||||
" FOR UPDATE OF ciq SKIP LOCKED " +
|
||||
") SELECT message_id, queue_id FROM hook.scheduled_task s " +
|
||||
" JOIN locked_customer_queue lq ON s.queue_id = lq.id " +
|
||||
" ORDER BY s.message_id" +
|
||||
" FOR UPDATE OF s SKIP LOCKED";
|
||||
try {
|
||||
List<Task> tasks = jdbcTemplate.query(sql,
|
||||
new MapSqlParameterSource("message_type", getMessageTopic())
|
||||
.addValue("curr_time", System.currentTimeMillis()),
|
||||
taskRowMapper);
|
||||
return splitByQueue(tasks);
|
||||
} catch (NestedRuntimeException e) {
|
||||
log.warn("Fail to get active tasks from scheduled_task", e);
|
||||
throw new DaoException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
package dev.vality.hooker.dao.impl;
|
||||
|
||||
import dev.vality.hooker.dao.IdsGeneratorDao;
|
||||
import dev.vality.hooker.exception.DaoException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.NestedRuntimeException;
|
||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class EventIdsGeneratorDaoImpl implements IdsGeneratorDao {
|
||||
|
||||
private final NamedParameterJdbcTemplate jdbcTemplate;
|
||||
|
||||
@Override
|
||||
public List<Long> get(int size) throws DaoException {
|
||||
try {
|
||||
String sql = "select nextval('hook.event_id_seq') from generate_series(1, :size)";
|
||||
MapSqlParameterSource parameterSource = new MapSqlParameterSource().addValue("size", size);
|
||||
return jdbcTemplate.queryForList(sql, parameterSource, Long.class);
|
||||
} catch (NestedRuntimeException e) {
|
||||
throw new DaoException(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -21,16 +21,9 @@ import org.springframework.jdbc.support.KeyHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Created by inal on 28.11.2016.
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
|
137
src/main/java/dev/vality/hooker/dao/impl/InvoicingDaoImpl.java
Normal file
137
src/main/java/dev/vality/hooker/dao/impl/InvoicingDaoImpl.java
Normal file
@ -0,0 +1,137 @@
|
||||
package dev.vality.hooker.dao.impl;
|
||||
|
||||
import dev.vality.hooker.dao.InvoicingMessageDao;
|
||||
import dev.vality.hooker.dao.rowmapper.InvoicingRowMapper;
|
||||
import dev.vality.hooker.exception.DaoException;
|
||||
import dev.vality.hooker.exception.NotFoundException;
|
||||
import dev.vality.hooker.model.InvoicingMessage;
|
||||
import dev.vality.hooker.model.InvoicingMessageKey;
|
||||
import dev.vality.hooker.model.WebhookMessageModel;
|
||||
import dev.vality.swag_webhook_events.model.Event;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.NestedRuntimeException;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||
import org.springframework.jdbc.support.GeneratedKeyHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class InvoicingDaoImpl implements InvoicingMessageDao {
|
||||
|
||||
private final RowMapper<InvoicingMessage> invoicingMessageRowMapper;
|
||||
private final RowMapper<WebhookMessageModel<InvoicingMessage>> webhookMessageModelRowMapper;
|
||||
private final NamedParameterJdbcTemplate jdbcTemplate;
|
||||
|
||||
@Value("${parent.not.exist.id:-1}")
|
||||
private Long parentNotExistId;
|
||||
|
||||
@Override
|
||||
public Long save(InvoicingMessage message) {
|
||||
try {
|
||||
final String sql = "INSERT INTO hook.message" +
|
||||
"(event_time, sequence_id, change_id, type, party_id, event_type, " +
|
||||
"invoice_id, shop_id, invoice_status, payment_id, payment_status, refund_id, refund_status) " +
|
||||
"VALUES " +
|
||||
"(:event_time, :sequence_id, :change_id, :type, :party_id, " +
|
||||
"CAST(:event_type as hook.eventtype), :invoice_id, :shop_id, :invoice_status, :payment_id, " +
|
||||
":payment_status, :refund_id, :refund_status) " +
|
||||
"ON CONFLICT (invoice_id, sequence_id, change_id) DO NOTHING " +
|
||||
"RETURNING id";
|
||||
|
||||
MapSqlParameterSource sqlParameterSources = new MapSqlParameterSource()
|
||||
.addValue(InvoicingRowMapper.EVENT_TIME, message.getEventTime())
|
||||
.addValue(InvoicingRowMapper.SEQUENCE_ID, message.getSequenceId())
|
||||
.addValue(InvoicingRowMapper.CHANGE_ID, message.getChangeId())
|
||||
.addValue(InvoicingRowMapper.TYPE, message.getType().getValue())
|
||||
.addValue(InvoicingRowMapper.PARTY_ID, message.getPartyId())
|
||||
.addValue(InvoicingRowMapper.EVENT_TYPE, message.getEventType().toString())
|
||||
.addValue(InvoicingRowMapper.INVOICE_ID, message.getSourceId())
|
||||
.addValue(InvoicingRowMapper.SHOP_ID, message.getShopId())
|
||||
.addValue(InvoicingRowMapper.INVOICE_STATUS, message.getInvoiceStatus().getValue())
|
||||
.addValue(InvoicingRowMapper.PAYMENT_ID, message.getPaymentId())
|
||||
.addValue(InvoicingRowMapper.PAYMENT_STATUS,
|
||||
message.getPaymentStatus() != null ? message.getPaymentStatus().getValue() : null)
|
||||
.addValue(InvoicingRowMapper.REFUND_ID, message.getRefundId())
|
||||
.addValue(InvoicingRowMapper.REFUND_STATUS,
|
||||
message.getRefundStatus() != null ? message.getRefundStatus().getValue() : null);
|
||||
GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
|
||||
jdbcTemplate.update(sql, sqlParameterSources, keyHolder);
|
||||
return keyHolder.getKey() == null ? null : keyHolder.getKey().longValue();
|
||||
} catch (NestedRuntimeException e) {
|
||||
throw new DaoException("Couldn't save batch messages: " + message.getSourceId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InvoicingMessage getInvoicingMessage(InvoicingMessageKey key) throws NotFoundException, DaoException {
|
||||
final String sql = "SELECT * FROM hook.message WHERE invoice_id =:invoice_id" +
|
||||
" AND (payment_id IS NULL OR payment_id=:payment_id)" +
|
||||
" AND (refund_id IS NULL OR refund_id=:refund_id)" +
|
||||
" AND type =:type ORDER BY id DESC LIMIT 1";
|
||||
var params = new MapSqlParameterSource(InvoicingRowMapper.INVOICE_ID, key.getInvoiceId())
|
||||
.addValue(InvoicingRowMapper.PAYMENT_ID, key.getPaymentId())
|
||||
.addValue(InvoicingRowMapper.REFUND_ID, key.getRefundId())
|
||||
.addValue(InvoicingRowMapper.TYPE, key.getType().getValue());
|
||||
try {
|
||||
return jdbcTemplate.queryForObject(sql, params, invoicingMessageRowMapper);
|
||||
} catch (EmptyResultDataAccessException e) {
|
||||
throw new NotFoundException(String.format("InvoicingMessage not found %s!", key));
|
||||
} catch (NestedRuntimeException e) {
|
||||
throw new DaoException(String.format("InvoicingMessage error %s", key), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<WebhookMessageModel<InvoicingMessage>> getWebhookModels(Long messageId) {
|
||||
final String sql = "select m.*, w.id as hook_id, w.url, pk.priv_key" +
|
||||
" from hook.message m" +
|
||||
" join hook.webhook w on m.party_id = w.party_id " +
|
||||
" and w.enabled and w.topic=CAST(:message_type as hook.message_topic)" +
|
||||
" join hook.webhook_to_events wte on wte.hook_id = w.id" +
|
||||
" join hook.party_data pk on w.party_id=pk.party_id" +
|
||||
" where m.id =:id " +
|
||||
" and m.event_type = wte.event_type " +
|
||||
" and (m.shop_id = wte.invoice_shop_id or wte.invoice_shop_id is null) " +
|
||||
" and (m.invoice_status = wte.invoice_status or wte.invoice_status is null) " +
|
||||
" and (m.payment_status = wte.invoice_payment_status or wte.invoice_payment_status is null)" +
|
||||
" and (m.refund_status = wte.invoice_payment_refund_status " +
|
||||
" or wte.invoice_payment_refund_status is null)";
|
||||
MapSqlParameterSource mapSqlParameterSource = new MapSqlParameterSource().addValue("id", messageId)
|
||||
.addValue("message_type", Event.TopicEnum.INVOICESTOPIC.getValue());
|
||||
return jdbcTemplate.query(sql, mapSqlParameterSource, webhookMessageModelRowMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getParentId(Long hookId, String invoiceId, Long messageId) {
|
||||
final String sql = "select m.id" +
|
||||
" from hook.message m " +
|
||||
" join hook.webhook w on w.id=:hook_id" +
|
||||
" join hook.webhook_to_events wte on wte.hook_id = w.id" +
|
||||
" where m.invoice_id =:invoice_id" +
|
||||
" and m.id <:id " +
|
||||
" and m.event_type = wte.event_type " +
|
||||
" and (m.shop_id = wte.invoice_shop_id or wte.invoice_shop_id is null) " +
|
||||
" and (m.invoice_status = wte.invoice_status or wte.invoice_status is null) " +
|
||||
" and (m.payment_status = wte.invoice_payment_status or wte.invoice_payment_status is null)" +
|
||||
" and (m.refund_status = wte.invoice_payment_refund_status " +
|
||||
" or wte.invoice_payment_refund_status is null)" +
|
||||
" order by id desc limit 1 ";
|
||||
MapSqlParameterSource mapSqlParameterSource = new MapSqlParameterSource()
|
||||
.addValue("hook_id", hookId)
|
||||
.addValue("invoice_id", invoiceId)
|
||||
.addValue("id", messageId);
|
||||
try {
|
||||
return jdbcTemplate.queryForObject(sql, mapSqlParameterSource, Long.class);
|
||||
} catch (EmptyResultDataAccessException e) {
|
||||
return parentNotExistId;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,132 +0,0 @@
|
||||
package dev.vality.hooker.dao.impl;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import dev.vality.hooker.dao.InvoicingMessageDao;
|
||||
import dev.vality.hooker.exception.DaoException;
|
||||
import dev.vality.hooker.exception.NotFoundException;
|
||||
import dev.vality.hooker.model.InvoicingMessage;
|
||||
import dev.vality.hooker.model.InvoicingMessageKey;
|
||||
import dev.vality.hooker.utils.KeyUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.NestedRuntimeException;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import static dev.vality.hooker.dao.impl.InvoicingMessageRowMapper.CHANGE_ID;
|
||||
import static dev.vality.hooker.dao.impl.InvoicingMessageRowMapper.EVENT_TIME;
|
||||
import static dev.vality.hooker.dao.impl.InvoicingMessageRowMapper.EVENT_TYPE;
|
||||
import static dev.vality.hooker.dao.impl.InvoicingMessageRowMapper.ID;
|
||||
import static dev.vality.hooker.dao.impl.InvoicingMessageRowMapper.INVOICE_ID;
|
||||
import static dev.vality.hooker.dao.impl.InvoicingMessageRowMapper.INVOICE_STATUS;
|
||||
import static dev.vality.hooker.dao.impl.InvoicingMessageRowMapper.NEW_EVENT_ID;
|
||||
import static dev.vality.hooker.dao.impl.InvoicingMessageRowMapper.PARTY_ID;
|
||||
import static dev.vality.hooker.dao.impl.InvoicingMessageRowMapper.PAYMENT_ID;
|
||||
import static dev.vality.hooker.dao.impl.InvoicingMessageRowMapper.PAYMENT_STATUS;
|
||||
import static dev.vality.hooker.dao.impl.InvoicingMessageRowMapper.REFUND_ID;
|
||||
import static dev.vality.hooker.dao.impl.InvoicingMessageRowMapper.REFUND_STATUS;
|
||||
import static dev.vality.hooker.dao.impl.InvoicingMessageRowMapper.SEQUENCE_ID;
|
||||
import static dev.vality.hooker.dao.impl.InvoicingMessageRowMapper.SHOP_ID;
|
||||
import static dev.vality.hooker.dao.impl.InvoicingMessageRowMapper.TYPE;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class InvoicingMessageDaoImpl implements InvoicingMessageDao {
|
||||
|
||||
private static RowMapper<InvoicingMessage> messageRowMapper = new InvoicingMessageRowMapper();
|
||||
private final NamedParameterJdbcTemplate jdbcTemplate;
|
||||
private final Cache<InvoicingMessageKey, InvoicingMessage> invoicingCache;
|
||||
|
||||
public void saveBatch(List<InvoicingMessage> messages) throws DaoException {
|
||||
int[] batchMessagesResult = saveBatchMessages(messages);
|
||||
log.info("Batch messages saved info {}",
|
||||
IntStream.range(0, messages.size())
|
||||
.mapToObj(i -> "(" + i + " : " + batchMessagesResult[i] + " : " + messages.get(i).getId() +
|
||||
" : " + messages.get(i).getInvoiceId() + ")")
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
private int[] saveBatchMessages(List<InvoicingMessage> messages) {
|
||||
try {
|
||||
messages.forEach(m -> invoicingCache.put(KeyUtils.key(m), m));
|
||||
|
||||
final String sql = "INSERT INTO hook.message" +
|
||||
"(id, new_event_id, event_time, sequence_id, change_id, type, party_id, event_type, " +
|
||||
"invoice_id, shop_id, invoice_status, payment_id, payment_status, refund_id, refund_status) " +
|
||||
"VALUES " +
|
||||
"(:id, :new_event_id, :event_time, :sequence_id, :change_id, :type, :party_id, " +
|
||||
"CAST(:event_type as hook.eventtype), :invoice_id, :shop_id, :invoice_status, :payment_id, " +
|
||||
":payment_status, :refund_id, :refund_status) " +
|
||||
"ON CONFLICT (invoice_id, sequence_id, change_id) DO NOTHING ";
|
||||
|
||||
MapSqlParameterSource[] sqlParameterSources = messages.stream()
|
||||
.map(message -> new MapSqlParameterSource()
|
||||
.addValue(ID, message.getId())
|
||||
.addValue(NEW_EVENT_ID, message.getEventId())
|
||||
.addValue(EVENT_TIME, message.getEventTime())
|
||||
.addValue(SEQUENCE_ID, message.getSequenceId())
|
||||
.addValue(CHANGE_ID, message.getChangeId())
|
||||
.addValue(TYPE, message.getType().getValue())
|
||||
.addValue(PARTY_ID, message.getPartyId())
|
||||
.addValue(EVENT_TYPE, message.getEventType().toString())
|
||||
.addValue(INVOICE_ID, message.getInvoiceId())
|
||||
.addValue(SHOP_ID, message.getShopId())
|
||||
.addValue(INVOICE_STATUS, message.getInvoiceStatus().getValue())
|
||||
.addValue(PAYMENT_ID, message.getPaymentId())
|
||||
.addValue(PAYMENT_STATUS,
|
||||
message.getPaymentStatus() != null ? message.getPaymentStatus().getValue() : null)
|
||||
.addValue(REFUND_ID, message.getRefundId())
|
||||
.addValue(REFUND_STATUS,
|
||||
message.getRefundStatus() != null ? message.getRefundStatus().getValue() : null))
|
||||
.toArray(MapSqlParameterSource[]::new);
|
||||
return jdbcTemplate.batchUpdate(sql, sqlParameterSources);
|
||||
} catch (NestedRuntimeException e) {
|
||||
List<String> shortInfo = messages.stream()
|
||||
.map(m -> "(" + m.getId() + " : " + m.getInvoiceId() + ")")
|
||||
.collect(Collectors.toList());
|
||||
throw new DaoException("Couldn't save batch messages: " + shortInfo, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InvoicingMessage getInvoicingMessage(InvoicingMessageKey key) throws NotFoundException, DaoException {
|
||||
InvoicingMessage result = invoicingCache.getIfPresent(key);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
final String sql = "SELECT * FROM hook.message WHERE invoice_id =:invoice_id" +
|
||||
" AND (payment_id IS NULL OR payment_id=:payment_id)" +
|
||||
" AND (refund_id IS NULL OR refund_id=:refund_id)" +
|
||||
" AND type =:type ORDER BY id DESC LIMIT 1";
|
||||
MapSqlParameterSource params = new MapSqlParameterSource(INVOICE_ID, key.getInvoiceId())
|
||||
.addValue(PAYMENT_ID, key.getPaymentId())
|
||||
.addValue(REFUND_ID, key.getRefundId())
|
||||
.addValue(TYPE, key.getType().getValue());
|
||||
try {
|
||||
return jdbcTemplate.queryForObject(sql, params, messageRowMapper);
|
||||
} catch (EmptyResultDataAccessException e) {
|
||||
throw new NotFoundException(String.format("InvoicingMessage not found %s!", key.toString()));
|
||||
} catch (NestedRuntimeException e) {
|
||||
throw new DaoException(String.format("InvoicingMessage error %s", key.toString()), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<InvoicingMessage> getBy(Collection<Long> messageIds) throws DaoException {
|
||||
final String sql = "SELECT * FROM hook.message WHERE id in (:ids)";
|
||||
try {
|
||||
return jdbcTemplate.query(sql, new MapSqlParameterSource("ids", messageIds), messageRowMapper);
|
||||
} catch (NestedRuntimeException e) {
|
||||
throw new DaoException("Couldn't get invoice message by ids: " + messageIds, e);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,105 +0,0 @@
|
||||
package dev.vality.hooker.dao.impl;
|
||||
|
||||
import dev.vality.hooker.dao.QueueDao;
|
||||
import dev.vality.hooker.exception.DaoException;
|
||||
import dev.vality.hooker.model.Hook;
|
||||
import dev.vality.hooker.model.InvoicingQueue;
|
||||
import dev.vality.hooker.retry.RetryPolicyType;
|
||||
import dev.vality.swag_webhook_events.model.Event;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.core.NestedRuntimeException;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by inalarsanukaev on 14.11.17.
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class InvoicingQueueDao implements QueueDao<InvoicingQueue> {
|
||||
|
||||
public static RowMapper<InvoicingQueue> queueWithPolicyRowMapper = (rs, i) -> {
|
||||
InvoicingQueue queue = new InvoicingQueue();
|
||||
queue.setId(rs.getLong("id"));
|
||||
queue.setInvoiceId(rs.getString("invoice_id"));
|
||||
Hook hook = new Hook();
|
||||
hook.setId(rs.getLong("hook_id"));
|
||||
hook.setPartyId(rs.getString("party_id"));
|
||||
hook.setTopic(rs.getString("message_type"));
|
||||
hook.setUrl(rs.getString("url"));
|
||||
hook.setPubKey(rs.getString("pub_key"));
|
||||
hook.setPrivKey(rs.getString("priv_key"));
|
||||
hook.setEnabled(rs.getBoolean("enabled"));
|
||||
RetryPolicyType retryPolicyType = RetryPolicyType.valueOf(rs.getString("retry_policy"));
|
||||
hook.setRetryPolicyType(retryPolicyType);
|
||||
queue.setHook(hook);
|
||||
queue.setRetryPolicyRecord(retryPolicyType.build(rs));
|
||||
return queue;
|
||||
};
|
||||
private final NamedParameterJdbcTemplate jdbcTemplate;
|
||||
|
||||
public int[] saveBatchWithPolicies(List<Long> messageIds) throws DaoException {
|
||||
final String sql = "with queue as ( " +
|
||||
" insert into hook.invoicing_queue(hook_id, invoice_id)" +
|
||||
" select w.id , m.invoice_id" +
|
||||
" from hook.message m" +
|
||||
" join hook.webhook w on m.party_id = w.party_id " +
|
||||
" and w.enabled and w.topic=CAST(:message_type as hook.message_topic)" +
|
||||
" where m.id = :id " +
|
||||
" on conflict(hook_id, invoice_id) do nothing returning *) " +
|
||||
" insert into hook.simple_retry_policy(queue_id, message_type) " +
|
||||
" select id, CAST(:message_type as hook.message_topic) from queue";
|
||||
MapSqlParameterSource[] sqlParameterSources = messageIds
|
||||
.stream()
|
||||
.map(id -> new MapSqlParameterSource()
|
||||
.addValue("id", id)
|
||||
.addValue("message_type", Event.TopicEnum.INVOICESTOPIC.getValue()))
|
||||
.toArray(MapSqlParameterSource[]::new);
|
||||
try {
|
||||
return jdbcTemplate.batchUpdate(sql, sqlParameterSources);
|
||||
} catch (NestedRuntimeException e) {
|
||||
throw new DaoException("Couldn't save queue batch with messageIds=" + messageIds, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<InvoicingQueue> getWithPolicies(Collection<Long> ids) {
|
||||
final String sql =
|
||||
" select q.id, q.hook_id, q.invoice_id, wh.party_id, wh.url, k.pub_key, k.priv_key, wh.enabled, " +
|
||||
" wh.retry_policy, srp.fail_count, srp.last_fail_time, " +
|
||||
" srp.next_fire_time_ms, srp.message_type " +
|
||||
" from hook.invoicing_queue q " +
|
||||
" join hook.webhook wh on wh.id = q.hook_id " +
|
||||
" and wh.enabled and wh.topic=CAST(:message_type as hook.message_topic)" +
|
||||
" join hook.party_data k on k.party_id = wh.party_id " +
|
||||
" left join hook.simple_retry_policy srp on q.id = srp.queue_id " +
|
||||
" and srp.message_type=CAST(:message_type as hook.message_topic)" +
|
||||
" where q.id in (:ids) and q.enabled";
|
||||
final MapSqlParameterSource params = new MapSqlParameterSource("ids", ids)
|
||||
.addValue("message_type", getMessagesTopic());
|
||||
try {
|
||||
return jdbcTemplate.query(sql, params, queueWithPolicyRowMapper);
|
||||
} catch (NestedRuntimeException e) {
|
||||
throw new DaoException("Couldn't get queue by queueIds=" + ids, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable(long id) throws DaoException {
|
||||
final String sql = " UPDATE hook.invoicing_queue SET enabled = FALSE where id=:id;";
|
||||
try {
|
||||
jdbcTemplate.update(sql, new MapSqlParameterSource("id", id));
|
||||
} catch (NestedRuntimeException e) {
|
||||
throw new DaoException("Couldn't disable queue with id=" + id, e);
|
||||
}
|
||||
}
|
||||
|
||||
public String getMessagesTopic() {
|
||||
return Event.TopicEnum.INVOICESTOPIC.getValue();
|
||||
}
|
||||
}
|
@ -1,130 +0,0 @@
|
||||
package dev.vality.hooker.dao.impl;
|
||||
|
||||
import dev.vality.hooker.dao.AbstractTaskDao;
|
||||
import dev.vality.hooker.exception.DaoException;
|
||||
import dev.vality.hooker.model.Task;
|
||||
import dev.vality.swag_webhook_events.model.Event;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.NestedRuntimeException;
|
||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Created by jeckep on 17.04.17.
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class InvoicingTaskDao extends AbstractTaskDao {
|
||||
|
||||
public InvoicingTaskDao(NamedParameterJdbcTemplate jdbcTemplate) {
|
||||
super(jdbcTemplate);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMessageTopic() {
|
||||
return Event.TopicEnum.INVOICESTOPIC.getValue();
|
||||
}
|
||||
|
||||
//TODO limit invoices from hook
|
||||
public int save(List<Long> messageIds) throws DaoException {
|
||||
if (messageIds == null || messageIds.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
final String sql =
|
||||
" insert into hook.scheduled_task(message_id, queue_id, message_type)" +
|
||||
" select m.id, q.id, w.topic" +
|
||||
" from hook.message m" +
|
||||
" join hook.webhook w on m.party_id = w.party_id " +
|
||||
" and w.enabled and w.topic=CAST(:message_type as hook.message_topic)" +
|
||||
" join hook.webhook_to_events wte on wte.hook_id = w.id" +
|
||||
" join hook.invoicing_queue q on q.hook_id=w.id and q.enabled and q.invoice_id=m.invoice_id" +
|
||||
" where m.id in (:message_ids) " +
|
||||
" and m.event_type = wte.event_type " +
|
||||
" and (m.shop_id = wte.invoice_shop_id or wte.invoice_shop_id is null) " +
|
||||
" and (m.invoice_status = wte.invoice_status or wte.invoice_status is null) " +
|
||||
" and (m.payment_status = wte.invoice_payment_status or wte.invoice_payment_status is null)" +
|
||||
" and (m.refund_status = wte.invoice_payment_refund_status " +
|
||||
" or wte.invoice_payment_refund_status is null)" +
|
||||
" ON CONFLICT (message_id, queue_id, message_type) DO NOTHING";
|
||||
|
||||
final MapSqlParameterSource sqlParameterSources = new MapSqlParameterSource("message_ids", messageIds)
|
||||
.addValue("message_type", Event.TopicEnum.INVOICESTOPIC.getValue());
|
||||
|
||||
try {
|
||||
return jdbcTemplate.update(sql, sqlParameterSources);
|
||||
} catch (NestedRuntimeException e) {
|
||||
throw new DaoException("Failed to create tasks for messageIds=" + messageIds, e);
|
||||
}
|
||||
}
|
||||
|
||||
public int save(Long hookId, String invoiceId) throws DaoException {
|
||||
final String sql =
|
||||
" insert into hook.scheduled_task(message_id, queue_id, message_type)" +
|
||||
" select m.id, q.id, w.topic" +
|
||||
" from hook.message m" +
|
||||
" join hook.webhook w on m.party_id = w.party_id " +
|
||||
" and w.id = :hook_id " +
|
||||
" and w.enabled " +
|
||||
" and w.topic=CAST(:message_type as hook.message_topic)" +
|
||||
" join hook.webhook_to_events wte on wte.hook_id = w.id" +
|
||||
" join hook.invoicing_queue q on q.hook_id=w.id " +
|
||||
" and q.enabled " +
|
||||
" and q.invoice_id=m.invoice_id" +
|
||||
" where m.invoice_id = :invoice_id " +
|
||||
" and m.event_type = wte.event_type " +
|
||||
" and (m.shop_id = wte.invoice_shop_id or wte.invoice_shop_id is null) " +
|
||||
" and (m.invoice_status = wte.invoice_status or wte.invoice_status is null) " +
|
||||
" and (m.payment_status = wte.invoice_payment_status or wte.invoice_payment_status is null)" +
|
||||
" and (m.refund_status = wte.invoice_payment_refund_status " +
|
||||
" or wte.invoice_payment_refund_status is null)" +
|
||||
" ON CONFLICT (message_id, queue_id, message_type) DO NOTHING";
|
||||
|
||||
final MapSqlParameterSource sqlParameterSources = new MapSqlParameterSource("hook_id", hookId)
|
||||
.addValue("invoice_id", invoiceId)
|
||||
.addValue("message_type", Event.TopicEnum.INVOICESTOPIC.getValue());
|
||||
|
||||
try {
|
||||
return jdbcTemplate.update(sql, sqlParameterSources);
|
||||
} catch (NestedRuntimeException e) {
|
||||
throw new DaoException("Failed to create tasks for hook_id=" + hookId, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Long, List<Task>> getScheduled() throws DaoException {
|
||||
final String sql = " WITH scheduled AS (" +
|
||||
"SELECT st.message_id, st.queue_id, iq.invoice_id " +
|
||||
"FROM hook.scheduled_task st " +
|
||||
"JOIN hook.invoicing_queue iq ON st.queue_id=iq.id AND iq.enabled " +
|
||||
"JOIN hook.simple_retry_policy srp ON st.queue_id=srp.queue_id " +
|
||||
"AND st.message_type=srp.message_type " +
|
||||
"JOIN hook.webhook w ON iq.hook_id = w.id AND w.enabled " +
|
||||
"WHERE st.message_type = CAST(:message_type as hook.message_topic) " +
|
||||
"AND COALESCE(srp.next_fire_time_ms, 0) < :curr_time " +
|
||||
"ORDER BY w.availability ASC, st.message_id ASC " +
|
||||
"LIMIT 1 " +
|
||||
"FOR UPDATE OF iq SKIP LOCKED " +
|
||||
"), locked_invoicing_queue AS (" +
|
||||
" SELECT liq.id FROM hook.invoicing_queue liq " +
|
||||
" WHERE liq.invoice_id IN (SELECT DISTINCT schd.invoice_id FROM scheduled schd) " +
|
||||
" FOR UPDATE OF liq SKIP LOCKED " +
|
||||
") SELECT message_id, queue_id FROM hook.scheduled_task s " +
|
||||
" JOIN locked_invoicing_queue lq ON s.queue_id = lq.id " +
|
||||
" ORDER BY s.message_id " +
|
||||
" FOR UPDATE OF s SKIP LOCKED";
|
||||
try {
|
||||
List<Task> tasks = jdbcTemplate.query(sql,
|
||||
new MapSqlParameterSource("message_type", getMessageTopic())
|
||||
.addValue("curr_time", System.currentTimeMillis()),
|
||||
taskRowMapper);
|
||||
return splitByQueue(tasks);
|
||||
} catch (NestedRuntimeException e) {
|
||||
log.warn("Fail to get active tasks from scheduled_task", e);
|
||||
throw new DaoException(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
package dev.vality.hooker.dao.impl;
|
||||
|
||||
import dev.vality.hooker.dao.IdsGeneratorDao;
|
||||
import dev.vality.hooker.exception.DaoException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.NestedRuntimeException;
|
||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class MessageIdsGeneratorDaoImpl implements IdsGeneratorDao {
|
||||
|
||||
private final NamedParameterJdbcTemplate jdbcTemplate;
|
||||
|
||||
@Override
|
||||
public List<Long> get(int size) throws DaoException {
|
||||
try {
|
||||
String sql = "select nextval('hook.seq') from generate_series(1, :size)";
|
||||
MapSqlParameterSource parameterSource = new MapSqlParameterSource().addValue("size", size);
|
||||
return jdbcTemplate.queryForList(sql, parameterSource, Long.class);
|
||||
} catch (NestedRuntimeException e) {
|
||||
throw new DaoException(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
package dev.vality.hooker.dao.impl;
|
||||
|
||||
import dev.vality.hooker.dao.SimpleRetryPolicyDao;
|
||||
import dev.vality.hooker.exception.DaoException;
|
||||
import dev.vality.hooker.retry.impl.simple.SimpleRetryPolicyRecord;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.core.NestedRuntimeException;
|
||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class SimpleRetryPolicyDaoImpl implements SimpleRetryPolicyDao {
|
||||
|
||||
private final NamedParameterJdbcTemplate jdbcTemplate;
|
||||
|
||||
@Override
|
||||
public void update(SimpleRetryPolicyRecord record) throws DaoException {
|
||||
final String sql = "update hook.simple_retry_policy " +
|
||||
" set last_fail_time = :last_fail_time, " +
|
||||
" fail_count = :fail_count, " +
|
||||
" next_fire_time_ms = :next_fire_time_ms" +
|
||||
" where queue_id = :queue_id and message_type=CAST(:message_type as hook.message_topic)";
|
||||
try {
|
||||
jdbcTemplate.update(sql, new MapSqlParameterSource("queue_id", record.getQueueId())
|
||||
.addValue("message_type", record.getMessageType())
|
||||
.addValue("last_fail_time", record.getLastFailTime())
|
||||
.addValue("fail_count", record.getFailCount())
|
||||
.addValue("next_fire_time_ms", record.getNextFireTime()));
|
||||
} catch (NestedRuntimeException e) {
|
||||
throw new DaoException("Fail to update simple_retry_policy for record=" + record.getQueueId(), e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package dev.vality.hooker.dao.rowmapper;
|
||||
|
||||
import dev.vality.hooker.model.CustomerMessage;
|
||||
import dev.vality.hooker.model.CustomerMessageEnum;
|
||||
import dev.vality.hooker.model.EventType;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
@Component
|
||||
public class CustomerRowMapper implements RowMapper<CustomerMessage> {
|
||||
public static final String ID = "id";
|
||||
public static final String TYPE = "type";
|
||||
public static final String OCCURED_AT = "occured_at";
|
||||
public static final String SEQUENCE_ID = "sequence_id";
|
||||
public static final String CHANGE_ID = "change_id";
|
||||
public static final String PARTY_ID = "party_id";
|
||||
public static final String EVENT_TYPE = "event_type";
|
||||
public static final String CUSTOMER_ID = "customer_id";
|
||||
public static final String CUSTOMER_SHOP_ID = "customer_shop_id";
|
||||
public static final String BINDING_ID = "binding_id";
|
||||
|
||||
@Override
|
||||
public CustomerMessage mapRow(ResultSet rs, int rowNum) throws SQLException {
|
||||
CustomerMessage message = new CustomerMessage();
|
||||
message.setId(rs.getLong(ID));
|
||||
message.setPartyId(rs.getString(PARTY_ID));
|
||||
message.setEventTime(rs.getString(OCCURED_AT));
|
||||
message.setSequenceId(rs.getLong(SEQUENCE_ID));
|
||||
message.setChangeId(rs.getInt(CHANGE_ID));
|
||||
message.setType(CustomerMessageEnum.lookup(rs.getString(TYPE)));
|
||||
message.setEventType(EventType.valueOf(rs.getString(EVENT_TYPE)));
|
||||
message.setSourceId(rs.getString(CUSTOMER_ID));
|
||||
message.setShopId(rs.getString(CUSTOMER_SHOP_ID));
|
||||
message.setBindingId(rs.getString(BINDING_ID));
|
||||
return message;
|
||||
}
|
||||
}
|
@ -1,20 +1,16 @@
|
||||
package dev.vality.hooker.dao.impl;
|
||||
package dev.vality.hooker.dao.rowmapper;
|
||||
|
||||
import dev.vality.hooker.model.EventType;
|
||||
import dev.vality.hooker.model.InvoiceStatusEnum;
|
||||
import dev.vality.hooker.model.InvoicingMessage;
|
||||
import dev.vality.hooker.model.InvoicingMessageEnum;
|
||||
import dev.vality.hooker.model.PaymentStatusEnum;
|
||||
import dev.vality.hooker.model.RefundStatusEnum;
|
||||
import dev.vality.hooker.model.*;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class InvoicingMessageRowMapper implements RowMapper<InvoicingMessage> {
|
||||
@Component
|
||||
public class InvoicingRowMapper implements RowMapper<InvoicingMessage> {
|
||||
|
||||
public static final String ID = "id";
|
||||
public static final String NEW_EVENT_ID = "new_event_id";
|
||||
public static final String EVENT_TIME = "event_time";
|
||||
public static final String SEQUENCE_ID = "sequence_id";
|
||||
public static final String CHANGE_ID = "change_id";
|
||||
@ -33,7 +29,6 @@ public class InvoicingMessageRowMapper implements RowMapper<InvoicingMessage> {
|
||||
public InvoicingMessage mapRow(ResultSet rs, int i) throws SQLException {
|
||||
InvoicingMessage message = new InvoicingMessage();
|
||||
message.setId(rs.getLong(ID));
|
||||
message.setEventId(rs.getLong(NEW_EVENT_ID));
|
||||
message.setEventTime(rs.getString(EVENT_TIME));
|
||||
message.setSequenceId(rs.getLong(SEQUENCE_ID));
|
||||
message.setChangeId(rs.getInt(CHANGE_ID));
|
||||
@ -41,7 +36,7 @@ public class InvoicingMessageRowMapper implements RowMapper<InvoicingMessage> {
|
||||
message.setPartyId(rs.getString(PARTY_ID));
|
||||
message.setShopId(rs.getString(SHOP_ID));
|
||||
message.setEventType(EventType.valueOf(rs.getString(EVENT_TYPE)));
|
||||
message.setInvoiceId(rs.getString(INVOICE_ID));
|
||||
message.setSourceId(rs.getString(INVOICE_ID));
|
||||
message.setInvoiceStatus(InvoiceStatusEnum.lookup(rs.getString(INVOICE_STATUS)));
|
||||
message.setPaymentId(rs.getString(PAYMENT_ID));
|
||||
message.setPaymentStatus(rs.getString(PAYMENT_STATUS) != null
|
@ -0,0 +1,29 @@
|
||||
package dev.vality.hooker.dao.rowmapper;
|
||||
|
||||
import dev.vality.hooker.model.Message;
|
||||
import dev.vality.hooker.model.WebhookMessageModel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class WebhookModelRowMapper<T extends Message> implements RowMapper<WebhookMessageModel<T>> {
|
||||
|
||||
public static final String HOOK_ID = "hook_id";
|
||||
public static final String URL = "url";
|
||||
public static final String PRIV_KEY = "priv_key";
|
||||
private final RowMapper<T> messageRowMapper;
|
||||
|
||||
@Override
|
||||
public WebhookMessageModel<T> mapRow(ResultSet rs, int rowNum) throws SQLException {
|
||||
T invoicingMessage = messageRowMapper.mapRow(rs, rowNum);
|
||||
WebhookMessageModel<T> webhookMessageModel = new WebhookMessageModel<>();
|
||||
webhookMessageModel.setMessage(invoicingMessage);
|
||||
webhookMessageModel.setHookId(rs.getLong(HOOK_ID));
|
||||
webhookMessageModel.setUrl(rs.getString(URL));
|
||||
webhookMessageModel.setPrivateKey(rs.getString(PRIV_KEY));
|
||||
return webhookMessageModel;
|
||||
}
|
||||
}
|
@ -8,14 +8,8 @@ import dev.vality.woody.thrift.impl.http.event.ServiceEventLogListener;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.servlet.GenericServlet;
|
||||
import javax.servlet.Servlet;
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.annotation.WebServlet;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@WebServlet("/hook")
|
||||
|
@ -1,48 +0,0 @@
|
||||
package dev.vality.hooker.endpoint;
|
||||
|
||||
import dev.vality.damsel.webhooker.WebhookMessageServiceSrv;
|
||||
import dev.vality.woody.api.event.CompositeServiceEventListener;
|
||||
import dev.vality.woody.thrift.impl.http.THServiceBuilder;
|
||||
import dev.vality.woody.thrift.impl.http.event.HttpServiceEventLogListener;
|
||||
import dev.vality.woody.thrift.impl.http.event.ServiceEventLogListener;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.servlet.GenericServlet;
|
||||
import javax.servlet.Servlet;
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.annotation.WebServlet;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@WebServlet("/message")
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class WebhookMessageServiceServlet extends GenericServlet {
|
||||
|
||||
private final WebhookMessageServiceSrv.Iface requestHandler;
|
||||
private Servlet thriftServlet;
|
||||
|
||||
@Override
|
||||
public void init(ServletConfig config) throws ServletException {
|
||||
log.info("Hooker servlet init.");
|
||||
super.init(config);
|
||||
thriftServlet = new THServiceBuilder()
|
||||
.withEventListener(
|
||||
new CompositeServiceEventListener<>(
|
||||
new ServiceEventLogListener(),
|
||||
new HttpServiceEventLogListener()
|
||||
)
|
||||
)
|
||||
.build(WebhookMessageServiceSrv.Iface.class, requestHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
|
||||
log.info("Start new request to WebhookMessageServiceServlet.");
|
||||
thriftServlet.service(req, res);
|
||||
}
|
||||
}
|
@ -21,4 +21,4 @@ public class RemoteHostException extends RuntimeException {
|
||||
boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
package dev.vality.hooker.handler;
|
||||
|
||||
import dev.vality.geck.filter.Filter;
|
||||
import dev.vality.hooker.model.EventInfo;
|
||||
import dev.vality.hooker.model.Message;
|
||||
|
||||
/**
|
||||
* Created by inal on 24.11.2016.
|
||||
*/
|
||||
public interface Handler<C, M extends Message> {
|
||||
default boolean accept(C change) {
|
||||
return getFilter().match(change);
|
||||
}
|
||||
|
||||
void handle(C change, EventInfo eventInfo);
|
||||
|
||||
Filter getFilter();
|
||||
}
|
@ -2,17 +2,14 @@ package dev.vality.hooker.handler;
|
||||
|
||||
import dev.vality.geck.filter.Filter;
|
||||
import dev.vality.hooker.model.EventInfo;
|
||||
import dev.vality.hooker.model.InvoicingMessageKey;
|
||||
import dev.vality.hooker.model.Message;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public interface Mapper<C, M extends Message> {
|
||||
default boolean accept(C change) {
|
||||
return getFilter().match(change);
|
||||
}
|
||||
|
||||
M handle(C change, EventInfo eventInfo, Map<InvoicingMessageKey, M> storage);
|
||||
M map(C change, EventInfo eventInfo);
|
||||
|
||||
Filter getFilter();
|
||||
}
|
||||
}
|
@ -1,31 +1,24 @@
|
||||
package dev.vality.hooker.handler.poller.customer;
|
||||
package dev.vality.hooker.handler.customer;
|
||||
|
||||
import dev.vality.geck.filter.Filter;
|
||||
import dev.vality.geck.filter.PathConditionFilter;
|
||||
import dev.vality.geck.filter.condition.IsNullCondition;
|
||||
import dev.vality.geck.filter.rule.PathConditionRule;
|
||||
import dev.vality.hooker.dao.impl.CustomerDaoImpl;
|
||||
import dev.vality.hooker.dao.impl.CustomerQueueDao;
|
||||
import dev.vality.hooker.dao.impl.CustomerTaskDao;
|
||||
import dev.vality.hooker.model.CustomerMessageEnum;
|
||||
import dev.vality.hooker.model.EventType;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Created by inalarsanukaev on 12.10.17.
|
||||
*/
|
||||
@Component
|
||||
public class CustomerBindingFailedHandler extends NeedReadCustomerEventHandler {
|
||||
public class CustomerBindingFailedMapper extends NeedReadCustomerEventMapper {
|
||||
|
||||
private EventType eventType = EventType.CUSTOMER_BINDING_FAILED;
|
||||
|
||||
private Filter filter =
|
||||
new PathConditionFilter(new PathConditionRule(eventType.getThriftPath(), new IsNullCondition().not()));
|
||||
|
||||
public CustomerBindingFailedHandler(CustomerDaoImpl customerDao,
|
||||
CustomerQueueDao customerQueueDao,
|
||||
CustomerTaskDao customerTaskDao) {
|
||||
super(customerDao, customerQueueDao, customerTaskDao);
|
||||
public CustomerBindingFailedMapper(CustomerDaoImpl customerDao) {
|
||||
super(customerDao);
|
||||
}
|
||||
|
||||
@Override
|
@ -1,4 +1,4 @@
|
||||
package dev.vality.hooker.handler.poller.customer;
|
||||
package dev.vality.hooker.handler.customer;
|
||||
|
||||
import dev.vality.damsel.payment_processing.CustomerChange;
|
||||
import dev.vality.geck.filter.Filter;
|
||||
@ -6,29 +6,21 @@ import dev.vality.geck.filter.PathConditionFilter;
|
||||
import dev.vality.geck.filter.condition.IsNullCondition;
|
||||
import dev.vality.geck.filter.rule.PathConditionRule;
|
||||
import dev.vality.hooker.dao.impl.CustomerDaoImpl;
|
||||
import dev.vality.hooker.dao.impl.CustomerQueueDao;
|
||||
import dev.vality.hooker.dao.impl.CustomerTaskDao;
|
||||
import dev.vality.hooker.model.CustomerMessage;
|
||||
import dev.vality.hooker.model.CustomerMessageEnum;
|
||||
import dev.vality.hooker.model.EventType;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
|
||||
/**
|
||||
* Created by inalarsanukaev on 12.10.17.
|
||||
*/
|
||||
@Component
|
||||
public class CustomerBindingStartedHandler extends NeedReadCustomerEventHandler {
|
||||
public class CustomerBindingStartedMapper extends NeedReadCustomerEventMapper {
|
||||
|
||||
private EventType eventType = EventType.CUSTOMER_BINDING_STARTED;
|
||||
|
||||
private Filter filter =
|
||||
new PathConditionFilter(new PathConditionRule(eventType.getThriftPath(), new IsNullCondition().not()));
|
||||
|
||||
public CustomerBindingStartedHandler(CustomerDaoImpl customerDao,
|
||||
CustomerQueueDao customerQueueDao,
|
||||
CustomerTaskDao customerTaskDao) {
|
||||
super(customerDao, customerQueueDao, customerTaskDao);
|
||||
public CustomerBindingStartedMapper(CustomerDaoImpl customerDao) {
|
||||
super(customerDao);
|
||||
}
|
||||
|
||||
@Override
|
@ -1,31 +1,24 @@
|
||||
package dev.vality.hooker.handler.poller.customer;
|
||||
package dev.vality.hooker.handler.customer;
|
||||
|
||||
import dev.vality.geck.filter.Filter;
|
||||
import dev.vality.geck.filter.PathConditionFilter;
|
||||
import dev.vality.geck.filter.condition.IsNullCondition;
|
||||
import dev.vality.geck.filter.rule.PathConditionRule;
|
||||
import dev.vality.hooker.dao.impl.CustomerDaoImpl;
|
||||
import dev.vality.hooker.dao.impl.CustomerQueueDao;
|
||||
import dev.vality.hooker.dao.impl.CustomerTaskDao;
|
||||
import dev.vality.hooker.model.CustomerMessageEnum;
|
||||
import dev.vality.hooker.model.EventType;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Created by inalarsanukaev on 12.10.17.
|
||||
*/
|
||||
@Component
|
||||
public class CustomerBindingSucceededHandler extends NeedReadCustomerEventHandler {
|
||||
public class CustomerBindingSucceededMapper extends NeedReadCustomerEventMapper {
|
||||
|
||||
private EventType eventType = EventType.CUSTOMER_BINDING_SUCCEEDED;
|
||||
|
||||
private Filter filter =
|
||||
new PathConditionFilter(new PathConditionRule(eventType.getThriftPath(), new IsNullCondition().not()));
|
||||
|
||||
public CustomerBindingSucceededHandler(CustomerDaoImpl customerDao,
|
||||
CustomerQueueDao customerQueueDao,
|
||||
CustomerTaskDao customerTaskDao) {
|
||||
super(customerDao, customerQueueDao, customerTaskDao);
|
||||
public CustomerBindingSucceededMapper(CustomerDaoImpl customerDao) {
|
||||
super(customerDao);
|
||||
}
|
||||
|
||||
@Override
|
@ -1,14 +1,12 @@
|
||||
package dev.vality.hooker.handler.poller.customer;
|
||||
package dev.vality.hooker.handler.customer;
|
||||
|
||||
import dev.vality.damsel.payment_processing.CustomerChange;
|
||||
import dev.vality.geck.filter.Filter;
|
||||
import dev.vality.geck.filter.PathConditionFilter;
|
||||
import dev.vality.geck.filter.condition.IsNullCondition;
|
||||
import dev.vality.geck.filter.rule.PathConditionRule;
|
||||
import dev.vality.hooker.dao.impl.CustomerDaoImpl;
|
||||
import dev.vality.hooker.dao.impl.CustomerQueueDao;
|
||||
import dev.vality.hooker.dao.impl.CustomerTaskDao;
|
||||
import dev.vality.hooker.exception.DaoException;
|
||||
import dev.vality.hooker.handler.Mapper;
|
||||
import dev.vality.hooker.model.CustomerMessage;
|
||||
import dev.vality.hooker.model.CustomerMessageEnum;
|
||||
import dev.vality.hooker.model.EventInfo;
|
||||
@ -16,16 +14,10 @@ import dev.vality.hooker.model.EventType;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Created by inalarsanukaev on 12.10.17.
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class CustomerCreatedHandler extends AbstractCustomerEventHandler {
|
||||
public class CustomerCreatedMapper implements Mapper<CustomerChange, CustomerMessage> {
|
||||
|
||||
private final CustomerDaoImpl customerDao;
|
||||
private final CustomerQueueDao customerQueueDao;
|
||||
private final CustomerTaskDao customerTaskDao;
|
||||
private EventType eventType = EventType.CUSTOMER_CREATED;
|
||||
private Filter filter =
|
||||
new PathConditionFilter(new PathConditionRule(eventType.getThriftPath(), new IsNullCondition().not()));
|
||||
@ -36,23 +28,17 @@ public class CustomerCreatedHandler extends AbstractCustomerEventHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void saveEvent(CustomerChange cc, EventInfo eventInfo) throws DaoException {
|
||||
public CustomerMessage map(CustomerChange cc, EventInfo eventInfo) throws DaoException {
|
||||
dev.vality.damsel.payment_processing.CustomerCreated customerCreatedOrigin = cc.getCustomerCreated();
|
||||
CustomerMessage customerMessage = new CustomerMessage();
|
||||
customerMessage.setEventId(eventInfo.getEventId());
|
||||
customerMessage.setEventTime(eventInfo.getEventCreatedAt());
|
||||
customerMessage.setSequenceId(eventInfo.getSequenceId());
|
||||
customerMessage.setChangeId(eventInfo.getChangeId());
|
||||
customerMessage.setType(CustomerMessageEnum.CUSTOMER);
|
||||
customerMessage.setPartyId(customerCreatedOrigin.getOwnerId());
|
||||
customerMessage.setEventType(eventType);
|
||||
customerMessage.setCustomerId(customerCreatedOrigin.getCustomerId());
|
||||
customerMessage.setSourceId(customerCreatedOrigin.getCustomerId());
|
||||
customerMessage.setShopId(customerCreatedOrigin.getShopId());
|
||||
Long messageId = customerDao.create(customerMessage);
|
||||
if (messageId != null) {
|
||||
customerMessage.setId(messageId);
|
||||
customerQueueDao.createWithPolicy(messageId);
|
||||
customerTaskDao.create(messageId);
|
||||
}
|
||||
return customerMessage;
|
||||
}
|
||||
}
|
@ -1,31 +1,24 @@
|
||||
package dev.vality.hooker.handler.poller.customer;
|
||||
package dev.vality.hooker.handler.customer;
|
||||
|
||||
import dev.vality.geck.filter.Filter;
|
||||
import dev.vality.geck.filter.PathConditionFilter;
|
||||
import dev.vality.geck.filter.condition.IsNullCondition;
|
||||
import dev.vality.geck.filter.rule.PathConditionRule;
|
||||
import dev.vality.hooker.dao.impl.CustomerDaoImpl;
|
||||
import dev.vality.hooker.dao.impl.CustomerQueueDao;
|
||||
import dev.vality.hooker.dao.impl.CustomerTaskDao;
|
||||
import dev.vality.hooker.model.CustomerMessageEnum;
|
||||
import dev.vality.hooker.model.EventType;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Created by inalarsanukaev on 12.10.17.
|
||||
*/
|
||||
@Component
|
||||
public class CustomerDeletedHandler extends NeedReadCustomerEventHandler {
|
||||
public class CustomerDeletedMapper extends NeedReadCustomerEventMapper {
|
||||
|
||||
private EventType eventType = EventType.CUSTOMER_DELETED;
|
||||
|
||||
private Filter filter =
|
||||
new PathConditionFilter(new PathConditionRule(eventType.getThriftPath(), new IsNullCondition().not()));
|
||||
|
||||
public CustomerDeletedHandler(CustomerDaoImpl customerDao,
|
||||
CustomerQueueDao customerQueueDao,
|
||||
CustomerTaskDao customerTaskDao) {
|
||||
super(customerDao, customerQueueDao, customerTaskDao);
|
||||
public CustomerDeletedMapper(CustomerDaoImpl customerDao) {
|
||||
super(customerDao);
|
||||
}
|
||||
|
||||
@Override
|
@ -1,31 +1,24 @@
|
||||
package dev.vality.hooker.handler.poller.customer;
|
||||
package dev.vality.hooker.handler.customer;
|
||||
|
||||
import dev.vality.geck.filter.Filter;
|
||||
import dev.vality.geck.filter.PathConditionFilter;
|
||||
import dev.vality.geck.filter.condition.IsNullCondition;
|
||||
import dev.vality.geck.filter.rule.PathConditionRule;
|
||||
import dev.vality.hooker.dao.impl.CustomerDaoImpl;
|
||||
import dev.vality.hooker.dao.impl.CustomerQueueDao;
|
||||
import dev.vality.hooker.dao.impl.CustomerTaskDao;
|
||||
import dev.vality.hooker.model.CustomerMessageEnum;
|
||||
import dev.vality.hooker.model.EventType;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Created by inalarsanukaev on 12.10.17.
|
||||
*/
|
||||
@Component
|
||||
public class CustomerReadyHandler extends NeedReadCustomerEventHandler {
|
||||
public class CustomerReadyMapper extends NeedReadCustomerEventMapper {
|
||||
|
||||
private EventType eventType = EventType.CUSTOMER_READY;
|
||||
|
||||
private Filter filter =
|
||||
new PathConditionFilter(new PathConditionRule(eventType.getThriftPath(), new IsNullCondition().not()));
|
||||
|
||||
public CustomerReadyHandler(CustomerDaoImpl customerDao,
|
||||
CustomerQueueDao customerQueueDao,
|
||||
CustomerTaskDao customerTaskDao) {
|
||||
super(customerDao, customerQueueDao, customerTaskDao);
|
||||
public CustomerReadyMapper(CustomerDaoImpl customerDao) {
|
||||
super(customerDao);
|
||||
}
|
||||
|
||||
@Override
|
@ -1,49 +1,33 @@
|
||||
package dev.vality.hooker.handler.poller.customer;
|
||||
package dev.vality.hooker.handler.customer;
|
||||
|
||||
import dev.vality.damsel.payment_processing.CustomerChange;
|
||||
import dev.vality.hooker.dao.impl.CustomerDaoImpl;
|
||||
import dev.vality.hooker.dao.impl.CustomerQueueDao;
|
||||
import dev.vality.hooker.dao.impl.CustomerTaskDao;
|
||||
import dev.vality.hooker.exception.DaoException;
|
||||
import dev.vality.hooker.handler.Mapper;
|
||||
import dev.vality.hooker.model.CustomerMessage;
|
||||
import dev.vality.hooker.model.CustomerMessageEnum;
|
||||
import dev.vality.hooker.model.EventInfo;
|
||||
import dev.vality.hooker.model.EventType;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* Created by inalarsanukaev on 12.10.17.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public abstract class NeedReadCustomerEventHandler extends AbstractCustomerEventHandler {
|
||||
public abstract class NeedReadCustomerEventMapper implements Mapper<CustomerChange, CustomerMessage> {
|
||||
|
||||
protected final CustomerDaoImpl customerDao;
|
||||
|
||||
private final CustomerQueueDao customerQueueDao;
|
||||
|
||||
private final CustomerTaskDao customerTaskDao;
|
||||
|
||||
@Override
|
||||
protected void saveEvent(CustomerChange cc, EventInfo eventInfo) throws DaoException {
|
||||
//getAny any saved message for related invoice
|
||||
public CustomerMessage map(CustomerChange cc, EventInfo eventInfo) throws DaoException {
|
||||
CustomerMessage message = getCustomerMessage(eventInfo.getSourceId());
|
||||
if (message == null) {
|
||||
throw new DaoException("CustomerMessage for customer with id " + eventInfo.getSourceId() + " not exist");
|
||||
}
|
||||
message.setEventType(getEventType());
|
||||
message.setType(getMessageType());
|
||||
message.setEventId(eventInfo.getEventId());
|
||||
message.setEventTime(eventInfo.getEventCreatedAt());
|
||||
message.setSequenceId(eventInfo.getSequenceId());
|
||||
message.setChangeId(eventInfo.getChangeId());
|
||||
modifyMessage(cc, message);
|
||||
|
||||
Long messageId = customerDao.create(message);
|
||||
if (messageId != null) {
|
||||
message.setId(messageId);
|
||||
customerQueueDao.createWithPolicy(messageId);
|
||||
customerTaskDao.create(messageId);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
protected CustomerMessage getCustomerMessage(String customerId) {
|
@ -1,4 +1,4 @@
|
||||
package dev.vality.hooker.handler.poller.invoicing;
|
||||
package dev.vality.hooker.handler.invoicing;
|
||||
|
||||
import dev.vality.damsel.payment_processing.InvoiceChange;
|
||||
import dev.vality.damsel.payment_processing.InvoicePayment;
|
||||
@ -7,11 +7,7 @@ import dev.vality.geck.filter.PathConditionFilter;
|
||||
import dev.vality.geck.filter.condition.IsNullCondition;
|
||||
import dev.vality.geck.filter.rule.PathConditionRule;
|
||||
import dev.vality.hooker.dao.InvoicingMessageDao;
|
||||
import dev.vality.hooker.model.EventType;
|
||||
import dev.vality.hooker.model.InvoicingMessage;
|
||||
import dev.vality.hooker.model.InvoicingMessageEnum;
|
||||
import dev.vality.hooker.model.InvoicingMessageKey;
|
||||
import dev.vality.hooker.model.PaymentStatusEnum;
|
||||
import dev.vality.hooker.model.*;
|
||||
import dev.vality.hooker.service.HellgateInvoicingService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
@ -1,4 +1,4 @@
|
||||
package dev.vality.hooker.handler.poller.invoicing;
|
||||
package dev.vality.hooker.handler.invoicing;
|
||||
|
||||
import dev.vality.damsel.domain.Invoice;
|
||||
import dev.vality.damsel.payment_processing.InvoiceChange;
|
||||
@ -7,19 +7,13 @@ import dev.vality.geck.filter.PathConditionFilter;
|
||||
import dev.vality.geck.filter.condition.IsNullCondition;
|
||||
import dev.vality.geck.filter.rule.PathConditionRule;
|
||||
import dev.vality.hooker.exception.DaoException;
|
||||
import dev.vality.hooker.model.EventInfo;
|
||||
import dev.vality.hooker.model.EventType;
|
||||
import dev.vality.hooker.model.InvoiceStatusEnum;
|
||||
import dev.vality.hooker.model.InvoicingMessage;
|
||||
import dev.vality.hooker.model.InvoicingMessageEnum;
|
||||
import dev.vality.hooker.model.InvoicingMessageKey;
|
||||
import dev.vality.hooker.handler.Mapper;
|
||||
import dev.vality.hooker.model.*;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
public class InvoiceCreatedMapper extends AbstractInvoiceEventMapper {
|
||||
public class InvoiceCreatedMapper implements Mapper<InvoiceChange, InvoicingMessage> {
|
||||
|
||||
private EventType eventType = EventType.INVOICE_CREATED;
|
||||
|
||||
@ -28,8 +22,7 @@ public class InvoiceCreatedMapper extends AbstractInvoiceEventMapper {
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public InvoicingMessage buildEvent(InvoiceChange ic, EventInfo eventInfo,
|
||||
Map<InvoicingMessageKey, InvoicingMessage> storage) throws DaoException {
|
||||
public InvoicingMessage map(InvoiceChange ic, EventInfo eventInfo) throws DaoException {
|
||||
Invoice invoiceOrigin = ic.getInvoiceCreated().getInvoice();
|
||||
InvoicingMessage message = new InvoicingMessage();
|
||||
message.setEventTime(eventInfo.getEventCreatedAt());
|
||||
@ -38,7 +31,7 @@ public class InvoiceCreatedMapper extends AbstractInvoiceEventMapper {
|
||||
message.setType(InvoicingMessageEnum.INVOICE);
|
||||
message.setPartyId(invoiceOrigin.getOwnerId());
|
||||
message.setEventType(eventType);
|
||||
message.setInvoiceId(invoiceOrigin.getId());
|
||||
message.setSourceId(invoiceOrigin.getId());
|
||||
message.setShopId(invoiceOrigin.getShopId());
|
||||
message.setInvoiceStatus(InvoiceStatusEnum.lookup(invoiceOrigin.getStatus().getSetField().getFieldName()));
|
||||
return message;
|
@ -1,4 +1,4 @@
|
||||
package dev.vality.hooker.handler.poller.invoicing;
|
||||
package dev.vality.hooker.handler.invoicing;
|
||||
|
||||
import dev.vality.damsel.domain.InvoicePaymentRefund;
|
||||
import dev.vality.damsel.payment_processing.InvoiceChange;
|
||||
@ -8,11 +8,7 @@ import dev.vality.geck.filter.PathConditionFilter;
|
||||
import dev.vality.geck.filter.condition.IsNullCondition;
|
||||
import dev.vality.geck.filter.rule.PathConditionRule;
|
||||
import dev.vality.hooker.dao.InvoicingMessageDao;
|
||||
import dev.vality.hooker.model.EventType;
|
||||
import dev.vality.hooker.model.InvoicingMessage;
|
||||
import dev.vality.hooker.model.InvoicingMessageEnum;
|
||||
import dev.vality.hooker.model.InvoicingMessageKey;
|
||||
import dev.vality.hooker.model.RefundStatusEnum;
|
||||
import dev.vality.hooker.model.*;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
@ -1,4 +1,4 @@
|
||||
package dev.vality.hooker.handler.poller.invoicing;
|
||||
package dev.vality.hooker.handler.invoicing;
|
||||
|
||||
import dev.vality.damsel.payment_processing.InvoiceChange;
|
||||
import dev.vality.geck.filter.Filter;
|
||||
@ -6,11 +6,7 @@ import dev.vality.geck.filter.PathConditionFilter;
|
||||
import dev.vality.geck.filter.condition.IsNullCondition;
|
||||
import dev.vality.geck.filter.rule.PathConditionRule;
|
||||
import dev.vality.hooker.dao.InvoicingMessageDao;
|
||||
import dev.vality.hooker.model.EventType;
|
||||
import dev.vality.hooker.model.InvoicingMessage;
|
||||
import dev.vality.hooker.model.InvoicingMessageEnum;
|
||||
import dev.vality.hooker.model.InvoicingMessageKey;
|
||||
import dev.vality.hooker.model.RefundStatusEnum;
|
||||
import dev.vality.hooker.model.*;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
@ -1,4 +1,4 @@
|
||||
package dev.vality.hooker.handler.poller.invoicing;
|
||||
package dev.vality.hooker.handler.invoicing;
|
||||
|
||||
import dev.vality.damsel.domain.InvoicePayment;
|
||||
import dev.vality.damsel.payment_processing.InvoiceChange;
|
||||
@ -7,11 +7,7 @@ import dev.vality.geck.filter.PathConditionFilter;
|
||||
import dev.vality.geck.filter.condition.IsNullCondition;
|
||||
import dev.vality.geck.filter.rule.PathConditionRule;
|
||||
import dev.vality.hooker.dao.InvoicingMessageDao;
|
||||
import dev.vality.hooker.model.EventType;
|
||||
import dev.vality.hooker.model.InvoicingMessage;
|
||||
import dev.vality.hooker.model.InvoicingMessageEnum;
|
||||
import dev.vality.hooker.model.InvoicingMessageKey;
|
||||
import dev.vality.hooker.model.PaymentStatusEnum;
|
||||
import dev.vality.hooker.model.*;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
@ -1,4 +1,4 @@
|
||||
package dev.vality.hooker.handler.poller.invoicing;
|
||||
package dev.vality.hooker.handler.invoicing;
|
||||
|
||||
import dev.vality.damsel.payment_processing.InvoiceChange;
|
||||
import dev.vality.geck.filter.Filter;
|
||||
@ -6,11 +6,7 @@ import dev.vality.geck.filter.PathConditionFilter;
|
||||
import dev.vality.geck.filter.condition.IsNullCondition;
|
||||
import dev.vality.geck.filter.rule.PathConditionRule;
|
||||
import dev.vality.hooker.dao.InvoicingMessageDao;
|
||||
import dev.vality.hooker.model.EventType;
|
||||
import dev.vality.hooker.model.InvoicingMessage;
|
||||
import dev.vality.hooker.model.InvoicingMessageEnum;
|
||||
import dev.vality.hooker.model.InvoicingMessageKey;
|
||||
import dev.vality.hooker.model.PaymentStatusEnum;
|
||||
import dev.vality.hooker.model.*;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
@ -1,4 +1,4 @@
|
||||
package dev.vality.hooker.handler.poller.invoicing;
|
||||
package dev.vality.hooker.handler.invoicing;
|
||||
|
||||
import dev.vality.damsel.payment_processing.InvoiceChange;
|
||||
import dev.vality.geck.filter.Filter;
|
||||
@ -6,11 +6,7 @@ import dev.vality.geck.filter.PathConditionFilter;
|
||||
import dev.vality.geck.filter.condition.IsNullCondition;
|
||||
import dev.vality.geck.filter.rule.PathConditionRule;
|
||||
import dev.vality.hooker.dao.InvoicingMessageDao;
|
||||
import dev.vality.hooker.model.EventType;
|
||||
import dev.vality.hooker.model.InvoiceStatusEnum;
|
||||
import dev.vality.hooker.model.InvoicingMessage;
|
||||
import dev.vality.hooker.model.InvoicingMessageEnum;
|
||||
import dev.vality.hooker.model.InvoicingMessageKey;
|
||||
import dev.vality.hooker.model.*;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
@ -1,35 +1,26 @@
|
||||
package dev.vality.hooker.handler.poller.invoicing;
|
||||
package dev.vality.hooker.handler.invoicing;
|
||||
|
||||
import dev.vality.damsel.payment_processing.InvoiceChange;
|
||||
import dev.vality.hooker.dao.InvoicingMessageDao;
|
||||
import dev.vality.hooker.exception.DaoException;
|
||||
import dev.vality.hooker.exception.NotFoundException;
|
||||
import dev.vality.hooker.model.EventInfo;
|
||||
import dev.vality.hooker.model.EventType;
|
||||
import dev.vality.hooker.model.InvoicingMessage;
|
||||
import dev.vality.hooker.model.InvoicingMessageEnum;
|
||||
import dev.vality.hooker.model.InvoicingMessageKey;
|
||||
import dev.vality.hooker.handler.Mapper;
|
||||
import dev.vality.hooker.model.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public abstract class NeedReadInvoiceEventMapper extends AbstractInvoiceEventMapper {
|
||||
public abstract class NeedReadInvoiceEventMapper implements Mapper<InvoiceChange, InvoicingMessage> {
|
||||
|
||||
private final InvoicingMessageDao messageDao;
|
||||
|
||||
@Override
|
||||
protected InvoicingMessage buildEvent(InvoiceChange ic, EventInfo eventInfo,
|
||||
Map<InvoicingMessageKey, InvoicingMessage> storage) throws DaoException {
|
||||
public InvoicingMessage map(InvoiceChange ic, EventInfo eventInfo) throws DaoException {
|
||||
InvoicingMessage message;
|
||||
InvoicingMessageKey messageKey = getMessageKey(eventInfo.getSourceId(), ic);
|
||||
try {
|
||||
message = storage.get(messageKey);
|
||||
if (message == null) {
|
||||
message = messageDao.getInvoicingMessage(messageKey);
|
||||
}
|
||||
message = messageDao.getInvoicingMessage(messageKey);
|
||||
message = message.copy();
|
||||
} catch (NotFoundException e) {
|
||||
log.warn(e.getMessage());
|
@ -1,20 +0,0 @@
|
||||
package dev.vality.hooker.handler.poller.customer;
|
||||
|
||||
import dev.vality.damsel.payment_processing.CustomerChange;
|
||||
import dev.vality.hooker.exception.DaoException;
|
||||
import dev.vality.hooker.handler.Handler;
|
||||
import dev.vality.hooker.model.CustomerMessage;
|
||||
import dev.vality.hooker.model.EventInfo;
|
||||
|
||||
/**
|
||||
* Created by inalarsanukaev on 07.04.17.
|
||||
*/
|
||||
public abstract class AbstractCustomerEventHandler implements Handler<CustomerChange, CustomerMessage> {
|
||||
|
||||
@Override
|
||||
public void handle(CustomerChange c, EventInfo eventInfo) throws DaoException {
|
||||
saveEvent(c, eventInfo);
|
||||
}
|
||||
|
||||
protected abstract void saveEvent(CustomerChange cc, EventInfo eventInfo) throws DaoException;
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
package dev.vality.hooker.handler.poller.invoicing;
|
||||
|
||||
import dev.vality.damsel.payment_processing.InvoiceChange;
|
||||
import dev.vality.hooker.exception.DaoException;
|
||||
import dev.vality.hooker.handler.Mapper;
|
||||
import dev.vality.hooker.model.EventInfo;
|
||||
import dev.vality.hooker.model.InvoicingMessage;
|
||||
import dev.vality.hooker.model.InvoicingMessageKey;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class AbstractInvoiceEventMapper implements Mapper<InvoiceChange, InvoicingMessage> {
|
||||
|
||||
@Override
|
||||
public InvoicingMessage handle(InvoiceChange ic, EventInfo eventInfo,
|
||||
Map<InvoicingMessageKey, InvoicingMessage> storage) throws DaoException {
|
||||
return buildEvent(ic, eventInfo, storage);
|
||||
}
|
||||
|
||||
protected abstract InvoicingMessage buildEvent(InvoiceChange ic, EventInfo eventInfo,
|
||||
Map<InvoicingMessageKey, InvoicingMessage> storage)
|
||||
throws DaoException;
|
||||
}
|
@ -2,11 +2,10 @@ package dev.vality.hooker.listener;
|
||||
|
||||
import dev.vality.damsel.payment_processing.CustomerChange;
|
||||
import dev.vality.damsel.payment_processing.EventPayload;
|
||||
import dev.vality.geck.serializer.kit.json.JsonHandler;
|
||||
import dev.vality.geck.serializer.kit.tbase.TBaseProcessor;
|
||||
import dev.vality.hooker.handler.Handler;
|
||||
import dev.vality.hooker.handler.poller.customer.AbstractCustomerEventHandler;
|
||||
import dev.vality.hooker.handler.Mapper;
|
||||
import dev.vality.hooker.model.CustomerMessage;
|
||||
import dev.vality.hooker.model.EventInfo;
|
||||
import dev.vality.hooker.service.MessageService;
|
||||
import dev.vality.machinegun.eventsink.MachineEvent;
|
||||
import dev.vality.sink.common.parser.impl.MachineEventParser;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@ -23,48 +22,39 @@ import java.util.List;
|
||||
public class CustomerMachineEventHandler implements MachineEventHandler {
|
||||
|
||||
private final MachineEventParser<EventPayload> parser;
|
||||
private final List<AbstractCustomerEventHandler> pollingEventHandlers;
|
||||
private final List<Mapper<CustomerChange, CustomerMessage>> customerEventMappers;
|
||||
private final MessageService<CustomerMessage> customerMessageService;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void handle(List<MachineEvent> machineEvents, Acknowledgment ack) {
|
||||
for (MachineEvent machineEvent : machineEvents) {
|
||||
EventPayload payload = parser.parse(machineEvent);
|
||||
if (!payload.isSetCustomerChanges()) {
|
||||
return;
|
||||
machineEvents.forEach(me -> {
|
||||
EventPayload payload = parser.parse(me);
|
||||
if (payload.isSetCustomerChanges()) {
|
||||
handleChanges(me, payload);
|
||||
}
|
||||
|
||||
List<CustomerChange> changes = payload.getCustomerChanges();
|
||||
for (int i = 0; i < changes.size(); ++i) {
|
||||
preparePollingHandlers(changes.get(i), machineEvent, i);
|
||||
}
|
||||
}
|
||||
});
|
||||
ack.acknowledge();
|
||||
}
|
||||
|
||||
private void preparePollingHandlers(CustomerChange cc, MachineEvent machineEvent, int i) {
|
||||
pollingEventHandlers.stream()
|
||||
.filter(handler -> handler.accept(cc))
|
||||
.findFirst()
|
||||
.ifPresent(handler -> processEvent(handler, cc, machineEvent, i));
|
||||
}
|
||||
|
||||
private void processEvent(Handler pollingEventHandler, Object cc, MachineEvent machineEvent, int i) {
|
||||
long id = machineEvent.getEventId();
|
||||
try {
|
||||
log.info("We got an event {}", new TBaseProcessor()
|
||||
.process(machineEvent, JsonHandler.newPrettyJsonInstance()));
|
||||
EventInfo eventInfo = new EventInfo(
|
||||
machineEvent.getEventId(),
|
||||
machineEvent.getCreatedAt(),
|
||||
machineEvent.getSourceId(),
|
||||
machineEvent.getEventId(),
|
||||
i
|
||||
);
|
||||
pollingEventHandler.handle(cc, eventInfo);
|
||||
} catch (Exception e) {
|
||||
log.error("Error when poller handling with id {}", id, e);
|
||||
private void handleChanges(MachineEvent me, EventPayload payload) {
|
||||
for (int i = 0; i < payload.getCustomerChanges().size(); ++i) {
|
||||
CustomerChange customerChange = payload.getCustomerChanges().get(i);
|
||||
handleChange(me, customerChange, i);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleChange(MachineEvent me, CustomerChange customerChange, int i) {
|
||||
customerEventMappers.stream()
|
||||
.filter(handler -> handler.accept(customerChange))
|
||||
.findFirst()
|
||||
.ifPresent(handler -> {
|
||||
log.info("Start to handle event {}", customerChange);
|
||||
var eventInfo = new EventInfo(me.getCreatedAt(), me.getSourceId(), me.getEventId(), i);
|
||||
CustomerMessage message = handler.map(customerChange, eventInfo);
|
||||
if (message != null) {
|
||||
customerMessageService.process(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -2,12 +2,11 @@ package dev.vality.hooker.listener;
|
||||
|
||||
import dev.vality.damsel.payment_processing.EventPayload;
|
||||
import dev.vality.damsel.payment_processing.InvoiceChange;
|
||||
import dev.vality.hooker.handler.Mapper;
|
||||
import dev.vality.hooker.model.EventInfo;
|
||||
import dev.vality.hooker.model.InvoicingMessage;
|
||||
import dev.vality.hooker.model.InvoicingMessageKey;
|
||||
import dev.vality.hooker.service.BatchService;
|
||||
import dev.vality.hooker.service.HandlerManager;
|
||||
import dev.vality.hooker.utils.KeyUtils;
|
||||
import dev.vality.hooker.service.MessageService;
|
||||
import dev.vality.machinegun.eventsink.MachineEvent;
|
||||
import dev.vality.sink.common.parser.impl.MachineEventParser;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@ -16,47 +15,47 @@ import org.springframework.kafka.support.Acknowledgment;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class InvoicingMachineEventHandler implements MachineEventHandler {
|
||||
|
||||
private final HandlerManager handlerManager;
|
||||
private final List<Mapper<InvoiceChange, InvoicingMessage>> handlers;
|
||||
private final MachineEventParser<EventPayload> parser;
|
||||
private final BatchService batchService;
|
||||
private final MessageService<InvoicingMessage> invoicingMessageService;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void handle(List<MachineEvent> machineEvents, Acknowledgment ack) {
|
||||
List<InvoicingMessage> messages = new ArrayList<>();
|
||||
Map<InvoicingMessageKey, InvoicingMessage> localCache = new HashMap<>();
|
||||
machineEvents.forEach(me -> {
|
||||
EventPayload payload = parser.parse(me);
|
||||
if (payload.isSetInvoiceChanges()) {
|
||||
for (int i = 0; i < payload.getInvoiceChanges().size(); ++i) {
|
||||
InvoiceChange invoiceChange = payload.getInvoiceChanges().get(i);
|
||||
int j = i;
|
||||
handlerManager.getHandler(invoiceChange).ifPresent(handler -> {
|
||||
log.info("Start to handle event {}", invoiceChange);
|
||||
InvoicingMessage message = handler.handle(invoiceChange,
|
||||
new EventInfo(null, me.getCreatedAt(), me.getSourceId(), me.getEventId(), j),
|
||||
localCache);
|
||||
if (message != null) {
|
||||
localCache.put(KeyUtils.key(message), message);
|
||||
messages.add(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
handleChanges(me, payload);
|
||||
}
|
||||
});
|
||||
if (!localCache.isEmpty()) {
|
||||
batchService.process(messages);
|
||||
}
|
||||
ack.acknowledge();
|
||||
}
|
||||
|
||||
private void handleChanges(MachineEvent me, EventPayload payload) {
|
||||
for (int i = 0; i < payload.getInvoiceChanges().size(); ++i) {
|
||||
InvoiceChange invoiceChange = payload.getInvoiceChanges().get(i);
|
||||
handleChange(me, invoiceChange, i);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleChange(MachineEvent me, InvoiceChange invoiceChange, int i) {
|
||||
handlers.stream()
|
||||
.filter(handler -> handler.accept(invoiceChange))
|
||||
.findFirst()
|
||||
.ifPresent(handler -> {
|
||||
log.info("Start to handle event {}", invoiceChange);
|
||||
var eventInfo = new EventInfo(me.getCreatedAt(), me.getSourceId(), me.getEventId(), i);
|
||||
InvoicingMessage message = handler.map(invoiceChange, eventInfo);
|
||||
if (message != null) {
|
||||
invoicingMessageService.process(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,285 +0,0 @@
|
||||
package dev.vality.hooker.logging;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.Connection;
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
import okhttp3.internal.http.HttpHeaders;
|
||||
import okio.Buffer;
|
||||
import okio.BufferedSource;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* An OkHttp interceptor which logs request and response information. Can be applied as an
|
||||
* {@linkplain OkHttpClient#interceptors() application interceptor} or as a {@linkplain
|
||||
* OkHttpClient#networkInterceptors() network interceptor}. <p> The format of the logs created by
|
||||
* this class should not be considered stable and may change slightly between releases. If you need
|
||||
* a stable logging format, use your own interceptor.
|
||||
*/
|
||||
@Slf4j
|
||||
public final class HttpLoggingInterceptor implements Interceptor {
|
||||
private static final Charset UTF8 = Charset.forName("UTF-8");
|
||||
private final Logger logger;
|
||||
private volatile Level level = Level.BASIC;
|
||||
|
||||
public HttpLoggingInterceptor() {
|
||||
this(Logger.DEFAULT);
|
||||
}
|
||||
|
||||
public HttpLoggingInterceptor(Logger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the body in question probably contains human readable text. Uses a small sample
|
||||
* of code points to detect unicode control characters commonly used in binary file signatures.
|
||||
*/
|
||||
static boolean isPlaintext(Buffer buffer) {
|
||||
try {
|
||||
Buffer prefix = new Buffer();
|
||||
long byteCount = buffer.size() < 64 ? buffer.size() : 64;
|
||||
buffer.copyTo(prefix, 0, byteCount);
|
||||
for (int i = 0; i < 16; i++) {
|
||||
if (prefix.exhausted()) {
|
||||
break;
|
||||
}
|
||||
int codePoint = prefix.readUtf8CodePoint();
|
||||
if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} catch (EOFException e) {
|
||||
return false; // Truncated UTF-8 sequence.
|
||||
}
|
||||
}
|
||||
|
||||
public Level getLevel() {
|
||||
return level;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the level at which this interceptor logs.
|
||||
*/
|
||||
public HttpLoggingInterceptor setLevel(Level level) {
|
||||
if (level == null) {
|
||||
throw new NullPointerException("level == null. Use Level.NONE instead.");
|
||||
}
|
||||
this.level = level;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response intercept(Chain chain) throws IOException {
|
||||
Level level = this.level;
|
||||
|
||||
Request request = chain.request();
|
||||
if (level == Level.NONE) {
|
||||
return chain.proceed(request);
|
||||
}
|
||||
|
||||
boolean logBody = level == Level.BODY;
|
||||
boolean logHeaders = logBody || level == Level.HEADERS;
|
||||
|
||||
RequestBody requestBody = request.body();
|
||||
boolean hasRequestBody = requestBody != null;
|
||||
|
||||
Connection connection = chain.connection();
|
||||
StringBuilder message = new StringBuilder("--> \n")
|
||||
.append(request.method())
|
||||
.append(' ').append(request.url())
|
||||
.append(connection != null ? " " + connection.protocol() : "").append('\n');
|
||||
if (!logHeaders && hasRequestBody) {
|
||||
message.append(" (").append(requestBody.contentLength()).append("-byte body)").append('\n');
|
||||
}
|
||||
//logger.log(requestStartMessage);
|
||||
|
||||
if (logHeaders) {
|
||||
if (hasRequestBody) {
|
||||
// Request body headers are only present when installed as a network interceptor. Force
|
||||
// them to be included (when available) so there values are known.
|
||||
if (requestBody.contentType() != null) {
|
||||
message.append("Content-Type: ").append(requestBody.contentType()).append('\n');
|
||||
}
|
||||
if (requestBody.contentLength() != -1) {
|
||||
message.append("Content-Length: ").append(requestBody.contentLength()).append('\n');
|
||||
}
|
||||
}
|
||||
|
||||
Headers headers = request.headers();
|
||||
for (int i = 0, count = headers.size(); i < count; i++) {
|
||||
String name = headers.name(i);
|
||||
// Skip headers from the request body as they are explicitly logged above.
|
||||
if (!"Authorization".equalsIgnoreCase(name)) {
|
||||
message.append(name).append(": ").append(headers.value(i)).append('\n');
|
||||
}
|
||||
}
|
||||
|
||||
if (!logBody || !hasRequestBody) {
|
||||
message.append("\n--> END ").append(request.method());
|
||||
} else if (bodyEncoded(request.headers())) {
|
||||
message.append("\n--> END ").append(request.method()).append(" (encoded body omitted)");
|
||||
} else {
|
||||
message.append('\n');
|
||||
Buffer buffer = new Buffer();
|
||||
requestBody.writeTo(buffer);
|
||||
|
||||
Charset charset = UTF8;
|
||||
MediaType contentType = requestBody.contentType();
|
||||
if (contentType != null) {
|
||||
charset = contentType.charset(UTF8);
|
||||
}
|
||||
|
||||
if (isPlaintext(buffer)) {
|
||||
message.append(buffer.readString(charset));
|
||||
message.append("\n--> END ").append(request.method())
|
||||
.append(" (").append(requestBody.contentLength()).append("-byte body)");
|
||||
} else {
|
||||
message.append("\n--> END ").append(request.method()).append(" (binary ")
|
||||
.append(requestBody.contentLength()).append("-byte body omitted)");
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.log(message.toString());
|
||||
long startNs = System.nanoTime();
|
||||
Response response;
|
||||
try {
|
||||
response = chain.proceed(request);
|
||||
} catch (Exception e) {
|
||||
logger.log("<-- HTTP FAILED: " + e);
|
||||
throw e;
|
||||
}
|
||||
long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
|
||||
|
||||
ResponseBody responseBody = response.body();
|
||||
long contentLength = responseBody.contentLength();
|
||||
message = new StringBuilder();
|
||||
String bodySize = contentLength != -1 ? contentLength + "-byte" : "unknown-length";
|
||||
message.append("<-- \n")
|
||||
.append(response.code())
|
||||
.append(response.message().isEmpty() ? "" : " " + response.message())
|
||||
.append(' ').append(response.request().url())
|
||||
.append(" (").append(tookMs).append("ms").append((!logHeaders ? ", " + bodySize + " body" : "") + ')')
|
||||
.append('\n');
|
||||
|
||||
if (logHeaders) {
|
||||
Headers headers = response.headers();
|
||||
for (int i = 0, count = headers.size(); i < count; i++) {
|
||||
message.append(headers.name(i)).append(": ").append(headers.value(i)).append('\n');
|
||||
}
|
||||
|
||||
if (!logBody || !HttpHeaders.hasBody(response)) {
|
||||
message.append("\n<-- END HTTP");
|
||||
} else if (bodyEncoded(response.headers())) {
|
||||
message.append("\n<-- END HTTP (encoded body omitted)");
|
||||
} else {
|
||||
message.append('\n');
|
||||
BufferedSource source = responseBody.source();
|
||||
source.request(Long.MAX_VALUE); // Buffer the entire body.
|
||||
Buffer buffer = source.buffer();
|
||||
|
||||
Charset charset = UTF8;
|
||||
MediaType contentType = responseBody.contentType();
|
||||
if (contentType != null) {
|
||||
charset = contentType.charset(UTF8);
|
||||
}
|
||||
|
||||
if (!isPlaintext(buffer)) {
|
||||
message.append("\n<-- END HTTP (binary ").append(buffer.size()).append("-byte body omitted)");
|
||||
return response;
|
||||
}
|
||||
|
||||
if (contentLength != 0) {
|
||||
message.append(buffer.clone().readString(charset));
|
||||
}
|
||||
|
||||
logger.log(message.append("\n<-- END HTTP (").append(buffer.size()).append("-byte body)").toString());
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private boolean bodyEncoded(Headers headers) {
|
||||
String contentEncoding = headers.get("Content-Encoding");
|
||||
return contentEncoding != null && !contentEncoding.equalsIgnoreCase("identity");
|
||||
}
|
||||
|
||||
public enum Level {
|
||||
/**
|
||||
* No logs.
|
||||
*/
|
||||
NONE,
|
||||
/**
|
||||
* Logs request and response lines.
|
||||
* <p>
|
||||
* <p>Example:
|
||||
* <pre>{@code
|
||||
* --> POST /greeting http/1.1 (3-byte body)
|
||||
*
|
||||
* <-- 200 OK (22ms, 6-byte body)
|
||||
* }</pre>
|
||||
*/
|
||||
BASIC,
|
||||
/**
|
||||
* Logs request and response lines and their respective headers.
|
||||
* <p>
|
||||
* <p>Example:
|
||||
* <pre>{@code
|
||||
* --> POST /greeting http/1.1
|
||||
* Host: example.com
|
||||
* Content-Type: plain/text
|
||||
* Content-Length: 3
|
||||
* --> END POST
|
||||
*
|
||||
* <-- 200 OK (22ms)
|
||||
* Content-Type: plain/text
|
||||
* Content-Length: 6
|
||||
* <-- END HTTP
|
||||
* }</pre>
|
||||
*/
|
||||
HEADERS,
|
||||
/**
|
||||
* Logs request and response lines and their respective headers and bodies (if present).
|
||||
* <p>
|
||||
* <p>Example:
|
||||
* <pre>{@code
|
||||
* --> POST /greeting http/1.1
|
||||
* Host: example.com
|
||||
* Content-Type: plain/text
|
||||
* Content-Length: 3
|
||||
*
|
||||
* Hi?
|
||||
* --> END POST
|
||||
*
|
||||
* <-- 200 OK (22ms)
|
||||
* Content-Type: plain/text
|
||||
* Content-Length: 6
|
||||
*
|
||||
* Hello!
|
||||
* <-- END HTTP
|
||||
* }</pre>
|
||||
*/
|
||||
BODY
|
||||
}
|
||||
|
||||
public interface Logger {
|
||||
/**
|
||||
* A {@link Logger} defaults output appropriate for the current platform.
|
||||
*/
|
||||
Logger DEFAULT = log::info;
|
||||
|
||||
void log(String message);
|
||||
}
|
||||
}
|
||||
|
@ -5,16 +5,12 @@ import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* Created by inalarsanukaev on 13.10.17.
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@ToString
|
||||
public class CustomerMessage extends Message {
|
||||
private CustomerMessageEnum type;
|
||||
private String customerId;
|
||||
private String bindingId;
|
||||
|
||||
public boolean isBinding() {
|
||||
|
@ -3,9 +3,6 @@ package dev.vality.hooker.model;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* Created by inalarsanukaev on 14.11.17.
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class CustomerQueue extends Queue {
|
||||
|
@ -6,7 +6,6 @@ import lombok.Data;
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class EventInfo {
|
||||
private Long eventId;
|
||||
private String eventCreatedAt;
|
||||
private String sourceId;
|
||||
private Long sequenceId;
|
||||
|
@ -1,38 +1,26 @@
|
||||
package dev.vality.hooker.model;
|
||||
|
||||
import dev.vality.hooker.dao.WebhookAdditionalFilter;
|
||||
import dev.vality.hooker.retry.RetryPolicyType;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.*;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Created by jeckep on 13.04.17.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@ToString
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class Hook {
|
||||
private long id;
|
||||
private String partyId;
|
||||
private String topic;
|
||||
@ToString.Exclude
|
||||
private Set<WebhookAdditionalFilter> filters;
|
||||
private String url;
|
||||
@ToString.Exclude
|
||||
private String pubKey;
|
||||
@ToString.Exclude
|
||||
private String privKey;
|
||||
private boolean enabled;
|
||||
private double availability;
|
||||
private RetryPolicyType retryPolicyType;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Hook{" +
|
||||
"id=" + id +
|
||||
", topic=" + topic +
|
||||
", partyId='" + partyId + '\'' +
|
||||
", url='" + url + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
@ -5,17 +5,12 @@ import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
|
||||
|
||||
/**
|
||||
* Created by inalarsanukaev on 07.04.17.
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
@ToString
|
||||
public class InvoicingMessage extends Message {
|
||||
|
||||
private InvoicingMessageEnum type;
|
||||
private String invoiceId;
|
||||
private InvoiceStatusEnum invoiceStatus;
|
||||
private String paymentId;
|
||||
private PaymentStatusEnum paymentStatus;
|
||||
|
@ -12,7 +12,7 @@ public enum InvoicingMessageEnum {
|
||||
PAYMENT("payment"),
|
||||
REFUND("refund");
|
||||
|
||||
private String value;
|
||||
private final String value;
|
||||
|
||||
public static InvoicingMessageEnum lookup(String v) {
|
||||
return Arrays.stream(values()).filter(value -> v.equals(value.getValue())).findFirst()
|
||||
|
@ -1,10 +1,6 @@
|
||||
package dev.vality.hooker.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import lombok.*;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
|
@ -3,9 +3,6 @@ package dev.vality.hooker.model;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* Created by inalarsanukaev on 14.11.17.
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class InvoicingQueue extends Queue {
|
||||
|
@ -3,17 +3,14 @@ package dev.vality.hooker.model;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* Created by inalarsanukaev on 20.11.17.
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class Message {
|
||||
private Long id;
|
||||
private Long eventId;
|
||||
private Long sequenceId;
|
||||
private Integer changeId;
|
||||
private String eventTime;
|
||||
private String sourceId;
|
||||
private String partyId;
|
||||
private String shopId;
|
||||
private EventType eventType;
|
||||
|
@ -1,14 +0,0 @@
|
||||
package dev.vality.hooker.model;
|
||||
|
||||
import dev.vality.swag_webhook_events.model.DigitalWalletDetails;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
public class PaymentToolDetailsDigitalWallet
|
||||
extends dev.vality.swag_webhook_events.model.PaymentToolDetailsDigitalWallet {
|
||||
private DigitalWalletDetails digitalWalletDetails;
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
package dev.vality.hooker.model;
|
||||
|
||||
import dev.vality.hooker.retry.RetryPolicyRecord;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@ -9,5 +8,4 @@ import lombok.NoArgsConstructor;
|
||||
public class Queue {
|
||||
private long id;
|
||||
private Hook hook;
|
||||
private RetryPolicyRecord retryPolicyRecord;
|
||||
}
|
||||
|
@ -1,15 +0,0 @@
|
||||
package dev.vality.hooker.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
public class Task {
|
||||
long messageId;
|
||||
long queueId;
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package dev.vality.hooker.model;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class WebhookMessageModel<T extends Message> {
|
||||
private T message;
|
||||
private Long hookId;
|
||||
private String url;
|
||||
private String privateKey;
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
package dev.vality.hooker.retry;
|
||||
|
||||
import dev.vality.hooker.dao.SimpleRetryPolicyDao;
|
||||
import dev.vality.hooker.retry.impl.simple.SimpleRetryPolicy;
|
||||
import dev.vality.hooker.retry.impl.simple.SimpleRetryPolicyRecord;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* Created by jeckep on 18.04.17.
|
||||
*/
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class RetryPoliciesService {
|
||||
|
||||
private final SimpleRetryPolicy simpleRetryPolicy;
|
||||
private final SimpleRetryPolicyDao simpleRetryPolicyDao;
|
||||
|
||||
public RetryPolicy getRetryPolicyByType(RetryPolicyType type) {
|
||||
if (RetryPolicyType.SIMPLE.equals(type)) {
|
||||
return simpleRetryPolicy;
|
||||
} else {
|
||||
throw new UnsupportedOperationException("Retry policy for type: " + type.toString() + " not found");
|
||||
}
|
||||
}
|
||||
|
||||
public void update(RetryPolicyRecord record) {
|
||||
if (RetryPolicyType.SIMPLE.equals(record.getType())) {
|
||||
simpleRetryPolicyDao.update((SimpleRetryPolicyRecord) record);
|
||||
} else {
|
||||
throw new UnsupportedOperationException(
|
||||
"Retry policy DAO for type: " + record.getType().toString() + " not found");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package dev.vality.hooker.retry;
|
||||
|
||||
/**
|
||||
* Created by jeckep on 17.04.17.
|
||||
*/
|
||||
public interface RetryPolicy<T> {
|
||||
boolean shouldDisable(T record);
|
||||
|
||||
RetryPolicyType getType();
|
||||
|
||||
void updateFailed(T record);
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package dev.vality.hooker.retry;
|
||||
|
||||
/**
|
||||
* Created by jeckep on 18.04.17.
|
||||
*/
|
||||
public abstract class RetryPolicyRecord {
|
||||
public abstract boolean isFailed();
|
||||
|
||||
public abstract void reset();
|
||||
|
||||
public abstract RetryPolicyType getType();
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
package dev.vality.hooker.retry;
|
||||
|
||||
import dev.vality.hooker.retry.impl.simple.SimpleRetryPolicyRecord;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Created by jeckep on 17.04.17.
|
||||
*/
|
||||
public enum RetryPolicyType {
|
||||
/*
|
||||
* Первая и самая простая политика переотправки.
|
||||
* Если хук не отвечает или отвечает с ошибкой,
|
||||
* пробуем 4 раза с интервалами 30сек, 5мин, 15мин, 1час опять послать
|
||||
* неотправленное сообщение в этот хук. При этом очередь сообщений для хука копится.
|
||||
* После первой удачной отправки, после неудачной, счетчик неудачных попыток сбрасывается.
|
||||
* */
|
||||
|
||||
SIMPLE {
|
||||
@Override
|
||||
public RetryPolicyRecord build(ResultSet rs) throws SQLException {
|
||||
SimpleRetryPolicyRecord record = new SimpleRetryPolicyRecord();
|
||||
record.setQueueId(rs.getLong("id"));
|
||||
record.setMessageType(rs.getString("message_type"));
|
||||
record.setFailCount(rs.getInt("fail_count"));
|
||||
record.setLastFailTime(rs.getLong("last_fail_time"));
|
||||
record.setNextFireTime(rs.getLong("next_fire_time_ms"));
|
||||
return record;
|
||||
}
|
||||
|
||||
public SimpleRetryPolicyRecord cast(RetryPolicyRecord record) {
|
||||
return (SimpleRetryPolicyRecord) record;
|
||||
}
|
||||
};
|
||||
|
||||
public abstract RetryPolicyRecord build(ResultSet rs) throws SQLException;
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
package dev.vality.hooker.retry.impl.simple;
|
||||
|
||||
import dev.vality.hooker.retry.RetryPolicy;
|
||||
import dev.vality.hooker.retry.RetryPolicyType;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Created by jeckep on 17.04.17.
|
||||
*/
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class SimpleRetryPolicy implements RetryPolicy<SimpleRetryPolicyRecord> {
|
||||
|
||||
private long[] delays = {30, 300, 900, 3600,
|
||||
3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600,
|
||||
3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600,
|
||||
3600, 3600, 3600, 3600
|
||||
}; //in seconds
|
||||
|
||||
@Override
|
||||
public RetryPolicyType getType() {
|
||||
return RetryPolicyType.SIMPLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFailed(SimpleRetryPolicyRecord record) {
|
||||
record.setFailCount(record.getFailCount() + 1);
|
||||
record.setLastFailTime(System.currentTimeMillis());
|
||||
if (record.getFailCount() <= delays.length) {
|
||||
record.setNextFireTime(record.getLastFailTime() + (delays[record.getFailCount() - 1] * 1000));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldDisable(SimpleRetryPolicyRecord rp) {
|
||||
return rp.getFailCount() > delays.length;
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
package dev.vality.hooker.retry.impl.simple;
|
||||
|
||||
import dev.vality.hooker.retry.RetryPolicyRecord;
|
||||
import dev.vality.hooker.retry.RetryPolicyType;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
@ToString
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
public class SimpleRetryPolicyRecord extends RetryPolicyRecord {
|
||||
public static RetryPolicyType type = RetryPolicyType.SIMPLE;
|
||||
|
||||
private Long queueId;
|
||||
private String messageType;
|
||||
private Integer failCount;
|
||||
private Long lastFailTime;
|
||||
private Long nextFireTime;
|
||||
|
||||
@Override
|
||||
public boolean isFailed() {
|
||||
return failCount > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
failCount = 0;
|
||||
lastFailTime = null;
|
||||
nextFireTime = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RetryPolicyType getType() {
|
||||
return type;
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
package dev.vality.hooker.scheduler;
|
||||
|
||||
import dev.vality.hooker.model.Message;
|
||||
import dev.vality.hooker.model.Queue;
|
||||
import dev.vality.hooker.service.MessageProcessor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class MessageScheduler<M extends Message, Q extends Queue> {
|
||||
private final int threadPoolSize;
|
||||
private final int delayMillis;
|
||||
private final MessageProcessor<M, Q> messageProcessor;
|
||||
private final ThreadPoolTaskScheduler executorService;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
IntStream.range(0, threadPoolSize).forEach(i ->
|
||||
executorService.scheduleWithFixedDelay(messageProcessor, delayMillis));
|
||||
}
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
package dev.vality.hooker.scheduler;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import dev.vality.hooker.model.Message;
|
||||
import dev.vality.hooker.model.Queue;
|
||||
import dev.vality.hooker.model.QueueStatus;
|
||||
import dev.vality.hooker.model.Task;
|
||||
import dev.vality.hooker.service.EventService;
|
||||
import dev.vality.hooker.service.FaultDetectorService;
|
||||
import dev.vality.hooker.service.PostSender;
|
||||
import dev.vality.hooker.service.crypt.Signer;
|
||||
import dev.vality.hooker.service.err.PostRequestException;
|
||||
import dev.vality.swag_webhook_events.model.Event;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.http.HttpStatus;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class MessageSender<M extends Message, Q extends Queue> {
|
||||
private final int connectionPoolSize;
|
||||
private final int timeout;
|
||||
private final Signer signer;
|
||||
private final EventService<M> eventService;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final FaultDetectorService faultDetector;
|
||||
|
||||
public List<QueueStatus> send(Map<Long, List<Task>> scheduledTasks, Map<Long, Q> queuesMap,
|
||||
Map<Long, M> messagesMap) {
|
||||
PostSender postSender = new PostSender(connectionPoolSize, timeout);
|
||||
List<QueueStatus> queueStatuses = new ArrayList<>();
|
||||
for (Map.Entry<Long, List<Task>> entry : scheduledTasks.entrySet()) {
|
||||
Long queueId = entry.getKey();
|
||||
List<Task> tasks = entry.getValue();
|
||||
Q queue = queuesMap.get(queueId);
|
||||
QueueStatus queueStatus = new QueueStatus();
|
||||
queueStatus.setQueue(queue);
|
||||
M currentMessage = null;
|
||||
try {
|
||||
for (Task task : tasks) {
|
||||
long messageId = task.getMessageId();
|
||||
M message = messagesMap.get(messageId);
|
||||
currentMessage = message;
|
||||
Event event = eventService.getEventByMessage(message);
|
||||
final String messageJson = objectMapper.writeValueAsString(event);
|
||||
final String signature = signer.sign(messageJson, queue.getHook().getPrivKey());
|
||||
faultDetector.startRequest(queue.getHook().getId(), message.getEventId());
|
||||
int statusCode =
|
||||
postSender.doPost(queue.getHook().getUrl(), message.getId(), messageJson, signature);
|
||||
if (statusCode != HttpStatus.SC_OK) {
|
||||
String wrongCodeMessage = String.format(
|
||||
"Wrong status code: %d from merchant, we'll try to resend it. Message with id: %d %s",
|
||||
statusCode, message.getId(), message);
|
||||
log.info(wrongCodeMessage);
|
||||
faultDetector.errorRequest(queue.getHook().getId(), message.getEventId());
|
||||
throw new PostRequestException(wrongCodeMessage);
|
||||
}
|
||||
faultDetector.finishRequest(queue.getHook().getId(), message.getEventId());
|
||||
queueStatus.getMessagesDone().add(message.getId());
|
||||
}
|
||||
queueStatus.setSuccess(true);
|
||||
} catch (Exception e) {
|
||||
if (currentMessage != null) {
|
||||
log.warn("Couldn't send message with id {} {} to hook {}. We'll try to resend it",
|
||||
currentMessage.getId(), currentMessage, queue.getHook(), e);
|
||||
}
|
||||
queueStatus.setSuccess(false);
|
||||
}
|
||||
queueStatuses.add(queueStatus);
|
||||
}
|
||||
return queueStatuses;
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package dev.vality.hooker.service;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AdditionalHeadersGenerator {
|
||||
public static final String SIGNATURE_HEADER = "Content-Signature";
|
||||
|
||||
public Map<String, String> generate(String signature) {
|
||||
return Map.of(SIGNATURE_HEADER, "alg=RS256; digest=" + signature);
|
||||
}
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
package dev.vality.hooker.service;
|
||||
|
||||
import dev.vality.hooker.dao.impl.InvoicingMessageDaoImpl;
|
||||
import dev.vality.hooker.dao.impl.InvoicingQueueDao;
|
||||
import dev.vality.hooker.dao.impl.InvoicingTaskDao;
|
||||
import dev.vality.hooker.dao.impl.MessageIdsGeneratorDaoImpl;
|
||||
import dev.vality.hooker.model.InvoicingMessage;
|
||||
import dev.vality.hooker.model.Message;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class BatchService {
|
||||
|
||||
private final InvoicingMessageDaoImpl invoicingMessageDao;
|
||||
private final InvoicingQueueDao invoicingQueueDao;
|
||||
private final InvoicingTaskDao invoicingTaskDao;
|
||||
private final MessageIdsGeneratorDaoImpl messageIdsGeneratorDao;
|
||||
|
||||
public void process(List<InvoicingMessage> messages) {
|
||||
log.info("Start processing of batch, size={}", messages.size());
|
||||
List<Long> ids = messageIdsGeneratorDao.get(messages.size());
|
||||
List<Long> eventIds = messageIdsGeneratorDao.get(messages.size());
|
||||
for (int i = 0; i < messages.size(); ++i) {
|
||||
messages.get(i).setId(ids.get(i));
|
||||
messages.get(i).setEventId(eventIds.get(i));
|
||||
}
|
||||
invoicingMessageDao.saveBatch(messages);
|
||||
List<Long> messageIds = messages.stream().map(Message::getId).collect(Collectors.toList());
|
||||
int[] queueBatchResult = invoicingQueueDao.saveBatchWithPolicies(messageIds);
|
||||
log.info("Queue batch size={}", queueBatchResult.length);
|
||||
int taskInsertResult = invoicingTaskDao.save(messageIds);
|
||||
log.info("Task insert size={}", taskInsertResult);
|
||||
log.info("End processing of batch");
|
||||
}
|
||||
}
|
@ -12,13 +12,7 @@ import dev.vality.hooker.exception.RemoteHostException;
|
||||
import dev.vality.hooker.model.CustomerMessage;
|
||||
import dev.vality.hooker.utils.HellgateUtils;
|
||||
import dev.vality.hooker.utils.TimeUtils;
|
||||
import dev.vality.swag_webhook_events.model.CustomerBindingFailed;
|
||||
import dev.vality.swag_webhook_events.model.CustomerBindingStarted;
|
||||
import dev.vality.swag_webhook_events.model.CustomerBindingSucceeded;
|
||||
import dev.vality.swag_webhook_events.model.CustomerCreated;
|
||||
import dev.vality.swag_webhook_events.model.CustomerDeleted;
|
||||
import dev.vality.swag_webhook_events.model.CustomerReady;
|
||||
import dev.vality.swag_webhook_events.model.Event;
|
||||
import dev.vality.swag_webhook_events.model.*;
|
||||
import dev.vality.woody.api.flow.WFlow;
|
||||
import dev.vality.woody.api.trace.ContextUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@ -38,17 +32,17 @@ public class CustomerEventService implements EventService<CustomerMessage> {
|
||||
try {
|
||||
Customer customer = woodyFlow.createServiceFork(() -> {
|
||||
addWoodyContext();
|
||||
return customerClient.get(message.getCustomerId(),
|
||||
return customerClient.get(message.getSourceId(),
|
||||
HellgateUtils.getEventRange(message.getSequenceId().intValue()));
|
||||
}
|
||||
).call();
|
||||
|
||||
return resolveEvent(message, customer)
|
||||
.eventID(message.getEventId().intValue())
|
||||
.eventID(message.getId().intValue())
|
||||
.occuredAt(TimeUtils.toOffsetDateTime(message.getEventTime()))
|
||||
.topic(Event.TopicEnum.CUSTOMERSTOPIC);
|
||||
} catch (CustomerNotFound e) {
|
||||
throw new NotFoundException("Customer not found, invoiceId=" + message.getCustomerId());
|
||||
throw new NotFoundException("Customer not found, invoiceId=" + message.getSourceId());
|
||||
} catch (Exception e) {
|
||||
throw new RemoteHostException(e);
|
||||
}
|
||||
@ -87,12 +81,12 @@ public class CustomerEventService implements EventService<CustomerMessage> {
|
||||
}
|
||||
|
||||
private dev.vality.damsel.payment_processing.CustomerBinding extractBinding(CustomerMessage message,
|
||||
Customer customer) {
|
||||
Customer customer) {
|
||||
return customer.getBindings().stream()
|
||||
.filter(b -> b.getId().equals(message.getBindingId()))
|
||||
.findFirst()
|
||||
.orElseThrow(() ->
|
||||
new NotFoundException(String.format("Customer binding not found, customerId=%s, bindingId=%s",
|
||||
message.getCustomerId(), message.getBindingId())));
|
||||
message.getSourceId(), message.getBindingId())));
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +0,0 @@
|
||||
package dev.vality.hooker.service;
|
||||
|
||||
import dev.vality.hooker.dao.impl.CustomerDaoImpl;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class CustomerLastEventService {
|
||||
|
||||
private final CustomerDaoImpl customerDao;
|
||||
|
||||
public Long getLastEventId() {
|
||||
Long custLastEventId = customerDao.getMaxEventId();
|
||||
log.info("Get last event id = {}", custLastEventId);
|
||||
return custLastEventId;
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
package dev.vality.hooker.service;
|
||||
|
||||
public interface FaultDetectorService {
|
||||
|
||||
double getRate(long hookId);
|
||||
|
||||
void startRequest(long hookId, long eventId);
|
||||
|
||||
void finishRequest(long hookId, long eventId);
|
||||
|
||||
void errorRequest(long hookId, long eventId);
|
||||
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
package dev.vality.hooker.service;
|
||||
|
||||
import dev.vality.damsel.fault_detector.Error;
|
||||
import dev.vality.damsel.fault_detector.FaultDetectorSrv;
|
||||
import dev.vality.damsel.fault_detector.Finish;
|
||||
import dev.vality.damsel.fault_detector.Operation;
|
||||
import dev.vality.damsel.fault_detector.OperationState;
|
||||
import dev.vality.damsel.fault_detector.ServiceConfig;
|
||||
import dev.vality.damsel.fault_detector.ServiceStatistics;
|
||||
import dev.vality.damsel.fault_detector.Start;
|
||||
import dev.vality.geck.common.util.TypeUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class FaultDetectorServiceImpl implements FaultDetectorService {
|
||||
|
||||
private final FaultDetectorSrv.Iface faultDetector;
|
||||
|
||||
@Value("${service.fault-detector.slidingWindow}")
|
||||
private Long slidingWindowMillis;
|
||||
|
||||
@Value("${service.fault-detector.operationTimeLimit}")
|
||||
private Long operationTimeLimit;
|
||||
|
||||
private ServiceConfig serviceConfig;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
serviceConfig = new ServiceConfig()
|
||||
.setSlidingWindow(slidingWindowMillis)
|
||||
.setOperationTimeLimit(operationTimeLimit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getRate(long hookId) {
|
||||
try {
|
||||
List<ServiceStatistics> statistics = faultDetector.getStatistics(List.of(buildServiceId(hookId)));
|
||||
return statistics.isEmpty() ? 0 : statistics.get(0).getFailureRate();
|
||||
} catch (Exception e) {
|
||||
log.error("Error in FaultDetectorService when getStatistics", e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startRequest(long hookId, long eventId) {
|
||||
registerOperation(hookId, eventId, OperationState.start(new Start(getNow())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finishRequest(long hookId, long eventId) {
|
||||
registerOperation(hookId, eventId, OperationState.finish(new Finish(getNow())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void errorRequest(long hookId, long eventId) {
|
||||
registerOperation(hookId, eventId, OperationState.error(new Error(getNow())));
|
||||
}
|
||||
|
||||
private void registerOperation(long hookId, long eventId, OperationState operationState) {
|
||||
Operation operation = new Operation()
|
||||
.setOperationId(String.valueOf(eventId))
|
||||
.setState(operationState);
|
||||
try {
|
||||
faultDetector.registerOperation(buildServiceId(hookId), operation, serviceConfig);
|
||||
} catch (Exception e) {
|
||||
log.error("Error in FaultDetectorService when registerOperation", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String getNow() {
|
||||
return TypeUtil.temporalToString(LocalDateTime.now(ZoneOffset.UTC));
|
||||
}
|
||||
|
||||
private String buildServiceId(long id) {
|
||||
return "hooker-" + id;
|
||||
}
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
package dev.vality.hooker.service;
|
||||
|
||||
import dev.vality.damsel.payment_processing.InvoiceChange;
|
||||
import dev.vality.hooker.handler.poller.invoicing.AbstractInvoiceEventMapper;
|
||||
import dev.vality.hooker.handler.Mapper;
|
||||
import dev.vality.hooker.model.InvoicingMessage;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@ -12,9 +13,9 @@ import java.util.Optional;
|
||||
@RequiredArgsConstructor
|
||||
public class HandlerManager {
|
||||
|
||||
private final List<AbstractInvoiceEventMapper> handlers;
|
||||
private final List<Mapper<InvoiceChange, InvoicingMessage>> handlers;
|
||||
|
||||
public Optional<AbstractInvoiceEventMapper> getHandler(InvoiceChange change) {
|
||||
public Optional<Mapper<InvoiceChange, InvoicingMessage>> getHandler(InvoiceChange change) {
|
||||
return handlers.stream().filter(handler -> handler.accept(change)).findFirst();
|
||||
}
|
||||
}
|
@ -11,23 +11,7 @@ import dev.vality.hooker.exception.RemoteHostException;
|
||||
import dev.vality.hooker.model.InvoicingMessage;
|
||||
import dev.vality.hooker.utils.HellgateUtils;
|
||||
import dev.vality.hooker.utils.TimeUtils;
|
||||
import dev.vality.swag_webhook_events.model.Event;
|
||||
import dev.vality.swag_webhook_events.model.Invoice;
|
||||
import dev.vality.swag_webhook_events.model.InvoiceCancelled;
|
||||
import dev.vality.swag_webhook_events.model.InvoiceCreated;
|
||||
import dev.vality.swag_webhook_events.model.InvoiceFulfilled;
|
||||
import dev.vality.swag_webhook_events.model.InvoicePaid;
|
||||
import dev.vality.swag_webhook_events.model.Payment;
|
||||
import dev.vality.swag_webhook_events.model.PaymentCancelled;
|
||||
import dev.vality.swag_webhook_events.model.PaymentCaptured;
|
||||
import dev.vality.swag_webhook_events.model.PaymentFailed;
|
||||
import dev.vality.swag_webhook_events.model.PaymentProcessed;
|
||||
import dev.vality.swag_webhook_events.model.PaymentRefunded;
|
||||
import dev.vality.swag_webhook_events.model.PaymentStarted;
|
||||
import dev.vality.swag_webhook_events.model.Refund;
|
||||
import dev.vality.swag_webhook_events.model.RefundCreated;
|
||||
import dev.vality.swag_webhook_events.model.RefundFailed;
|
||||
import dev.vality.swag_webhook_events.model.RefundSucceeded;
|
||||
import dev.vality.swag_webhook_events.model.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.thrift.TException;
|
||||
import org.springframework.stereotype.Service;
|
||||
@ -45,7 +29,7 @@ public class InvoicingEventService
|
||||
@Override
|
||||
public Event getEventByMessage(InvoicingMessage message) {
|
||||
return resolveEvent(message, getInvoiceByMessage(message))
|
||||
.eventID(message.getEventId().intValue())
|
||||
.eventID(message.getId().intValue())
|
||||
.occuredAt(TimeUtils.toOffsetDateTime(message.getEventTime()))
|
||||
.topic(Event.TopicEnum.INVOICESTOPIC);
|
||||
}
|
||||
@ -60,41 +44,34 @@ public class InvoicingEventService
|
||||
try {
|
||||
return invoicingClient.get(
|
||||
HellgateUtils.USER_INFO,
|
||||
message.getInvoiceId(),
|
||||
message.getSourceId(),
|
||||
HellgateUtils.getEventRange(message.getSequenceId().intValue())
|
||||
);
|
||||
} catch (InvoiceNotFound e) {
|
||||
throw new NotFoundException("Invoice not found, invoiceId=" + message.getInvoiceId());
|
||||
throw new NotFoundException("Invoice not found, invoiceId=" + message.getSourceId());
|
||||
} catch (TException e) {
|
||||
throw new RemoteHostException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Event resolveEvent(InvoicingMessage m, dev.vality.damsel.payment_processing.Invoice invoiceInfo) {
|
||||
switch (m.getEventType()) {
|
||||
case INVOICE_CREATED:
|
||||
return new InvoiceCreated()
|
||||
.invoice(getSwagInvoice(invoiceInfo))
|
||||
.eventType(Event.EventTypeEnum.INVOICECREATED);
|
||||
case INVOICE_STATUS_CHANGED:
|
||||
return resolveInvoiceStatusChanged(m, invoiceInfo);
|
||||
case INVOICE_PAYMENT_STARTED:
|
||||
return new PaymentStarted()
|
||||
.invoice(getSwagInvoice(invoiceInfo))
|
||||
.payment(getSwagPayment(m, invoiceInfo))
|
||||
.eventType(Event.EventTypeEnum.PAYMENTSTARTED);
|
||||
case INVOICE_PAYMENT_STATUS_CHANGED:
|
||||
return resolvePaymentStatusChanged(m, invoiceInfo);
|
||||
case INVOICE_PAYMENT_REFUND_STARTED:
|
||||
return new RefundCreated()
|
||||
.invoice(getSwagInvoice(invoiceInfo))
|
||||
.payment(getSwagPayment(m, invoiceInfo))
|
||||
.refund(getSwagRefund(m, invoiceInfo));
|
||||
case INVOICE_PAYMENT_REFUND_STATUS_CHANGED:
|
||||
return resolveRefundStatusChanged(m, invoiceInfo);
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown event type " + m.getEventType());
|
||||
}
|
||||
return switch (m.getEventType()) {
|
||||
case INVOICE_CREATED -> new InvoiceCreated()
|
||||
.invoice(getSwagInvoice(invoiceInfo))
|
||||
.eventType(Event.EventTypeEnum.INVOICECREATED);
|
||||
case INVOICE_STATUS_CHANGED -> resolveInvoiceStatusChanged(m, invoiceInfo);
|
||||
case INVOICE_PAYMENT_STARTED -> new PaymentStarted()
|
||||
.invoice(getSwagInvoice(invoiceInfo))
|
||||
.payment(getSwagPayment(m, invoiceInfo))
|
||||
.eventType(Event.EventTypeEnum.PAYMENTSTARTED);
|
||||
case INVOICE_PAYMENT_STATUS_CHANGED -> resolvePaymentStatusChanged(m, invoiceInfo);
|
||||
case INVOICE_PAYMENT_REFUND_STARTED -> new RefundCreated()
|
||||
.invoice(getSwagInvoice(invoiceInfo))
|
||||
.payment(getSwagPayment(m, invoiceInfo))
|
||||
.refund(getSwagRefund(m, invoiceInfo));
|
||||
case INVOICE_PAYMENT_REFUND_STATUS_CHANGED -> resolveRefundStatusChanged(m, invoiceInfo);
|
||||
default -> throw new UnsupportedOperationException("Unknown event type " + m.getEventType());
|
||||
};
|
||||
}
|
||||
|
||||
private Invoice getSwagInvoice(dev.vality.damsel.payment_processing.Invoice invoiceInfo) {
|
||||
@ -134,7 +111,7 @@ public class InvoicingEventService
|
||||
.findFirst()
|
||||
.orElseThrow(
|
||||
() -> new NotFoundException(
|
||||
String.format("Payment not found, invoiceId=%s, paymentId=%s", message.getInvoiceId(),
|
||||
String.format("Payment not found, invoiceId=%s, paymentId=%s", message.getSourceId(),
|
||||
message.getPaymentId())
|
||||
)
|
||||
);
|
||||
@ -194,7 +171,7 @@ public class InvoicingEventService
|
||||
() -> new NotFoundException(
|
||||
String.format(
|
||||
"Refund not found, invoiceId=%s, paymentId=%s, refundId=%s",
|
||||
m.getInvoiceId(), m.getPaymentId(), m.getRefundId()
|
||||
m.getSourceId(), m.getPaymentId(), m.getRefundId()
|
||||
)
|
||||
)
|
||||
);
|
||||
@ -205,15 +182,11 @@ public class InvoicingEventService
|
||||
Invoice swagInvoice = getSwagInvoice(invoiceInfo);
|
||||
Payment swagPayment = getSwagPayment(message, invoiceInfo);
|
||||
Refund swagRefund = getSwagRefund(message, invoiceInfo);
|
||||
switch (message.getRefundStatus()) {
|
||||
case PENDING:
|
||||
return new RefundCreated().invoice(swagInvoice).payment(swagPayment).refund(swagRefund);
|
||||
case SUCCEEDED:
|
||||
return new RefundSucceeded().invoice(swagInvoice).payment(swagPayment).refund(swagRefund);
|
||||
case FAILED:
|
||||
return new RefundFailed().invoice(swagInvoice).payment(swagPayment).refund(swagRefund);
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown refund status " + message.getRefundStatus());
|
||||
}
|
||||
return switch (message.getRefundStatus()) {
|
||||
case PENDING -> new RefundCreated().invoice(swagInvoice).payment(swagPayment).refund(swagRefund);
|
||||
case SUCCEEDED -> new RefundSucceeded().invoice(swagInvoice).payment(swagPayment).refund(swagRefund);
|
||||
case FAILED -> new RefundFailed().invoice(swagInvoice).payment(swagPayment).refund(swagRefund);
|
||||
default -> throw new UnsupportedOperationException("Unknown refund status " + message.getRefundStatus());
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,118 +0,0 @@
|
||||
package dev.vality.hooker.service;
|
||||
|
||||
import dev.vality.hooker.dao.HookDao;
|
||||
import dev.vality.hooker.dao.MessageDao;
|
||||
import dev.vality.hooker.dao.QueueDao;
|
||||
import dev.vality.hooker.dao.TaskDao;
|
||||
import dev.vality.hooker.exception.DaoException;
|
||||
import dev.vality.hooker.model.Message;
|
||||
import dev.vality.hooker.model.Queue;
|
||||
import dev.vality.hooker.model.QueueStatus;
|
||||
import dev.vality.hooker.model.Task;
|
||||
import dev.vality.hooker.retry.RetryPoliciesService;
|
||||
import dev.vality.hooker.retry.RetryPolicy;
|
||||
import dev.vality.hooker.retry.RetryPolicyRecord;
|
||||
import dev.vality.hooker.scheduler.MessageSender;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class MessageProcessor<M extends Message, Q extends Queue> implements Runnable {
|
||||
|
||||
private static final double UPDATE_PROBABILITY = 0.25;
|
||||
private final HookDao hookDao;
|
||||
private final TaskDao taskDao;
|
||||
private final QueueDao<Q> queueDao;
|
||||
private final MessageDao<M> messageDao;
|
||||
private final RetryPoliciesService retryPoliciesService;
|
||||
private final TransactionTemplate transactionTemplate;
|
||||
private final FaultDetectorService faultDetector;
|
||||
private final MessageSender<M, Q> messageSender;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
transactionTemplate.execute(k -> {
|
||||
process();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private void process() {
|
||||
Map<Long, List<Task>> scheduledTasks = taskDao.getScheduled();
|
||||
log.debug("scheduledTasks {}", scheduledTasks);
|
||||
if (scheduledTasks.entrySet().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Set<Long> queueIds = scheduledTasks.keySet();
|
||||
Map<Long, Q> queuesMap =
|
||||
queueDao.getWithPolicies(queueIds).stream().collect(Collectors.toMap(Queue::getId, q -> q));
|
||||
Set<Long> messageIds = scheduledTasks.values().stream().flatMap(Collection::stream).map(Task::getMessageId)
|
||||
.collect(Collectors.toSet());
|
||||
Map<Long, M> messagesMap =
|
||||
messageDao.getBy(messageIds).stream().collect(Collectors.toMap(Message::getId, m -> m));
|
||||
|
||||
List<QueueStatus> queueStatuses = messageSender.send(scheduledTasks, queuesMap, messagesMap);
|
||||
queueStatuses.forEach(queueStatus -> {
|
||||
try {
|
||||
Queue queue = queueStatus.getQueue();
|
||||
queueStatus.getMessagesDone().forEach(id -> taskDao.remove(queue.getId(), id));
|
||||
if (queueStatus.isSuccess()) {
|
||||
done(queue);
|
||||
} else {
|
||||
fail(queue);
|
||||
}
|
||||
} catch (DaoException e) {
|
||||
log.error("DaoException error when remove sent messages. " +
|
||||
"It's not a big deal, but some messages may be re-sent: {}",
|
||||
queueStatus.getMessagesDone(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void done(Queue queue) {
|
||||
if (queue.getRetryPolicyRecord().isFailed()) {
|
||||
RetryPolicyRecord record = queue.getRetryPolicyRecord();
|
||||
record.reset();
|
||||
updatePolicy(record);
|
||||
updateAvailability(queue);
|
||||
} else {
|
||||
if (Math.random() < UPDATE_PROBABILITY) {
|
||||
updateAvailability(queue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void fail(Queue queue) {
|
||||
log.warn("Queue {} failed.", queue.getId());
|
||||
RetryPolicy retryPolicy = retryPoliciesService.getRetryPolicyByType(queue.getHook().getRetryPolicyType());
|
||||
RetryPolicyRecord retryPolicyRecord = queue.getRetryPolicyRecord();
|
||||
retryPolicy.updateFailed(retryPolicyRecord);
|
||||
updatePolicy(retryPolicyRecord);
|
||||
updateAvailability(queue);
|
||||
if (retryPolicy.shouldDisable(retryPolicyRecord)) {
|
||||
queueDao.disable(queue.getId());
|
||||
taskDao.removeAll(queue.getId());
|
||||
log.warn("Queue {} was disabled according to retry policy.", queue.getId());
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePolicy(RetryPolicyRecord record) {
|
||||
retryPoliciesService.update(record);
|
||||
log.info("Queue retry policy has been updated {}", record);
|
||||
}
|
||||
|
||||
private void updateAvailability(Queue queue) {
|
||||
//double rate = faultDetector.getRate(queue.getHook().getId());
|
||||
//hookDao.updateAvailability(queue.getHook().getId(), rate);
|
||||
//log.info("Hook {} availability has been updated to {}", queue.getHook().getId(), rate);
|
||||
}
|
||||
}
|
41
src/main/java/dev/vality/hooker/service/MessageService.java
Normal file
41
src/main/java/dev/vality/hooker/service/MessageService.java
Normal file
@ -0,0 +1,41 @@
|
||||
package dev.vality.hooker.service;
|
||||
|
||||
import dev.vality.hooker.converter.WebhookMessageBuilder;
|
||||
import dev.vality.hooker.dao.MessageDao;
|
||||
import dev.vality.hooker.model.Message;
|
||||
import dev.vality.swag_webhook_events.model.Event;
|
||||
import dev.vality.webhook.dispatcher.WebhookMessage;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class MessageService<T extends Message> {
|
||||
private final MessageDao<T> messageDao;
|
||||
private final EventService<T> eventService;
|
||||
private final WebhookMessageBuilder webhookMessageBuilder;
|
||||
private final WebhookKafkaProducerService webhookKafkaProducerService;
|
||||
|
||||
public void process(T message) {
|
||||
log.info("Start processing of message {}", message);
|
||||
Long id = messageDao.save(message);
|
||||
String sourceId = message.getSourceId();
|
||||
if (id != null) {
|
||||
message.setId(id);
|
||||
var webhookModels = messageDao.getWebhookModels(id);
|
||||
if (!webhookModels.isEmpty()) {
|
||||
log.info("Processing {} webhook(s)", webhookModels.size());
|
||||
Event event = eventService.getEventByMessage(message);
|
||||
webhookModels.forEach(w -> {
|
||||
Long hookId = w.getHookId();
|
||||
Long parentEventId = messageDao.getParentId(hookId, sourceId, id);
|
||||
WebhookMessage webhookMessage = webhookMessageBuilder.build(w, event, sourceId, parentEventId);
|
||||
log.info("Try to send webhook to kafka: {}", webhookMessage);
|
||||
webhookKafkaProducerService.send(webhookMessage);
|
||||
log.info("Webhook to kafka was sent: sourceId={}", webhookMessage.getSourceId());
|
||||
});
|
||||
}
|
||||
}
|
||||
log.info("End processing of message {}", sourceId);
|
||||
}
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
package dev.vality.hooker.service;
|
||||
|
||||
import dev.vality.hooker.logging.HttpLoggingInterceptor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.ConnectionPool;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Slf4j
|
||||
public class PostSender {
|
||||
|
||||
public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
|
||||
public static final String SIGNATURE_HEADER = "Content-Signature";
|
||||
public static final long RESPONSE_MAX_LENGTH = 4096L;
|
||||
private final OkHttpClient httpClient;
|
||||
|
||||
public PostSender(int connectionPoolSize, int timeout) {
|
||||
OkHttpClient.Builder httpBuilder = new OkHttpClient.Builder();
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(message -> log.debug(message));
|
||||
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
|
||||
httpBuilder.addInterceptor(httpLoggingInterceptor);
|
||||
}
|
||||
|
||||
ConnectionPool connectionPool = new ConnectionPool(2 * connectionPoolSize, 5, TimeUnit.MINUTES);
|
||||
this.httpClient = httpBuilder
|
||||
.connectionPool(connectionPool)
|
||||
.retryOnConnectionFailure(false)
|
||||
.connectTimeout(timeout, TimeUnit.SECONDS)
|
||||
.writeTimeout(timeout, TimeUnit.SECONDS)
|
||||
.readTimeout(timeout, TimeUnit.SECONDS)
|
||||
.build();
|
||||
}
|
||||
|
||||
public int doPost(String url, long messageId, String paramsAsString, String signature) throws IOException {
|
||||
log.info("Sending message with id {}, {} to hook: {} ", messageId, paramsAsString, url);
|
||||
RequestBody body = RequestBody.create(JSON, paramsAsString);
|
||||
final Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.addHeader(SIGNATURE_HEADER, "alg=RS256; digest=" + signature)
|
||||
.post(body)
|
||||
.build();
|
||||
|
||||
try (Response response = httpClient.newCall(request).execute()) {
|
||||
log.info("Response from hook: messageId: {}, code: {}; body: {}", messageId, response.code(),
|
||||
response.body() != null ? response.peekBody(RESPONSE_MAX_LENGTH).string() : "<empty>");
|
||||
return response.code();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package dev.vality.hooker.service;
|
||||
|
||||
import dev.vality.kafka.common.exception.KafkaProduceException;
|
||||
import dev.vality.webhook.dispatcher.WebhookMessage;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.kafka.core.KafkaTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class WebhookKafkaProducerService {
|
||||
|
||||
private final KafkaTemplate<String, WebhookMessage> kafkaTemplate;
|
||||
|
||||
@Value("${kafka.topic.webhook-dispatcher.name}")
|
||||
private String topicName;
|
||||
|
||||
@Value("${kafka.topic.webhook-dispatcher.produce.enabled}")
|
||||
private boolean producerEnabled;
|
||||
|
||||
public void send(WebhookMessage webhookMessage) {
|
||||
if (producerEnabled) {
|
||||
sendWebhook(webhookMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendWebhook(WebhookMessage webhookMessage) {
|
||||
try {
|
||||
kafkaTemplate.send(topicName, webhookMessage.getSourceId(), webhookMessage).get();
|
||||
} catch (InterruptedException e) {
|
||||
log.error("InterruptedException command: {}", webhookMessage, e);
|
||||
Thread.currentThread().interrupt();
|
||||
throw new KafkaProduceException(e);
|
||||
} catch (Exception e) {
|
||||
log.error("Error while sending command: {}", webhookMessage, e);
|
||||
throw new KafkaProduceException(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +1,6 @@
|
||||
package dev.vality.hooker.service;
|
||||
|
||||
import dev.vality.damsel.webhooker.LimitExceeded;
|
||||
import dev.vality.damsel.webhooker.Webhook;
|
||||
import dev.vality.damsel.webhooker.WebhookManagerSrv;
|
||||
import dev.vality.damsel.webhooker.WebhookNotFound;
|
||||
import dev.vality.damsel.webhooker.WebhookParams;
|
||||
import dev.vality.damsel.webhooker.*;
|
||||
import dev.vality.hooker.dao.HookDao;
|
||||
import dev.vality.hooker.model.Hook;
|
||||
import dev.vality.hooker.utils.HookConverter;
|
||||
|
@ -1,47 +0,0 @@
|
||||
package dev.vality.hooker.service;
|
||||
|
||||
import dev.vality.damsel.webhooker.SourceNotFound;
|
||||
import dev.vality.damsel.webhooker.WebhookMessageServiceSrv;
|
||||
import dev.vality.damsel.webhooker.WebhookNotFound;
|
||||
import dev.vality.hooker.dao.HookDao;
|
||||
import dev.vality.hooker.dao.impl.CustomerTaskDao;
|
||||
import dev.vality.hooker.dao.impl.InvoicingTaskDao;
|
||||
import dev.vality.hooker.model.Hook;
|
||||
import dev.vality.swag_webhook_events.model.Event;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.thrift.TException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class WebhookMessageService implements WebhookMessageServiceSrv.Iface {
|
||||
|
||||
private final HookDao hookDao;
|
||||
private final InvoicingTaskDao invoicingTaskDao;
|
||||
private final CustomerTaskDao customerTaskDao;
|
||||
|
||||
@Override
|
||||
public void send(long hookId, String sourceId) throws TException {
|
||||
log.info("Start creating tasks for sending hooks for hookId={}, invoiceId={}", hookId, sourceId);
|
||||
Hook hook = hookDao.getHookById(hookId);
|
||||
if (hook == null) {
|
||||
log.warn("Webhook with id={} not found", hookId);
|
||||
throw new WebhookNotFound();
|
||||
}
|
||||
int count;
|
||||
if (hook.getTopic().equals(Event.TopicEnum.INVOICESTOPIC.getValue())) {
|
||||
count = invoicingTaskDao.save(hookId, sourceId);
|
||||
} else if (hook.getTopic().equals(Event.TopicEnum.CUSTOMERSTOPIC.getValue())) {
|
||||
count = customerTaskDao.create(hookId, sourceId);
|
||||
} else {
|
||||
throw new RuntimeException("Unknown webhook type " + hook.getTopic());
|
||||
}
|
||||
if (count < 1) {
|
||||
log.warn("No tasks created for hookId={} and invoiceId={}", hookId, sourceId);
|
||||
throw new SourceNotFound();
|
||||
}
|
||||
log.info("Tasks has been created. Count={} for hookId={}, invoiceId={}", count, hookId, sourceId);
|
||||
}
|
||||
}
|
@ -4,13 +4,7 @@ import dev.vality.hooker.service.err.UnknownCryptoException;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.Signature;
|
||||
import java.security.SignatureException;
|
||||
import java.security.*;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.KeySpec;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
|
@ -1,17 +0,0 @@
|
||||
package dev.vality.hooker.service.err;
|
||||
|
||||
public class PostRequestException extends Exception {
|
||||
public PostRequestException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public PostRequestException(String errMessage) {
|
||||
super(errMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
String message = getCause() != null ? getCause().getMessage() : super.getMessage();
|
||||
return "Unknown error during request to merchant execution. \n" + message;
|
||||
}
|
||||
}
|
@ -1,28 +1,6 @@
|
||||
package dev.vality.hooker.utils;
|
||||
|
||||
import dev.vality.damsel.webhooker.CustomerBindingEvent;
|
||||
import dev.vality.damsel.webhooker.CustomerBindingFailed;
|
||||
import dev.vality.damsel.webhooker.CustomerBindingStarted;
|
||||
import dev.vality.damsel.webhooker.CustomerBindingSucceeded;
|
||||
import dev.vality.damsel.webhooker.CustomerCreated;
|
||||
import dev.vality.damsel.webhooker.CustomerDeleted;
|
||||
import dev.vality.damsel.webhooker.CustomerEventFilter;
|
||||
import dev.vality.damsel.webhooker.CustomerEventType;
|
||||
import dev.vality.damsel.webhooker.CustomerStatusReady;
|
||||
import dev.vality.damsel.webhooker.EventFilter;
|
||||
import dev.vality.damsel.webhooker.InvoiceCreated;
|
||||
import dev.vality.damsel.webhooker.InvoiceEventFilter;
|
||||
import dev.vality.damsel.webhooker.InvoiceEventType;
|
||||
import dev.vality.damsel.webhooker.InvoicePaymentCreated;
|
||||
import dev.vality.damsel.webhooker.InvoicePaymentEventType;
|
||||
import dev.vality.damsel.webhooker.InvoicePaymentRefundChange;
|
||||
import dev.vality.damsel.webhooker.InvoicePaymentRefundCreated;
|
||||
import dev.vality.damsel.webhooker.InvoicePaymentRefundStatus;
|
||||
import dev.vality.damsel.webhooker.InvoicePaymentRefundStatusChanged;
|
||||
import dev.vality.damsel.webhooker.InvoicePaymentStatus;
|
||||
import dev.vality.damsel.webhooker.InvoicePaymentStatusChanged;
|
||||
import dev.vality.damsel.webhooker.InvoiceStatus;
|
||||
import dev.vality.damsel.webhooker.InvoiceStatusChanged;
|
||||
import dev.vality.damsel.webhooker.*;
|
||||
import dev.vality.hooker.dao.WebhookAdditionalFilter;
|
||||
import dev.vality.hooker.model.EventType;
|
||||
import dev.vality.swag_webhook_events.model.Event;
|
||||
|
@ -7,7 +7,7 @@ public class KeyUtils {
|
||||
|
||||
public static InvoicingMessageKey key(InvoicingMessage message) {
|
||||
return InvoicingMessageKey.builder()
|
||||
.invoiceId(message.getInvoiceId())
|
||||
.invoiceId(message.getSourceId())
|
||||
.paymentId(message.getPaymentId())
|
||||
.refundId(message.getRefundId())
|
||||
.type(message.getType())
|
||||
|
@ -11,8 +11,6 @@ import dev.vality.swag_webhook_events.model.*;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
public class PaymentToolUtils {
|
||||
|
||||
@ -25,14 +23,14 @@ public class PaymentToolUtils {
|
||||
.lastDigits(paymentTool.getBankCard().getLastDigits())
|
||||
.cardNumberMask(
|
||||
paymentTool.getBankCard().getBin() + "******" + paymentTool.getBankCard().getLastDigits())
|
||||
.tokenProvider(
|
||||
Optional.ofNullable(TokenProviderUtil.getTokenProviderName(paymentTool.getBankCard()))
|
||||
.map(PaymentToolDetailsBankCard.TokenProviderEnum::fromValue)
|
||||
.orElse(null)
|
||||
)
|
||||
.tokenProvider(paymentTool.getBankCard().getTokenProviderDeprecated() != null
|
||||
? PaymentToolDetailsBankCard.TokenProviderEnum.fromValue(
|
||||
paymentTool.getBankCard().getTokenProviderDeprecated().name())
|
||||
: null)
|
||||
.paymentSystem(PaymentSystemUtil.getPaymentSystemName(paymentTool.getBankCard()))
|
||||
.issuerCountry(paymentTool.getBankCard().getIssuerCountry() != null
|
||||
? paymentTool.getBankCard().getIssuerCountry().name() : null)
|
||||
? paymentTool.getBankCard().getIssuerCountry().name()
|
||||
: null)
|
||||
.bankName(paymentTool.getBankCard().getBankName())
|
||||
.detailsType(PaymentToolDetails.DetailsTypeEnum.PAYMENTTOOLDETAILSBANKCARD);
|
||||
} else if (paymentTool.isSetPaymentTerminal()) {
|
||||
@ -41,7 +39,6 @@ public class PaymentToolUtils {
|
||||
TerminalPaymentUtil.getTerminalPaymentProviderName(paymentTool.getPaymentTerminal())))
|
||||
.detailsType(PaymentToolDetails.DetailsTypeEnum.PAYMENTTOOLDETAILSPAYMENTTERMINAL);
|
||||
} else if (paymentTool.isSetDigitalWallet()) {
|
||||
//TODO Bump swag-webhook-events api
|
||||
LegacyDigitalWalletProvider walletProvider = LegacyDigitalWalletProvider.valueOf(
|
||||
DigitalWalletUtil.getDigitalWalletName(paymentTool.getDigitalWallet()));
|
||||
if (walletProvider == LegacyDigitalWalletProvider.qiwi) {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user