diff --git a/.gitignore b/.gitignore
index 33589c6..fb08ff7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,42 +1,56 @@
# Created by .ignore support plugin (hsz.mobi)
-### JetBrains template
-# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm
-
-*.DS_Store
-
-*.iml
-
-## Directory-based project format:
-.idea/
-# if you remove the above rule, at least ignore the following:
+.eunit
+deps
+*.o
+*.beam
+*.plt
+erl_crash.dump
+ebin/*.beam
+rel/example_project
+.concrete/DEV_MODE
+.rebar
+target/
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+pom.xml.next
+release.properties
+dependency-reduced-pom.xml
+buildNumber.properties
+.mvn/timing.properties
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff:
-# .idea/workspace.xml
-# .idea/tasks.xml
-# .idea/dictionaries
+.idea/
+.idea/workspace.xml
+.idea/tasks.xml
+.idea/dictionaries
+.idea/vcs.xml
+.idea/jsLibraryMappings.xml
# Sensitive or high-churn files:
-# .idea/dataSources.ids
-# .idea/dataSources.xml
-# .idea/sqlDataSources.xml
-# .idea/dynamic.xml
-# .idea/uiDesigner.xml
+.idea/dataSources.ids
+.idea/dataSources.xml
+.idea/dataSources.local.xml
+.idea/sqlDataSources.xml
+.idea/dynamic.xml
+.idea/uiDesigner.xml
# Gradle:
-# .idea/gradle.xml
-# .idea/libraries
+.idea/gradle.xml
+.idea/libraries
# Mongo Explorer plugin:
-# .idea/mongoSettings.xml
+.idea/mongoSettings.xml
-## File-based project format:
-*.ipr
*.iws
+*.ipr
+*.iml
-## Plugin-specific files:
# IntelliJ
-out/
+/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
@@ -48,9 +62,17 @@ atlassian-ide-plugin.xml
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
+fabric.properties
+*.class
-# Target folder
-target
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
-# Target ant folder
-build
+# Package Files #
+*.jar
+*.war
+*.ear
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+env.list
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..ca5a761
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,4 @@
+[submodule "build_utils"]
+ path = build_utils
+ url = git@github.com:rbkmoney/build_utils.git
+ branch = master
diff --git a/Jenkinsfile b/Jenkinsfile
new file mode 100644
index 0000000..fff612e
--- /dev/null
+++ b/Jenkinsfile
@@ -0,0 +1,16 @@
+#!groovy
+build('wb-list-manager', '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)
+}
\ No newline at end of file
diff --git a/build_utils b/build_utils
new file mode 160000
index 0000000..269686d
--- /dev/null
+++ b/build_utils
@@ -0,0 +1 @@
+Subproject commit 269686d735abef363f9f40a1bf4e1b7c751f3722
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..6d14eb4
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,190 @@
+
+
+ 4.0.0
+
+
+ com.rbkmoney
+ spring-boot-starter-parent
+ 2.1.1.RELEASE
+
+
+ wb-list-manager
+ 0.0.1-SNAPSHOT
+ jar
+
+ wb-list-manager
+
+
+ UTF-8
+ UTF-8
+ 11
+ 8022
+ ${server.port}
+ 81f57f9ef5e5854c27659c9f56f59cdb2b7c43e5
+ 0.3.2
+
+
+
+
+ com.rbkmoney
+ shared-resources
+ ${shared.resources.version}
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ javax.servlet
+ javax.servlet-api
+ 4.0.1
+
+
+ com.rbkmoney.woody
+ woody-thrift
+ 1.1.15
+
+
+ com.rbkmoney
+ spring-boot-starter-metrics-statsd
+ 1.1.0
+
+
+ org.projectlombok
+ lombok
+ 1.18.4
+ provided
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.testcontainers
+ testcontainers
+ 1.10.5
+ test
+
+
+
+ com.basho.riak
+ riak-client
+ 2.1.1
+
+
+
+
+ org.apache.kafka
+ kafka-clients
+ 2.1.0
+
+
+ org.springframework.kafka
+ spring-kafka
+ 2.2.2.RELEASE
+
+
+ org.apache.kafka
+ kafka-clients
+
+
+
+
+ org.testcontainers
+ kafka
+ 1.10.2
+ test
+
+
+
+ com.rbkmoney
+ wb-list-proto
+ 1.8-36aed97
+
+
+
+
+
+
+ ${project.build.directory}/maven-shared-archive-resources
+ ${project.build.directory}
+
+ Dockerfile
+
+ true
+
+
+ ${project.build.directory}/maven-shared-archive-resources
+ true
+
+ Dockerfile
+
+
+
+ src/main/resources
+ true
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ 2.1.1.RELEASE
+
+
+ org.apache.maven.plugins
+ maven-remote-resources-plugin
+ 1.6.0
+
+
+ org.apache.maven.shared
+ maven-filtering
+ 1.3
+
+
+
+
+ com.rbkmoney:shared-resources:${shared.resources.version}
+
+ false
+ false
+
+
+
+
+ process
+
+
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ 0.8.2
+
+ ${sonar.jacoco.reportPath}
+ true
+
+
+
+ agent
+
+ prepare-agent
+
+
+
+
+
+
+
diff --git a/src/main/java/com/rbkmoney/wb/list/manager/WbListManagerApplication.java b/src/main/java/com/rbkmoney/wb/list/manager/WbListManagerApplication.java
new file mode 100644
index 0000000..53e972f
--- /dev/null
+++ b/src/main/java/com/rbkmoney/wb/list/manager/WbListManagerApplication.java
@@ -0,0 +1,15 @@
+package com.rbkmoney.wb.list.manager;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.web.servlet.ServletComponentScan;
+
+@ServletComponentScan
+@SpringBootApplication
+public class WbListManagerApplication extends SpringApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(WbListManagerApplication.class);
+ }
+
+}
diff --git a/src/main/java/com/rbkmoney/wb/list/manager/config/KafkaConfig.java b/src/main/java/com/rbkmoney/wb/list/manager/config/KafkaConfig.java
new file mode 100644
index 0000000..97b32f3
--- /dev/null
+++ b/src/main/java/com/rbkmoney/wb/list/manager/config/KafkaConfig.java
@@ -0,0 +1,48 @@
+package com.rbkmoney.wb.list.manager.config;
+
+import org.apache.kafka.clients.consumer.ConsumerConfig;
+import org.apache.kafka.common.serialization.StringDeserializer;
+import org.springframework.beans.factory.annotation.Value;
+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.listener.ConcurrentMessageListenerContainer;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Configuration
+public class KafkaConfig {
+
+ private static final String GROUP_ID = "CommandListener";
+ private static final String EARLIEST = "earliest";
+
+ @Value("${kafka.bootstrap.servers}")
+ private String bootstrapServers;
+
+ @Bean
+ public Map consumerConfigs() {
+ Map props = new HashMap<>();
+ props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
+ props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
+ props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
+ props.put(ConsumerConfig.GROUP_ID_CONFIG, GROUP_ID);
+ props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, EARLIEST);
+ return props;
+ }
+
+ @Bean
+ public ConsumerFactory consumerFactory() {
+ return new DefaultKafkaConsumerFactory<>(consumerConfigs());
+ }
+
+ @Bean
+ public KafkaListenerContainerFactory> kafkaListenerContainerFactory() {
+ ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>();
+ factory.setConsumerFactory(consumerFactory());
+ return factory;
+ }
+}
diff --git a/src/main/java/com/rbkmoney/wb/list/manager/config/ResourceConfig.java b/src/main/java/com/rbkmoney/wb/list/manager/config/ResourceConfig.java
new file mode 100644
index 0000000..d256265
--- /dev/null
+++ b/src/main/java/com/rbkmoney/wb/list/manager/config/ResourceConfig.java
@@ -0,0 +1,17 @@
+package com.rbkmoney.wb.list.manager.config;
+
+import com.rbkmoney.damsel.wb_list.WbListServiceSrv;
+import com.rbkmoney.wb.list.manager.handler.WbListServiceHandler;
+import com.rbkmoney.wb.list.manager.repository.ListRepository;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class ResourceConfig {
+
+ @Bean
+ public WbListServiceSrv.Iface fraudInspectorHandler(ListRepository listRepository) {
+ return new WbListServiceHandler(listRepository);
+ }
+
+}
diff --git a/src/main/java/com/rbkmoney/wb/list/manager/config/RiakConfig.java b/src/main/java/com/rbkmoney/wb/list/manager/config/RiakConfig.java
new file mode 100644
index 0000000..1947f06
--- /dev/null
+++ b/src/main/java/com/rbkmoney/wb/list/manager/config/RiakConfig.java
@@ -0,0 +1,35 @@
+package com.rbkmoney.wb.list.manager.config;
+
+import com.basho.riak.client.api.RiakClient;
+import com.basho.riak.client.core.RiakCluster;
+import com.basho.riak.client.core.RiakNode;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class RiakConfig {
+
+ @Value("${riak.port}")
+ public int riakPort;
+
+ @Value("${riak.address}")
+ public String riakAddress;
+
+ @Bean
+ public RiakCluster riakCluster() {
+ RiakNode node = new RiakNode.Builder()
+ .withRemoteAddress(riakAddress)
+ .withRemotePort(riakPort)
+ .build();
+ RiakCluster cluster = new RiakCluster.Builder(node)
+ .build();
+ cluster.start();
+ return cluster;
+ }
+
+ @Bean
+ public RiakClient riakClient() {
+ return new RiakClient(riakCluster());
+ }
+}
diff --git a/src/main/java/com/rbkmoney/wb/list/manager/exception/RiakExecutionException.java b/src/main/java/com/rbkmoney/wb/list/manager/exception/RiakExecutionException.java
new file mode 100644
index 0000000..1652cde
--- /dev/null
+++ b/src/main/java/com/rbkmoney/wb/list/manager/exception/RiakExecutionException.java
@@ -0,0 +1,23 @@
+package com.rbkmoney.wb.list.manager.exception;
+
+public class RiakExecutionException extends RuntimeException {
+
+ public RiakExecutionException() {
+ }
+
+ public RiakExecutionException(String message) {
+ super(message);
+ }
+
+ public RiakExecutionException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public RiakExecutionException(Throwable cause) {
+ super(cause);
+ }
+
+ public RiakExecutionException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+}
diff --git a/src/main/java/com/rbkmoney/wb/list/manager/handler/WbListServiceHandler.java b/src/main/java/com/rbkmoney/wb/list/manager/handler/WbListServiceHandler.java
new file mode 100644
index 0000000..3700eb2
--- /dev/null
+++ b/src/main/java/com/rbkmoney/wb/list/manager/handler/WbListServiceHandler.java
@@ -0,0 +1,29 @@
+package com.rbkmoney.wb.list.manager.handler;
+
+import com.rbkmoney.damsel.wb_list.ListNotFound;
+import com.rbkmoney.damsel.wb_list.WbListServiceSrv;
+import com.rbkmoney.wb.list.manager.exception.RiakExecutionException;
+import com.rbkmoney.wb.list.manager.repository.ListRepository;
+import com.rbkmoney.wb.list.manager.utils.KeyGenerator;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.thrift.TException;
+
+@Slf4j
+@RequiredArgsConstructor
+public class WbListServiceHandler implements WbListServiceSrv.Iface {
+
+ private final ListRepository listRepository;
+
+ @Override
+ public boolean isExist(String partyId, String shopId, String listName, String value) throws ListNotFound, TException {
+ String bucket = KeyGenerator.generateBucket(partyId, shopId);
+ String key = KeyGenerator.generateKey(listName, value);
+ try {
+ return listRepository.get(bucket, key).isPresent();
+ } catch (RiakExecutionException e) {
+ throw new TException(e);
+ }
+ }
+
+}
diff --git a/src/main/java/com/rbkmoney/wb/list/manager/listener/WbListListener.java b/src/main/java/com/rbkmoney/wb/list/manager/listener/WbListListener.java
new file mode 100644
index 0000000..460ed22
--- /dev/null
+++ b/src/main/java/com/rbkmoney/wb/list/manager/listener/WbListListener.java
@@ -0,0 +1,22 @@
+package com.rbkmoney.wb.list.manager.listener;
+
+import com.rbkmoney.wb.list.manager.model.Row;
+import com.rbkmoney.wb.list.manager.repository.ListRepository;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.kafka.annotation.KafkaListener;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class WbListListener {
+
+ private final ListRepository listRepository;
+
+ @KafkaListener(topics = "${kafka.wblist.topic}", containerFactory = "kafkaListenerContainerFactory")
+ public void listen(Row row) {
+ log.info("TemplateListener ruleTemplate: {}", row);
+ listRepository.create(row);
+ }
+}
diff --git a/src/main/java/com/rbkmoney/wb/list/manager/model/Row.java b/src/main/java/com/rbkmoney/wb/list/manager/model/Row.java
new file mode 100644
index 0000000..e195cd9
--- /dev/null
+++ b/src/main/java/com/rbkmoney/wb/list/manager/model/Row.java
@@ -0,0 +1,17 @@
+package com.rbkmoney.wb.list.manager.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class Row {
+
+ private String bucketName;
+ private String key;
+
+ private String value;
+
+}
diff --git a/src/main/java/com/rbkmoney/wb/list/manager/repository/CrudRepository.java b/src/main/java/com/rbkmoney/wb/list/manager/repository/CrudRepository.java
new file mode 100644
index 0000000..73dde12
--- /dev/null
+++ b/src/main/java/com/rbkmoney/wb/list/manager/repository/CrudRepository.java
@@ -0,0 +1,15 @@
+package com.rbkmoney.wb.list.manager.repository;
+
+import com.rbkmoney.wb.list.manager.model.Row;
+
+import java.util.Optional;
+
+public interface CrudRepository {
+
+ void create(T row);
+
+ void remove(T row);
+
+ Optional get(String bucket, K key);
+
+}
diff --git a/src/main/java/com/rbkmoney/wb/list/manager/repository/ListRepository.java b/src/main/java/com/rbkmoney/wb/list/manager/repository/ListRepository.java
new file mode 100644
index 0000000..1a5139f
--- /dev/null
+++ b/src/main/java/com/rbkmoney/wb/list/manager/repository/ListRepository.java
@@ -0,0 +1,91 @@
+package com.rbkmoney.wb.list.manager.repository;
+
+import com.basho.riak.client.api.RiakClient;
+import com.basho.riak.client.api.cap.Quorum;
+import com.basho.riak.client.api.commands.kv.DeleteValue;
+import com.basho.riak.client.api.commands.kv.FetchValue;
+import com.basho.riak.client.api.commands.kv.StoreValue;
+import com.basho.riak.client.core.query.Location;
+import com.basho.riak.client.core.query.Namespace;
+import com.basho.riak.client.core.query.RiakObject;
+import com.basho.riak.client.core.util.BinaryValue;
+import com.rbkmoney.wb.list.manager.exception.RiakExecutionException;
+import com.rbkmoney.wb.list.manager.model.Row;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.util.Optional;
+import java.util.concurrent.ExecutionException;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class ListRepository implements CrudRepository {
+
+ private static final String TEXT_PLAIN = "text/plain";
+ private final RiakClient client;
+
+ @Override
+ public void create(Row row) {
+ try {
+ RiakObject quoteObject = new RiakObject()
+ .setContentType(TEXT_PLAIN)
+ .setValue(BinaryValue.create(row.getValue()));
+ Location quoteObjectLocation = createLocation(row.getBucketName(), row.getKey());
+ StoreValue storeOp = new StoreValue.Builder(quoteObject)
+ .withOption(StoreValue.Option.W, Quorum.oneQuorum())
+ .withLocation(quoteObjectLocation)
+ .build();
+ client.execute(storeOp);
+ } catch (ExecutionException e) {
+ log.error("Exception in ListRepository when create e: ", e);
+ throw new RiakExecutionException();
+ } catch (InterruptedException e) {
+ log.error("InterruptedException in ListRepository when get e: ", e);
+ Thread.currentThread().interrupt();
+ throw new RiakExecutionException(e);
+ }
+ }
+
+ @Override
+ public void remove(Row row) {
+ try {
+ Location quoteObjectLocation = createLocation(row.getBucketName(), row.getKey());
+ DeleteValue delete = new DeleteValue.Builder(quoteObjectLocation).build();
+ client.execute(delete);
+ } catch (ExecutionException e) {
+ log.error("Exception in ListRepository when remove e: ", e);
+ throw new RiakExecutionException(e);
+ } catch (InterruptedException e) {
+ log.error("InterruptedException in ListRepository when get e: ", e);
+ Thread.currentThread().interrupt();
+ throw new RiakExecutionException(e);
+ }
+ }
+
+ @Override
+ public Optional get(String bucket, String key) {
+ try {
+ Location quoteObjectLocation = createLocation(bucket, key);
+ FetchValue fetch = new FetchValue.Builder(quoteObjectLocation)
+ .withOption(FetchValue.Option.R, new Quorum(3))
+ .build();
+ FetchValue.Response response = client.execute(fetch);
+ RiakObject obj = response.getValue(RiakObject.class);
+ return Optional.of(new Row(bucket, key, obj.getValue().toString()));
+ } catch (ExecutionException e) {
+ log.error("Exception in ListRepository when get e: ", e);
+ throw new RiakExecutionException(e);
+ } catch (InterruptedException e) {
+ log.error("InterruptedException in ListRepository when get e: ", e);
+ Thread.currentThread().interrupt();
+ throw new RiakExecutionException(e);
+ }
+ }
+
+ private Location createLocation(String bucketName, String key) {
+ Namespace quotesBucket = new Namespace(bucketName);
+ return new Location(quotesBucket, key);
+ }
+}
diff --git a/src/main/java/com/rbkmoney/wb/list/manager/resource/FraudInspectorServlet.java b/src/main/java/com/rbkmoney/wb/list/manager/resource/FraudInspectorServlet.java
new file mode 100644
index 0000000..acdfe0e
--- /dev/null
+++ b/src/main/java/com/rbkmoney/wb/list/manager/resource/FraudInspectorServlet.java
@@ -0,0 +1,30 @@
+package com.rbkmoney.wb.list.manager.resource;
+
+import com.rbkmoney.damsel.wb_list.WbListServiceSrv;
+import com.rbkmoney.woody.thrift.impl.http.THServiceBuilder;
+import lombok.RequiredArgsConstructor;
+
+import javax.servlet.*;
+import javax.servlet.annotation.WebServlet;
+import java.io.IOException;
+
+@WebServlet("/v1/wb_list")
+@RequiredArgsConstructor
+public class FraudInspectorServlet extends GenericServlet {
+
+ private Servlet thriftServlet;
+
+ private final WbListServiceSrv.Iface fraudInspectorHandler;
+
+ @Override
+ public void init(ServletConfig config) throws ServletException {
+ super.init(config);
+ thriftServlet = new THServiceBuilder()
+ .build(WbListServiceSrv.Iface.class, fraudInspectorHandler);
+ }
+
+ @Override
+ public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
+ thriftServlet.service(req, res);
+ }
+}
diff --git a/src/main/java/com/rbkmoney/wb/list/manager/serializer/JsonPOJODeserializer.java b/src/main/java/com/rbkmoney/wb/list/manager/serializer/JsonPOJODeserializer.java
new file mode 100644
index 0000000..12ad0bb
--- /dev/null
+++ b/src/main/java/com/rbkmoney/wb/list/manager/serializer/JsonPOJODeserializer.java
@@ -0,0 +1,46 @@
+package com.rbkmoney.wb.list.manager.serializer;
+
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.kafka.common.errors.SerializationException;
+import org.apache.kafka.common.serialization.Deserializer;
+
+import java.util.Map;
+
+public class JsonPOJODeserializer implements Deserializer {
+ private ObjectMapper objectMapper = new ObjectMapper();
+
+ private Class tClass;
+
+ /**
+ * Default constructor needed by Kafka
+ */
+ public JsonPOJODeserializer() {
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void configure(Map props, boolean isKey) {
+ tClass = (Class) props.get("JsonPOJOClass");
+ }
+
+ @Override
+ public T deserialize(String topic, byte[] bytes) {
+ if (bytes == null)
+ return null;
+
+ T data;
+ try {
+ data = objectMapper.readValue(bytes, tClass);
+ } catch (Exception e) {
+ throw new SerializationException(e);
+ }
+
+ return data;
+ }
+
+ @Override
+ public void close() {
+
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/rbkmoney/wb/list/manager/serializer/JsonPOJOSerializer.java b/src/main/java/com/rbkmoney/wb/list/manager/serializer/JsonPOJOSerializer.java
new file mode 100644
index 0000000..42b9c08
--- /dev/null
+++ b/src/main/java/com/rbkmoney/wb/list/manager/serializer/JsonPOJOSerializer.java
@@ -0,0 +1,38 @@
+package com.rbkmoney.wb.list.manager.serializer;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.kafka.common.errors.SerializationException;
+import org.apache.kafka.common.serialization.Serializer;
+
+import java.util.Map;
+
+public class JsonPOJOSerializer implements Serializer {
+ private final ObjectMapper objectMapper = new ObjectMapper();
+
+ /**
+ * Default constructor needed by Kafka
+ */
+ public JsonPOJOSerializer() {
+ }
+
+ @Override
+ public void configure(Map props, boolean isKey) {
+ }
+
+ @Override
+ public byte[] serialize(String topic, T data) {
+ if (data == null)
+ return null;
+
+ try {
+ return objectMapper.writeValueAsBytes(data);
+ } catch (Exception e) {
+ throw new SerializationException("Error serializing JSON message", e);
+ }
+ }
+
+ @Override
+ public void close() {
+ }
+
+}
diff --git a/src/main/java/com/rbkmoney/wb/list/manager/utils/KeyGenerator.java b/src/main/java/com/rbkmoney/wb/list/manager/utils/KeyGenerator.java
new file mode 100644
index 0000000..a3aba17
--- /dev/null
+++ b/src/main/java/com/rbkmoney/wb/list/manager/utils/KeyGenerator.java
@@ -0,0 +1,19 @@
+package com.rbkmoney.wb.list.manager.utils;
+
+public class KeyGenerator {
+
+ public static final String DELIMITER = "_";
+
+ public static String generateBucket(String partyId, String shopId) {
+ return generateKeySecondField(partyId, shopId);
+ }
+
+ public static String generateKey(String type, String key) {
+ return generateKeySecondField(type, key);
+ }
+
+ private static String generateKeySecondField(String first, String second) {
+ return first + DELIMITER + second;
+ }
+
+}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
new file mode 100644
index 0000000..de92a89
--- /dev/null
+++ b/src/main/resources/application.yml
@@ -0,0 +1,16 @@
+server.port: @server.port@
+management.security.flag: false
+spring.application.name: @project.name@
+info.version: @project.version@
+info.stage: dev
+
+management.metrics.export.statsd:
+ flavor: etsy
+
+riak:
+ address: localhost
+ port: 8087
+
+kafka:
+ bootstrap.servers: "localhost:9092"
+ wblist.topic: "wblist"
\ No newline at end of file
diff --git a/src/test/java/com/rbkmoney/wb/list/manager/KafkaAbstractTest.java b/src/test/java/com/rbkmoney/wb/list/manager/KafkaAbstractTest.java
new file mode 100644
index 0000000..5f1ce2b
--- /dev/null
+++ b/src/test/java/com/rbkmoney/wb/list/manager/KafkaAbstractTest.java
@@ -0,0 +1,34 @@
+package com.rbkmoney.wb.list.manager;
+
+import org.junit.ClassRule;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.util.TestPropertyValues;
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.testcontainers.containers.KafkaContainer;
+
+import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = RANDOM_PORT)
+@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
+@ContextConfiguration(initializers = KafkaAbstractTest.Initializer.class)
+public abstract class KafkaAbstractTest {
+
+ @ClassRule
+ public static KafkaContainer kafka = new KafkaContainer("5.0.1").withEmbeddedZookeeper();
+
+ public static class Initializer implements ApplicationContextInitializer {
+ @Override
+ public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
+ TestPropertyValues
+ .of("kafka.bootstrap.servers=" + kafka.getBootstrapServers())
+ .applyTo(configurableApplicationContext.getEnvironment());
+ }
+ }
+
+}
diff --git a/src/test/java/com/rbkmoney/wb/list/manager/WbListManagerApplicationTest.java b/src/test/java/com/rbkmoney/wb/list/manager/WbListManagerApplicationTest.java
new file mode 100644
index 0000000..50f23dc
--- /dev/null
+++ b/src/test/java/com/rbkmoney/wb/list/manager/WbListManagerApplicationTest.java
@@ -0,0 +1,96 @@
+package com.rbkmoney.wb.list.manager;
+
+import com.basho.riak.client.api.RiakClient;
+import com.basho.riak.client.api.commands.kv.FetchValue;
+import com.basho.riak.client.core.query.Location;
+import com.basho.riak.client.core.query.Namespace;
+import com.basho.riak.client.core.query.RiakObject;
+import com.rbkmoney.wb.list.manager.config.KafkaConfig;
+import com.rbkmoney.wb.list.manager.model.Row;
+import com.rbkmoney.wb.list.manager.repository.ListRepository;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.util.TestPropertyValues;
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.FilterType;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.strategy.WaitAllStrategy;
+
+import java.util.Optional;
+import java.util.concurrent.ExecutionException;
+
+import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
+
+@Slf4j
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = RANDOM_PORT)
+@ContextConfiguration(classes = WbListManagerApplication.class, initializers = WbListManagerApplicationTest.Initializer.class)
+public class WbListManagerApplicationTest extends KafkaAbstractTest {
+
+ public static final String TYPE = "type";
+ public static final String BUCKET_NAME = "bucketName";
+ public static final String VALUE = "value";
+ public static final String KEY = "key";
+
+ @Autowired
+ private ListRepository listRepository;
+ @Autowired
+ private RiakClient client;
+
+ @ClassRule
+ public static GenericContainer riak = new GenericContainer("basho/riak-kv")
+ .waitingFor(new WaitAllStrategy());
+
+ public static class Initializer implements ApplicationContextInitializer {
+ @Override
+ public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
+ TestPropertyValues
+ .of("riak.port=" + riak.getMappedPort(8087))
+ .applyTo(configurableApplicationContext.getEnvironment());
+ }
+ }
+
+ @Before
+ public void init() throws InterruptedException {
+ riak.start();
+ // TODO add cycle for up check
+ Thread.sleep(8000L);
+ }
+
+ @Test
+ public void contextLoads() throws ExecutionException, InterruptedException {
+ Row row = new Row();
+ row.setKey(KEY);
+ row.setBucketName(BUCKET_NAME);
+ row.setValue(VALUE);
+ listRepository.create(row);
+
+ Namespace ns = new Namespace(BUCKET_NAME);
+ Location location = new Location(ns, KEY);
+ FetchValue fv = new FetchValue.Builder(location).build();
+ FetchValue.Response response = client.execute(fv);
+ RiakObject obj = response.getValue(RiakObject.class);
+
+ String result = obj.getValue().toString();
+ Assert.assertEquals(VALUE, result);
+
+ Optional resultGet = listRepository.get(BUCKET_NAME, KEY);
+ Assert.assertFalse(resultGet.isEmpty());
+ Assert.assertEquals(VALUE, resultGet.get().getValue());
+
+ listRepository.remove(row);
+ response = client.execute(fv);
+ obj = response.getValue(RiakObject.class);
+ Assert.assertNull(obj);
+ }
+}
diff --git a/src/test/java/com/rbkmoney/wb/list/manager/handler/WbListServiceHandlerTest.java b/src/test/java/com/rbkmoney/wb/list/manager/handler/WbListServiceHandlerTest.java
new file mode 100644
index 0000000..a17b69a
--- /dev/null
+++ b/src/test/java/com/rbkmoney/wb/list/manager/handler/WbListServiceHandlerTest.java
@@ -0,0 +1,43 @@
+package com.rbkmoney.wb.list.manager.handler;
+
+import com.rbkmoney.wb.list.manager.model.Row;
+import com.rbkmoney.wb.list.manager.repository.ListRepository;
+import org.apache.thrift.TException;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Optional;
+
+import static org.mockito.ArgumentMatchers.anyString;
+
+public class WbListServiceHandlerTest {
+
+ public static final String VALUE = "value";
+ WbListServiceHandler wbListServiceHandler;
+
+ @Mock
+ ListRepository listRepository;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ wbListServiceHandler = new WbListServiceHandler(listRepository);
+ }
+
+ @Test
+ public void isExist() throws TException {
+ Row value = new Row();
+ value.setValue(VALUE);
+ Mockito.when(listRepository.get(anyString(), anyString())).thenReturn(Optional.of(value));
+ boolean exist = wbListServiceHandler.isExist("partyId", "shopId", "listName", "value");
+ Assert.assertTrue(exist);
+
+ Mockito.when(listRepository.get(anyString(), anyString())).thenReturn(Optional.empty());
+ exist = wbListServiceHandler.isExist("partyId", "shopId", "listName", "value");
+ Assert.assertFalse(exist);
+ }
+}
\ No newline at end of file
diff --git a/src/test/resources/app/start.sh b/src/test/resources/app/start.sh
new file mode 100644
index 0000000..39cb450
--- /dev/null
+++ b/src/test/resources/app/start.sh
@@ -0,0 +1,2 @@
+riak-admin bucket-type create type
+riak-admin bucket-type activate type
\ No newline at end of file
diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml
new file mode 100644
index 0000000..e1f57ed
--- /dev/null
+++ b/src/test/resources/logback-test.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+