diff --git a/.gitignore b/.gitignore index a1c2a238a..4fde0c05c 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,9 @@ # BlueJ files *.ctxt +# Idea folders +.idea + # Mobile Tools for Java (J2ME) .mtj.tmp/ diff --git a/Accountant/.gitignore b/Accountant/.gitignore new file mode 100644 index 000000000..797b705f6 --- /dev/null +++ b/Accountant/.gitignore @@ -0,0 +1 @@ +*.iml \ No newline at end of file diff --git a/Accountant/accountant-app/.gitignore b/Accountant/accountant-app/.gitignore new file mode 100644 index 000000000..e7c460346 --- /dev/null +++ b/Accountant/accountant-app/.gitignore @@ -0,0 +1,80 @@ +# Created by .ignore support plugin (hsz.mobi) +### Kotlin template +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +.idea/artifacts +.idea/compiler.xml +.idea/jarRepositories.xml +.idea/modules.xml +.idea/*.iml +.idea/modules +*.iml +*.ipr +.mvn/ + +# File-based project format +*.iws + +# IntelliJ +out/ + +target/ + +.DS_Store + + + + diff --git a/Accountant/accountant-app/Dockerfile b/Accountant/accountant-app/Dockerfile new file mode 100644 index 000000000..f2cbd4c26 --- /dev/null +++ b/Accountant/accountant-app/Dockerfile @@ -0,0 +1,5 @@ +FROM openjdk:8-jdk-alpine +VOLUME /tmp +ARG JAR_FILE=target/*.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"] \ No newline at end of file diff --git a/Accountant/accountant-app/pom.xml b/Accountant/accountant-app/pom.xml new file mode 100644 index 000000000..ade675037 --- /dev/null +++ b/Accountant/accountant-app/pom.xml @@ -0,0 +1,200 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.4 + + + co.nilin.opex + accountant-app + 1.0-SNAPSHOT + accountant-app + Accountant app Opex + + + 1.8 + 1.4.31 + ${version} + ${version} + + + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + com.fasterxml.jackson.module + jackson-module-kotlin + + + org.springframework.boot + spring-boot-starter + + + co.nilin.opex + accountant-core + ${accountant.version} + + + co.nilin.opex + accountant-eventlistener-kafka + ${accountant.version} + + + co.nilin.opex + accountant-submitter-kafka + ${accountant.version} + + + co.nilin.opex + accountant-persister-postgres + ${accountant.version} + + + co.nilin.opex + accountant-wallet-proxy + ${accountant.version} + + + co.nilin.opex + error-handler + ${utility.version} + + + co.nilin.opex + logging-handler + ${utility.version} + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + 2.18 + + + ${skip.unit.tests} + + + **/*IntegrationTest.java + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-test-source + generate-test-sources + + add-test-source + + + + src/test/java + + + + + compile + + add-source + + + + src/main/java + + + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + compile + + + + test-compile + test-compile + + test-compile + + + + + + -Xjsr305=strict + + + spring + + 1.8 + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + compile + compile + + compile + + + + testCompile + test-compile + + testCompile + + + + + + opex-accountant + + diff --git a/Accountant/accountant-app/src/main/kotlin/co/nilin/opex/app/AccountantApp.kt b/Accountant/accountant-app/src/main/kotlin/co/nilin/opex/app/AccountantApp.kt new file mode 100644 index 000000000..8b5c89f25 --- /dev/null +++ b/Accountant/accountant-app/src/main/kotlin/co/nilin/opex/app/AccountantApp.kt @@ -0,0 +1,15 @@ +package co.nilin.opex.app + +import co.nilin.opex.utility.error.EnableOpexErrorHandler +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication +import org.springframework.context.annotation.ComponentScan + +@SpringBootApplication +@ComponentScan("co.nilin.opex") +@EnableOpexErrorHandler +class AccountantApp + +fun main(args: Array) { + runApplication(*args) +} \ No newline at end of file diff --git a/Accountant/accountant-app/src/main/kotlin/co/nilin/opex/app/config/AppConfig.kt b/Accountant/accountant-app/src/main/kotlin/co/nilin/opex/app/config/AppConfig.kt new file mode 100644 index 000000000..1ddca4bdd --- /dev/null +++ b/Accountant/accountant-app/src/main/kotlin/co/nilin/opex/app/config/AppConfig.kt @@ -0,0 +1,238 @@ +package co.nilin.opex.app.config + +import co.nilin.opex.accountant.core.api.FinancialActionJobManager +import co.nilin.opex.accountant.core.api.OrderManager +import co.nilin.opex.accountant.core.api.TradeManager +import co.nilin.opex.accountant.core.service.FinancialActionJobManagerImpl +import co.nilin.opex.accountant.core.service.OrderManagerImpl +import co.nilin.opex.accountant.core.service.TradeManagerImpl +import co.nilin.opex.accountant.core.spi.* +import co.nilin.opex.matching.core.eventh.events.* +import co.nilin.opex.port.accountant.kafka.consumer.OrderKafkaListener +import co.nilin.opex.port.accountant.kafka.spi.OrderSubmitRequestListener +import co.nilin.opex.port.order.kafka.inout.OrderSubmitRequest +import co.nilin.opex.port.accountant.kafka.consumer.EventKafkaListener +import co.nilin.opex.port.accountant.kafka.consumer.TempEventKafkaListener +import co.nilin.opex.port.accountant.kafka.consumer.TradeKafkaListener +import co.nilin.opex.port.accountant.kafka.spi.EventListener +import co.nilin.opex.port.accountant.kafka.spi.TempEventListener +import co.nilin.opex.port.accountant.kafka.spi.TradeListener +import kotlinx.coroutines.runBlocking +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.scheduling.annotation.EnableScheduling + +@Configuration +@EnableScheduling +class AppConfig { + + @Bean + fun getFinancialActionJobManager( + financialActionLoader: FinancialActionLoader, + financialActionPersister: FinancialActionPersister, + walletProxy: WalletProxy + ): FinancialActionJobManager { + return FinancialActionJobManagerImpl( + financialActionLoader, + financialActionPersister, + walletProxy + ) + } + + @Bean + fun orderManager( + pairConfigLoader: PairConfigLoader, + financialActionPersister: FinancialActionPersister, + financeActionLoader: FinancialActionLoader, + orderPersister: OrderPersister, + tempEventPersister: TempEventPersister, + tempEventRepublisher: TempEventRepublisher, + richOrderPublisher: RichOrderPublisher + ): OrderManager { + return OrderManagerImpl( + pairConfigLoader, + financialActionPersister, + financeActionLoader, + orderPersister, + tempEventPersister, + tempEventRepublisher, + richOrderPublisher + ) + } + + @Bean + fun tradeManager( + pairStaticRateLoader: PairStaticRateLoader, + financeActionPersister: FinancialActionPersister, + financeActionLoader: FinancialActionLoader, + orderPersister: OrderPersister, + tempEventPersister: TempEventPersister, + richTradePublisher: RichTradePublisher, + walletProxy: WalletProxy, + @Value("\${app.coin}") platformCoin: String, + @Value("\${app.address}") platformAddress: String + ): TradeManager { + return TradeManagerImpl( + pairStaticRateLoader, + financeActionPersister, + financeActionLoader, + orderPersister, + tempEventPersister, + richTradePublisher, + walletProxy, + platformCoin, + platformAddress + ) + } + + @Bean + fun orderListener(orderManager: OrderManager): OrderListener { + return OrderListener(orderManager) + } + + @Bean + fun accountantTradeListener(tradeManager: TradeManager): AccountantTradeListener { + return AccountantTradeListener(tradeManager) + } + + @Bean + fun accountantEventListener( + orderManager: OrderManager + ): AccountantEventListener { + return AccountantEventListener(orderManager) + } + + @Bean + fun accountantTempEventListener( + orderManager: OrderManager, + tradeManager: TradeManager + ): AccountantTempEventListener { + return AccountantTempEventListener(orderManager, tradeManager) + } + + @Autowired + fun configureOrderListener(orderKafkaListener: OrderKafkaListener, orderListener: OrderListener) { + orderKafkaListener.addOrderListener(orderListener) + } + + @Autowired + fun configureTradeListener( + tradeKafkaListener: TradeKafkaListener, + accountantTradeListener: AccountantTradeListener + ) { + tradeKafkaListener.addTradeListener(accountantTradeListener) + } + + @Autowired + fun configureEventListener( + eventKafkaListener: EventKafkaListener, + accountantEventListener: AccountantEventListener + ) { + eventKafkaListener.addEventListener(accountantEventListener) + } + + @Autowired + fun configureTempEventListener( + tempEventKafkaListener: TempEventKafkaListener, + accountantTempEventListener: AccountantTempEventListener + ) { + tempEventKafkaListener.addEventListener(accountantTempEventListener) + } + + class OrderListener(val orderManager: OrderManager) : OrderSubmitRequestListener { + + override fun id(): String { + return "OrderListener" + } + + override fun onOrder(order: OrderSubmitRequest, partition: Int, offset: Long, timestamp: Long) { + runBlocking(AppDispatchers.kafkaExecutor) { + orderManager.handleRequestOrder( + SubmitOrderEvent( + order.ouid, + order.uuid, + order.orderId, + order.pair, + order.price, + order.quantity, + order.quantity, + order.direction, + order.matchConstraint, + order.orderType + ) + ) + } + } + } + + class AccountantTradeListener(val tradeManager: TradeManager) : TradeListener { + + override fun id(): String { + return "TradeListener" + } + + override fun onTrade(tradeEvent: TradeEvent, partition: Int, offset: Long, timestamp: Long) { + runBlocking(AppDispatchers.kafkaExecutor) { + tradeManager.handleTrade(tradeEvent) + } + } + } + + class AccountantEventListener( + val orderManager: OrderManager + ) : EventListener { + + override fun id(): String { + return "EventListener" + } + + override fun onEvent(coreEvent: CoreEvent, partition: Int, offset: Long, timestamp: Long) { + runBlocking(AppDispatchers.kafkaExecutor) { + if (coreEvent is CreateOrderEvent) + orderManager.handleNewOrder(coreEvent) + else if (coreEvent is RejectOrderEvent) + orderManager.handleRejectOrder(coreEvent) + else if (coreEvent is UpdatedOrderEvent) + orderManager.handleUpdateOrder(coreEvent) + else if (coreEvent is CancelOrderEvent) + orderManager.handleCancelOrder(coreEvent) + else { + println("Event is not accepted ${coreEvent::class.java}") + } + } + println("onEvent") + } + } + + class AccountantTempEventListener( + val orderManager: OrderManager, + val tradeManager: TradeManager + ) : TempEventListener { + + override fun id(): String { + return "TempEventListener" + } + + override fun onEvent(coreEvent: CoreEvent, partition: Int, offset: Long, timestamp: Long) { + println("TempEvent " + coreEvent) + runBlocking(AppDispatchers.kafkaExecutor) { + if (coreEvent is CreateOrderEvent) + orderManager.handleNewOrder(coreEvent) + else if (coreEvent is RejectOrderEvent) + orderManager.handleRejectOrder(coreEvent) + else if (coreEvent is UpdatedOrderEvent) + orderManager.handleUpdateOrder(coreEvent) + else if (coreEvent is CancelOrderEvent) + orderManager.handleCancelOrder(coreEvent) + else if (coreEvent is TradeEvent) + tradeManager.handleTrade(coreEvent) + else { + throw IllegalArgumentException("Event is not accepted ${coreEvent::class.java}") + } + } + println("onEvent") + } + } +} \ No newline at end of file diff --git a/Accountant/accountant-app/src/main/kotlin/co/nilin/opex/app/config/AppDispatchers.kt b/Accountant/accountant-app/src/main/kotlin/co/nilin/opex/app/config/AppDispatchers.kt new file mode 100644 index 000000000..a94b76fb9 --- /dev/null +++ b/Accountant/accountant-app/src/main/kotlin/co/nilin/opex/app/config/AppDispatchers.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.app.config + +import kotlinx.coroutines.asCoroutineDispatcher +import java.util.concurrent.Executors + +object AppDispatchers { + val kafkaExecutor = Executors.newSingleThreadExecutor().asCoroutineDispatcher() +} \ No newline at end of file diff --git a/Accountant/accountant-app/src/main/kotlin/co/nilin/opex/app/controller/AccountantController.kt b/Accountant/accountant-app/src/main/kotlin/co/nilin/opex/app/controller/AccountantController.kt new file mode 100644 index 000000000..7cb4ce65b --- /dev/null +++ b/Accountant/accountant-app/src/main/kotlin/co/nilin/opex/app/controller/AccountantController.kt @@ -0,0 +1,52 @@ +package co.nilin.opex.app.controller + +import co.nilin.opex.accountant.core.model.PairConfig +import co.nilin.opex.accountant.core.model.PairFeeConfig +import co.nilin.opex.accountant.core.spi.FinancialActionLoader +import co.nilin.opex.accountant.core.spi.PairConfigLoader +import co.nilin.opex.accountant.core.spi.WalletProxy +import co.nilin.opex.matching.core.eventh.events.SubmitOrderEvent +import co.nilin.opex.matching.core.model.OrderDirection +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController +import java.math.BigDecimal + +@RestController +class AccountantController( + val walletProxy: WalletProxy, + val financialActionLoader: FinancialActionLoader, + val pairConfigLoader: PairConfigLoader +) { + data class BooleanResponse(val result: Boolean) + + @GetMapping("{uuid}/create_order/{amount}_{currency}/allowed") + suspend fun canCreateOrder( + @PathVariable("uuid") uuid: String, + @PathVariable("currency") currency: String, + @PathVariable("amount") amount: BigDecimal + ): BooleanResponse { + return BooleanResponse( + financialActionLoader.countUnprocessed(uuid, currency, SubmitOrderEvent::class.simpleName!!) <= 0 + && walletProxy.canFulfil(currency, "main", uuid, amount) + ) + } + + @GetMapping( + value = ["/config/{pair}/fee/{direction}-{userLevel}", "/config/{pair}/fee/{direction}"] + ) + suspend fun fetchPairFeeConfig( + @PathVariable("pair") pair: String, + @PathVariable("direction") direction: OrderDirection, + @PathVariable("userLevel") level: String? + ): PairFeeConfig { + return pairConfigLoader.load(pair, direction, level ?: "") + } + + @GetMapping("/config/all") + suspend fun fetchPairConfigs():List{ + return pairConfigLoader.loadPairConfigs() + } + +} \ No newline at end of file diff --git a/Accountant/accountant-app/src/main/kotlin/co/nilin/opex/app/scheduler/FinancialActionsJob.kt b/Accountant/accountant-app/src/main/kotlin/co/nilin/opex/app/scheduler/FinancialActionsJob.kt new file mode 100644 index 000000000..d4e02be81 --- /dev/null +++ b/Accountant/accountant-app/src/main/kotlin/co/nilin/opex/app/scheduler/FinancialActionsJob.kt @@ -0,0 +1,28 @@ +package co.nilin.opex.app.scheduler + +import co.nilin.opex.accountant.core.api.FinancialActionJobManager +import kotlinx.coroutines.runBlocking +import org.slf4j.LoggerFactory +import org.springframework.scheduling.annotation.Scheduled +import org.springframework.stereotype.Service + +@Service +class FinancialActionsJob( + val financialActionJobManager: FinancialActionJobManager +) { + + private val log = LoggerFactory.getLogger(FinancialActionsJob::class.java) + + @Scheduled(fixedDelay = 10000) + fun processFinancialActions() { + runBlocking { + try { + //read unprocessed fa records and call transfer + financialActionJobManager.processFinancialActions(0, 100) + } catch (e: Exception) { + log.error("Job error!", e) + } + } + } + +} \ No newline at end of file diff --git a/Accountant/accountant-app/src/main/kotlin/co/nilin/opex/app/scheduler/TempEventsJob.kt b/Accountant/accountant-app/src/main/kotlin/co/nilin/opex/app/scheduler/TempEventsJob.kt new file mode 100644 index 000000000..6956089d2 --- /dev/null +++ b/Accountant/accountant-app/src/main/kotlin/co/nilin/opex/app/scheduler/TempEventsJob.kt @@ -0,0 +1,34 @@ +package co.nilin.opex.app.scheduler + +import co.nilin.opex.accountant.core.spi.TempEventPersister +import co.nilin.opex.accountant.core.spi.TempEventRepublisher +import kotlinx.coroutines.runBlocking +import org.slf4j.LoggerFactory +import org.springframework.scheduling.annotation.Scheduled +import org.springframework.stereotype.Service + +@Service +class TempEventsJob( + val tempEventPersister: TempEventPersister, + val tempEventRepublisher: TempEventRepublisher, +) { + + private val log = LoggerFactory.getLogger(TempEventsJob::class.java) + + @Scheduled(fixedDelay = 1000) + fun processTempEventJobs() { + runBlocking { + try { + //read unprocessed temp event and call republish + val tempEvents = tempEventPersister.fetchTempEvents(0, 100); + if (tempEvents.isNotEmpty()) { + tempEventRepublisher.republish(tempEvents.map { event -> event.eventBody }) + tempEventPersister.removeTempEvents(tempEvents) + } + } catch (e: Exception) { + log.error("Job error!", e) + } + } + } + +} \ No newline at end of file diff --git a/Accountant/accountant-app/src/main/resources/application-docker.yml b/Accountant/accountant-app/src/main/resources/application-docker.yml new file mode 100644 index 000000000..238e144fa --- /dev/null +++ b/Accountant/accountant-app/src/main/resources/application-docker.yml @@ -0,0 +1,12 @@ +spring: + kafka: + bootstrap-servers: ${KAFKA_IP_PORT} + redis: + host: ${REDIS_HOST} + r2dbc: + url: r2dbc:postgresql://${DB_IP_PORT}/opex_accountant + username: opex + password: hiopex + cloud: + consul: + host: ${CONSUL_HOST} diff --git a/Accountant/accountant-app/src/main/resources/application.yml b/Accountant/accountant-app/src/main/resources/application.yml new file mode 100644 index 000000000..aa238fa2f --- /dev/null +++ b/Accountant/accountant-app/src/main/resources/application.yml @@ -0,0 +1,37 @@ +server.port: 8089 +logging: + level: + co.nilin: DEBUG + reactor.netty.http.client: DEBUG +spring: + application: + name: opex-accountant + main: + allow-bean-definition-overriding: false + kafka: + bootstrap-servers: 192.168.178.29:9092 + consumer: + group-id: accountant + redis: + hostname: 127.0.0.1 + port: 6379 + r2dbc: + url: r2dbc:postgresql://localhost/opex_accountant + username: opex + password: hiopex + initialization-mode: always + cloud: + bootstrap: + enabled: true + consul: + port: 8500 + discovery: + #healthCheckPath: ${management.context-path}/health + instance-id: ${spring.application.name}:${server.port} + healthCheckInterval: 20s + prefer-ip-address: true +app: + coin: nln + address: 1 + wallet: + url: lb://opex-wallet/ diff --git a/Accountant/accountant-core/.gitignore b/Accountant/accountant-core/.gitignore new file mode 100644 index 000000000..f3a8317d6 --- /dev/null +++ b/Accountant/accountant-core/.gitignore @@ -0,0 +1,4 @@ +*.iml +target/ +.mvn/ +.idea/ \ No newline at end of file diff --git a/Accountant/accountant-core/mvnw b/Accountant/accountant-core/mvnw new file mode 100644 index 000000000..a16b5431b --- /dev/null +++ b/Accountant/accountant-core/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/Accountant/accountant-core/mvnw.cmd b/Accountant/accountant-core/mvnw.cmd new file mode 100644 index 000000000..c8d43372c --- /dev/null +++ b/Accountant/accountant-core/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/Accountant/accountant-core/pom.xml b/Accountant/accountant-core/pom.xml new file mode 100644 index 000000000..3f7cc6f20 --- /dev/null +++ b/Accountant/accountant-core/pom.xml @@ -0,0 +1,105 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.4 + + + co.nilin.opex + accountant-core + 1.0-SNAPSHOT + accountant-core + Accountant logic of Opex + + + 1.8 + 1.4.31 + ${version} + + + + + org.springframework.boot + spring-boot-starter + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + exchange.core2 + collections + 0.5.1 + + + + io.projectreactor.kotlin + reactor-kotlin-extensions + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + + + org.jetbrains.kotlinx + kotlinx-coroutines-core + + + + co.nilin.opex + matching-core + ${matching.version} + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + org.springframework + spring-tx + provided + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + -Xjsr305=strict + + + spring + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + + + diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/api/FinancialActionJobManager.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/api/FinancialActionJobManager.kt new file mode 100644 index 000000000..bb9c26062 --- /dev/null +++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/api/FinancialActionJobManager.kt @@ -0,0 +1,5 @@ +package co.nilin.opex.accountant.core.api + +interface FinancialActionJobManager { + suspend fun processFinancialActions(offset: Long, size: Long) +} diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/api/OrderManager.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/api/OrderManager.kt new file mode 100644 index 000000000..fb947ea8d --- /dev/null +++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/api/OrderManager.kt @@ -0,0 +1,12 @@ +package co.nilin.opex.accountant.core.api + +import co.nilin.opex.accountant.core.model.FinancialAction +import co.nilin.opex.matching.core.eventh.events.* + +interface OrderManager { + suspend fun handleRequestOrder(submitOrderEvent: SubmitOrderEvent): List + suspend fun handleNewOrder(createOrderEvent: CreateOrderEvent): List + suspend fun handleUpdateOrder(updatedOrderEvent: UpdatedOrderEvent): List + suspend fun handleRejectOrder(rejectOrderEvent: RejectOrderEvent): List + suspend fun handleCancelOrder(cancelOrderEvent: CancelOrderEvent): List +} \ No newline at end of file diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/api/TradeManager.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/api/TradeManager.kt new file mode 100644 index 000000000..003e4fca2 --- /dev/null +++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/api/TradeManager.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.accountant.core.api + +import co.nilin.opex.accountant.core.model.FinancialAction +import co.nilin.opex.matching.core.eventh.events.TradeEvent + +interface TradeManager { + suspend fun handleTrade(trade:TradeEvent): List +} \ No newline at end of file diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/inout/OrderStatus.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/inout/OrderStatus.kt new file mode 100644 index 000000000..b554691b6 --- /dev/null +++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/inout/OrderStatus.kt @@ -0,0 +1,40 @@ +package co.nilin.opex.accountant.core.inout + +enum class OrderStatus(val code: Int, private val orderOfAppearance: Int) { + + REQUESTED(0, 0), + NEW(1, 1), //The order has been accepted by the engine. + PARTIALLY_FILLED(4, 2), //A part of the order has been filled. + FILLED(5, 3), //The order has been completed. + CANCELED(2, 3), //The order has been canceled by the user. + REJECTED(3, 3), //The order was not accepted by the engine and not processed. + EXPIRED(6, 3); //The order was canceled according to the order type's rules (e.g. LIMIT FOK orders with no fill, LIMIT IOC or MARKET orders that partially fill) or by the exchange, (e.g. orders canceled during liquidation, orders canceled during maintenance) + + fun comesBefore(status: OrderStatus?): Boolean { + if (status == null) + return false + return orderOfAppearance < status.orderOfAppearance + } + + fun comesAfter(status: OrderStatus?): Boolean { + if (status == null) + return false + return orderOfAppearance > status.orderOfAppearance + } + + companion object { + fun fromCode(code: Int?): OrderStatus? { + if (code == null) + return null + return values().find { it.code == code } + } + } +} + +fun Int?.comesBefore(code: Int?): Boolean { + return OrderStatus.fromCode(this)?.comesBefore(OrderStatus.fromCode(code)) == true +} + +fun Int?.comesAfter(code: Int?): Boolean { + return OrderStatus.fromCode(this)?.comesAfter(OrderStatus.fromCode(code)) == true +} \ No newline at end of file diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/inout/RichOrder.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/inout/RichOrder.kt new file mode 100644 index 000000000..dd5a98893 --- /dev/null +++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/inout/RichOrder.kt @@ -0,0 +1,67 @@ +package co.nilin.opex.accountant.core.inout + +import co.nilin.opex.matching.core.model.MatchConstraint +import co.nilin.opex.matching.core.model.OrderDirection +import co.nilin.opex.matching.core.model.OrderType +import java.math.BigDecimal + +class RichOrder() { + var orderId: Long? = 0 + lateinit var pair: String + lateinit var ouid: String + lateinit var uuid: String + lateinit var userLevel: String + lateinit var makerFee: BigDecimal + lateinit var takerFee: BigDecimal + lateinit var leftSideFraction: BigDecimal + lateinit var rightSideFraction: BigDecimal + lateinit var direction: OrderDirection + lateinit var constraint: MatchConstraint + lateinit var type: OrderType + lateinit var price: BigDecimal; + lateinit var quantity: BigDecimal; + lateinit var executedQuantity: BigDecimal; + lateinit var accumulativeQuoteQty: BigDecimal; + lateinit var quoteQuantity: BigDecimal; + var status: Int = 0; + + constructor( + orderId: Long?, + pair: String, + ouid: String, + uuid: String, + userLevel: String, + makerFee: BigDecimal, + takerFee: BigDecimal, + leftSideFraction: BigDecimal, + rightSideFraction: BigDecimal, + direction: OrderDirection, + constraint: MatchConstraint, + type: OrderType, + price: BigDecimal, + quantity: BigDecimal, + quoteQuantity: BigDecimal, + executedQuantity: BigDecimal, + accumulativeQuoteQty: BigDecimal, + status: Int + ) : this() { + this.orderId = orderId + this.pair = pair + this.ouid = ouid + this.uuid = uuid + this.userLevel = userLevel + this.makerFee = makerFee + this.takerFee = takerFee + this.leftSideFraction = leftSideFraction + this.rightSideFraction = rightSideFraction + this.direction = direction + this.constraint = constraint + this.type = type + this.price = price + this.quantity = quantity + this.executedQuantity = executedQuantity + this.accumulativeQuoteQty = accumulativeQuoteQty + this.quoteQuantity = quoteQuantity + this.status = status + } +} diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/inout/RichTrade.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/inout/RichTrade.kt new file mode 100644 index 000000000..964b5b5e1 --- /dev/null +++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/inout/RichTrade.kt @@ -0,0 +1,85 @@ +package co.nilin.opex.accountant.core.inout + +import co.nilin.opex.matching.core.model.OrderDirection +import java.math.BigDecimal +import java.time.LocalDateTime + +class RichTrade() { + var id: Long = 0 + lateinit var pair: String + lateinit var takerOuid: String + lateinit var takerUuid: String + var takerOrderId: Long = 0 + lateinit var takerDirection: OrderDirection + lateinit var takerPrice: BigDecimal + lateinit var takerQuantity: BigDecimal + lateinit var takerQuoteQuantity: BigDecimal + lateinit var takerRemainedQuantity: BigDecimal + lateinit var takerCommision: BigDecimal + lateinit var takerCommisionAsset: String + lateinit var makerOuid: String + lateinit var makerUuid: String + var makerOrderId: Long = 0 + lateinit var makerDirection: OrderDirection + lateinit var makerPrice: BigDecimal + lateinit var makerQuantity: BigDecimal + lateinit var makerQuoteQuantity: BigDecimal + lateinit var makerRemainedQuantity: BigDecimal + lateinit var matchedQuantity: BigDecimal + lateinit var makerCommision: BigDecimal + lateinit var makerCommisionAsset: String + + lateinit var tradeDateTime: LocalDateTime + + constructor( + id: Long, + pair: String, + takerOuid: String, + takerUuid: String, + takerOrderId: Long, + takerDirection: OrderDirection, + takerPrice: BigDecimal, + takerQuantity: BigDecimal, + takerQuoteQuantity: BigDecimal, + takerRemainedQuantity: BigDecimal, + takerCommision: BigDecimal, + takerCommisionAsset: String, + makerOuid: String, + makerUuid: String, + makerOrderId: Long, + makerDirection: OrderDirection, + makerPrice: BigDecimal, + makerQuantity: BigDecimal, + makerQuoteQuantity: BigDecimal, + makerRemainedQuantity: BigDecimal, + makerCommision: BigDecimal, + makerCommisionAsset: String, + matchedQuantity: BigDecimal, + tradeDateTime: LocalDateTime + ) : this() { + this.id = id + this.pair = pair + this.takerOuid = takerOuid + this.takerUuid = takerUuid + this.takerOrderId = takerOrderId + this.takerDirection = takerDirection + this.takerPrice = takerPrice + this.takerQuantity = takerQuantity + this.takerQuoteQuantity = takerQuoteQuantity + this.takerRemainedQuantity = takerRemainedQuantity + this.takerCommision = takerCommision + this.takerCommisionAsset = takerCommisionAsset + this.makerOuid = makerOuid + this.makerUuid = makerUuid + this.makerOrderId = makerOrderId + this.makerDirection = makerDirection + this.makerPrice = makerPrice + this.makerQuantity = makerQuantity + this.makerQuoteQuantity = makerQuoteQuantity + this.makerRemainedQuantity = makerRemainedQuantity + this.matchedQuantity = matchedQuantity + this.makerCommision = makerCommision + this.makerCommisionAsset = makerCommisionAsset + this.tradeDateTime = tradeDateTime + } +} \ No newline at end of file diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/FinancialAction.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/FinancialAction.kt new file mode 100644 index 000000000..e6f57e4d7 --- /dev/null +++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/FinancialAction.kt @@ -0,0 +1,41 @@ +package co.nilin.opex.accountant.core.model + +import java.math.BigDecimal +import java.time.LocalDateTime + +class FinancialAction( + val id: Long? = null, + val parent: FinancialAction?, + val eventType: String, + val pointer: String, + val symbol: String, + val amount: BigDecimal, + val sender: String, + val senderWalletType: String, + val receiver: String, + val receiverWalletType: String, + val createDate: LocalDateTime +) { + constructor( + parent: FinancialAction?, + eventType: String, + pointer: String, + symbol: String, + amount: BigDecimal, + sender: String, + senderWalletType: String, + receiver: String, + receiverWalletType: String, + createDate: LocalDateTime + ) : this(null, parent, eventType, pointer, symbol, amount, sender, senderWalletType, receiver, receiverWalletType, createDate) + + override fun toString(): String { + return "FinancialAction(id=$id, parent=$parent, eventType='$eventType', pointer='$pointer', symbol='$symbol', amount=$amount, sender='$sender', senderWalletType='$senderWalletType', receiver='$receiver', receiverWalletType='$receiverWalletType', createDate=$createDate)" + } + + +} + +enum class FinancialActionStatus { + CREATED, PROCESSED, ERROR +} \ No newline at end of file diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/Order.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/Order.kt new file mode 100644 index 000000000..ca311f1a6 --- /dev/null +++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/Order.kt @@ -0,0 +1,31 @@ +package co.nilin.opex.accountant.core.model + +import co.nilin.opex.matching.core.model.MatchConstraint +import co.nilin.opex.matching.core.model.OrderDirection +import co.nilin.opex.matching.core.model.OrderType +import java.math.BigDecimal + +data class Order( + val pair: String, + val ouid: String, + var matchingEngineId: Long?, + val makerFee: Double, + val takerFee: Double, + val leftSideFraction: Double, + val rightSideFraction: Double, + val uuid: String, + val userLevel: String, + val direction: OrderDirection, + val matchConstraint: MatchConstraint, + val orderType: OrderType, + val price: Long, + val quantity: Long, + val filledQuantity: Long, + val origPrice: BigDecimal, + val origQuantity: BigDecimal, + val filledOrigQuantity: BigDecimal, + val firstTransferAmount: BigDecimal, + var remainedTransferAmount: BigDecimal, + var status: Int, + val id: Long? = null +) \ No newline at end of file diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/PairConfig.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/PairConfig.kt new file mode 100644 index 000000000..6df4d4e82 --- /dev/null +++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/PairConfig.kt @@ -0,0 +1,9 @@ +package co.nilin.opex.accountant.core.model + +class PairConfig( + val pair: String, + val leftSideWalletSymbol: String, //can be same as pair left side + val rightSideWalletSymbol: String, //can be same as pair right side + val leftSideFraction: Double, + val rightSideFraction: Double +) \ No newline at end of file diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/PairFeeConfig.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/PairFeeConfig.kt new file mode 100644 index 000000000..ba8e3fbf8 --- /dev/null +++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/PairFeeConfig.kt @@ -0,0 +1,9 @@ +package co.nilin.opex.accountant.core.model + +class PairFeeConfig( + val pairConfig: PairConfig, + val direction: String?, + val userLevel: String?, + val makerFee: Double, + val takerFee: Double +) \ No newline at end of file diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/TempEvent.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/TempEvent.kt new file mode 100644 index 000000000..144cddf19 --- /dev/null +++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/TempEvent.kt @@ -0,0 +1,6 @@ +package co.nilin.opex.accountant.core.model + +import co.nilin.opex.matching.core.eventh.events.CoreEvent +import java.time.LocalDateTime + +data class TempEvent(val id: Long, val ouid: String, val eventBody: CoreEvent, val eventDate: LocalDateTime) \ No newline at end of file diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/service/FinancialActionJobManagerImpl.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/service/FinancialActionJobManagerImpl.kt new file mode 100644 index 000000000..b2c9faf6c --- /dev/null +++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/service/FinancialActionJobManagerImpl.kt @@ -0,0 +1,39 @@ +package co.nilin.opex.accountant.core.service + +import co.nilin.opex.accountant.core.api.FinancialActionJobManager +import co.nilin.opex.accountant.core.model.FinancialActionStatus +import co.nilin.opex.accountant.core.spi.FinancialActionLoader +import co.nilin.opex.accountant.core.spi.FinancialActionPersister +import co.nilin.opex.accountant.core.spi.WalletProxy +import org.slf4j.LoggerFactory + +class FinancialActionJobManagerImpl( + val financialActionLoader: FinancialActionLoader, + val financialActionPersister: FinancialActionPersister, + val walletProxy: WalletProxy +) : FinancialActionJobManager { + + private val log = LoggerFactory.getLogger(FinancialActionJobManagerImpl::class.java) + + override suspend fun processFinancialActions(offset: Long, size: Long) { + val factions = financialActionLoader.loadUnprocessed(offset, size) + factions.forEach { faction -> + try { + walletProxy.transfer( + faction.symbol, + faction.senderWalletType, + faction.sender, + faction.receiverWalletType, + faction.receiver, + faction.amount, + faction.eventType + faction.pointer, + null + ) + financialActionPersister.updateStatus(faction, FinancialActionStatus.PROCESSED) + } catch (e: Exception) { + log.error("financial job error", e) + financialActionPersister.updateStatus(faction, FinancialActionStatus.ERROR) + } + } + } +} \ No newline at end of file diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/service/OrderManagerImpl.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/service/OrderManagerImpl.kt new file mode 100644 index 000000000..06638663f --- /dev/null +++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/service/OrderManagerImpl.kt @@ -0,0 +1,220 @@ +package co.nilin.opex.accountant.core.service + +import co.nilin.opex.accountant.core.api.OrderManager +import co.nilin.opex.accountant.core.inout.OrderStatus +import co.nilin.opex.accountant.core.inout.RichOrder +import co.nilin.opex.accountant.core.model.FinancialAction +import co.nilin.opex.accountant.core.model.Order +import co.nilin.opex.accountant.core.spi.* +import co.nilin.opex.matching.core.eventh.events.* +import co.nilin.opex.matching.core.model.OrderDirection +import org.springframework.transaction.annotation.Transactional +import java.math.BigDecimal +import java.time.LocalDateTime + +open class OrderManagerImpl( + val pairConfigLoader: PairConfigLoader, + val financialActionPersister: FinancialActionPersister, + val financeActionLoader: FinancialActionLoader, + val orderPersister: OrderPersister, + val tempEventPersister: TempEventPersister, + val tempEventRepublisher: TempEventRepublisher, + val richOrderPublisher: RichOrderPublisher +) : OrderManager { + + @Transactional + override suspend fun handleRequestOrder(submitOrderEvent: SubmitOrderEvent): List { + //pair + dir -> symbol + //user level? + //pair config.makerFee and takerFee + val symbol = if (submitOrderEvent.direction == OrderDirection.ASK) { + submitOrderEvent.pair.leftSideName + } else { + submitOrderEvent.pair.rightSideName + } + val pairFeeConfig = pairConfigLoader.load(submitOrderEvent.pair.toString(), submitOrderEvent.direction, "") + val makerFee = pairFeeConfig.makerFee * 1 //user level formula + val takerFee = pairFeeConfig.takerFee * 1 //user level formula + + //create fa for transfer uuid symbol main wallet to uuid symbol exchange wallet + /* + amount for sell (ask): quantity + amount for buy (bid): quantity * price + */ + val financialAction = + FinancialAction( + null, + SubmitOrderEvent::class.simpleName!!, + submitOrderEvent.ouid, + symbol, + if (submitOrderEvent.direction == OrderDirection.ASK) { + BigDecimal(submitOrderEvent.quantity).multiply(pairFeeConfig.pairConfig.leftSideFraction.toBigDecimal()) + } else { + BigDecimal(submitOrderEvent.quantity).multiply(pairFeeConfig.pairConfig.leftSideFraction.toBigDecimal()) + .multiply(submitOrderEvent.price.toBigDecimal()) + .multiply(pairFeeConfig.pairConfig.rightSideFraction.toBigDecimal()) + }, + submitOrderEvent.uuid, + "main", + submitOrderEvent.uuid, + "exchange", + LocalDateTime.now() + ) + //store order (ouid, uuid, fees, userlevel, pair, direction, price, quantity, filledQ, status, transfered) + orderPersister.save( + Order( + submitOrderEvent.pair.toString(), + submitOrderEvent.ouid, + null, + makerFee, + takerFee, + pairFeeConfig.pairConfig.leftSideFraction, + pairFeeConfig.pairConfig.rightSideFraction, + submitOrderEvent.uuid, + "", + submitOrderEvent.direction, + submitOrderEvent.matchConstraint, + submitOrderEvent.orderType, + submitOrderEvent.price, + submitOrderEvent.quantity, + submitOrderEvent.quantity - submitOrderEvent.remainedQuantity, + submitOrderEvent.price.toBigDecimal() + .multiply(pairFeeConfig.pairConfig.rightSideFraction.toBigDecimal()), + submitOrderEvent.quantity.toBigDecimal() + .multiply(pairFeeConfig.pairConfig.leftSideFraction.toBigDecimal()), + BigDecimal(submitOrderEvent.quantity - submitOrderEvent.remainedQuantity).multiply(pairFeeConfig.pairConfig.leftSideFraction.toBigDecimal()), + financialAction.amount, + financialAction.amount, + OrderStatus.REQUESTED.code + ) + ) + return financialActionPersister.persist(listOf(financialAction)) + } + + @Transactional + override suspend fun handleNewOrder(createOrderEvent: CreateOrderEvent): List { + //update order add id to other fields + val order = orderPersister.load(createOrderEvent.ouid) + if (order != null) { + order.matchingEngineId = createOrderEvent.orderId + orderPersister.save(order) + //new order accepted by engine + publishRichOrder(order, createOrderEvent.remainedQuantity.toBigDecimal()) + } else { + tempEventPersister.saveTempEvent(createOrderEvent.ouid, createOrderEvent) + } + return emptyList() + } + + private suspend fun publishRichOrder( + order: Order, remainedQuantity: BigDecimal, status: OrderStatus? = null + ) { + richOrderPublisher.publish( + RichOrder( + order.id, + order.pair, + order.ouid, + order.uuid, + order.userLevel, + order.makerFee.toBigDecimal(), + order.takerFee.toBigDecimal(), + order.leftSideFraction.toBigDecimal(), + order.rightSideFraction.toBigDecimal(), + order.direction, + order.matchConstraint, + order.orderType, + order.origPrice, + order.origQuantity, + order.origPrice.multiply(order.origQuantity), + order.quantity.toBigDecimal().subtract(remainedQuantity) + .multiply(order.leftSideFraction.toBigDecimal()), + order.origPrice.multiply( + order.quantity.toBigDecimal().subtract(remainedQuantity) + ), + status?.code ?: if (remainedQuantity.compareTo(BigDecimal.ZERO) == 0) { + OrderStatus.FILLED.code + } else if (remainedQuantity.compareTo(order.quantity.toBigDecimal()) == 0) { + OrderStatus.NEW.code + } else { + OrderStatus.PARTIALLY_FILLED.code + } + ) + ) + } + + override suspend fun handleUpdateOrder(updatedOrderEvent: UpdatedOrderEvent): List { + TODO("Not yet implemented") + } + + @Transactional + override suspend fun handleRejectOrder(rejectOrderEvent: RejectOrderEvent): List { + //order by ouid + val order = orderPersister.load(rejectOrderEvent.ouid) + if (order == null) { + tempEventPersister.saveTempEvent(rejectOrderEvent.ouid, rejectOrderEvent) + return emptyList() + } + val symbol = if (rejectOrderEvent.direction == OrderDirection.ASK) { + rejectOrderEvent.pair.leftSideName + } else { + rejectOrderEvent.pair.rightSideName + } + //check uuid + //lookup for parent fa + val parentFinancialAction = financeActionLoader.findLast(rejectOrderEvent.uuid, rejectOrderEvent.ouid) + //create fa for transfer remaining transfered uuid symbol exchange wallet to uuid main exchange wallet + val financialAction = FinancialAction( + parentFinancialAction, + RejectOrderEvent::class.simpleName!!, + rejectOrderEvent.ouid, + symbol, + order.remainedTransferAmount, + rejectOrderEvent.uuid, + "exchange", + rejectOrderEvent.uuid, + "main", + LocalDateTime.now() + ) + //update order status + order.status = OrderStatus.REJECTED.code + orderPersister.save(order) + publishRichOrder(order, order.quantity.toBigDecimal(), OrderStatus.REJECTED) + return financialActionPersister.persist(listOf(financialAction)) + } + + @Transactional + override suspend fun handleCancelOrder(cancelOrderEvent: CancelOrderEvent): List { + //order by ouid + val order = orderPersister.load(cancelOrderEvent.ouid) + if (order == null) { + tempEventPersister.saveTempEvent(cancelOrderEvent.ouid, cancelOrderEvent) + return emptyList() + } + val symbol = if (cancelOrderEvent.direction == OrderDirection.ASK) { + cancelOrderEvent.pair.leftSideName + } else { + cancelOrderEvent.pair.rightSideName + } + //check uuid + //lookup for parent fa + val parentFinancialAction = financeActionLoader.findLast(cancelOrderEvent.uuid, cancelOrderEvent.ouid) + //create fa for transfer remaining transfered uuid symbol exchange wallet to uuid main exchange wallet + val financialAction = FinancialAction( + parentFinancialAction, + RejectOrderEvent::class.simpleName!!, + cancelOrderEvent.ouid, + symbol, + order.remainedTransferAmount, + cancelOrderEvent.uuid, + "exchange", + cancelOrderEvent.uuid, + "main", + LocalDateTime.now() + ) + //update order status + order.status = OrderStatus.CANCELED.code + orderPersister.save(order) + publishRichOrder(order, cancelOrderEvent.quantity.toBigDecimal(), OrderStatus.CANCELED) + return financialActionPersister.persist(listOf(financialAction)) + } +} \ No newline at end of file diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/service/TradeManagerImpl.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/service/TradeManagerImpl.kt new file mode 100644 index 000000000..bf24dfa7d --- /dev/null +++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/service/TradeManagerImpl.kt @@ -0,0 +1,266 @@ +package co.nilin.opex.accountant.core.service + +import co.nilin.opex.accountant.core.api.TradeManager +import co.nilin.opex.accountant.core.inout.RichTrade +import co.nilin.opex.accountant.core.model.FinancialAction +import co.nilin.opex.accountant.core.spi.* +import co.nilin.opex.matching.core.eventh.events.TradeEvent +import co.nilin.opex.matching.core.model.OrderDirection +import org.slf4j.LoggerFactory +import org.springframework.transaction.annotation.Transactional +import java.math.BigDecimal +import java.time.LocalDateTime + +open class TradeManagerImpl( + val pairStaticRateLoader: PairStaticRateLoader, + val financeActionPersister: FinancialActionPersister, + val financeActionLoader: FinancialActionLoader, + val orderPersister: OrderPersister, + val tempEventPersister: TempEventPersister, + val richTradePublisher: RichTradePublisher, + val walletProxy: WalletProxy, + val platformCoin: String, + val platformAddress: String +) : TradeManager { + + private val log = LoggerFactory.getLogger(TradeManagerImpl::class.java) + + @Transactional + override suspend fun handleTrade(trade: TradeEvent): List { + log.info("trade event started {}", trade) + val financialActions = mutableListOf() + //taker order by ouid + val takerOrder = orderPersister.load(trade.takerOuid) + //maker order by ouid + val makerOrder = orderPersister.load(trade.makerOuid) + if (takerOrder == null || makerOrder == null) { + if (takerOrder == null) { + tempEventPersister.saveTempEvent(trade.takerOuid, trade) + } + if (makerOrder == null) { + tempEventPersister.saveTempEvent(trade.makerOuid, trade) + } + return emptyList() + } + //check taker uuid + // + //check maker uuid + // + val leftSidePCRate = pairStaticRateLoader.calculateStaticRate(platformCoin, trade.pair.leftSideName) + val rightSidePCRate = pairStaticRateLoader.calculateStaticRate(platformCoin, trade.pair.rightSideName) + + val takerMatchedAmount = if (takerOrder.direction == OrderDirection.ASK) { + trade.matchedQuantity.toBigDecimal().multiply(takerOrder.leftSideFraction.toBigDecimal()) + } else { + trade.matchedQuantity.toBigDecimal().multiply(takerOrder.leftSideFraction.toBigDecimal()) + .multiply(trade.makerPrice.toBigDecimal()).multiply(takerOrder.rightSideFraction.toBigDecimal()) + } + + val takerPCFeeCoefficient: Double = if (takerOrder.direction == OrderDirection.ASK) { + leftSidePCRate ?: 0.0 + } else { + rightSidePCRate ?: 0.0 + } + + val makerMatchedAmount = if (makerOrder.direction == OrderDirection.ASK) { + trade.matchedQuantity.toBigDecimal().multiply(makerOrder.leftSideFraction.toBigDecimal()) + } else { + trade.matchedQuantity.toBigDecimal().multiply(makerOrder.leftSideFraction.toBigDecimal()) + .multiply(trade.takerPrice.toBigDecimal()).multiply(makerOrder.rightSideFraction.toBigDecimal()) + } + + val makerPCFeeCoefficient: Double = if (makerOrder.direction == OrderDirection.ASK) { + leftSidePCRate ?: 0.0 + } else { + rightSidePCRate ?: 0.0 + } + log.info("trade event configs loaded ") + //lookup for taker parent fa + val takerParentFinancialAction = financeActionLoader.findLast(trade.takerUuid, trade.takerOuid) + log.info("trade event takerParentFinancialAction {} ", takerParentFinancialAction) + //lookup for maker parent fa + val makerParentFinancialAction = financeActionLoader.findLast(trade.makerUuid, trade.makerOuid) + log.info("trade event makerParentFinancialAction {} ", makerParentFinancialAction) + + + //calculate maker fee + val makerFee = makerOrder.makerFee + val makerTotalFeeWithPlatformCoin = takerMatchedAmount + .multiply(makerFee.toBigDecimal()) + .multiply(makerPCFeeCoefficient.toBigDecimal()) + //check if maker uuid can pay the fee with platform coin + //create fa for transfer taker uuid symbol exchange wallet to maker symbol main wallet + /* + amount for sell (ask): match_quantity (if not pay by platform coin then - maker fee) + amount for buy (bid): match_quantity * maker price (if not pay by platform coin then - maker fee) + */ + val takerTransferAction = FinancialAction( + takerParentFinancialAction, + TradeEvent::class.simpleName!!, + trade.takerOuid, + if (takerOrder.direction == OrderDirection.ASK) { + trade.pair.leftSideName + } else { + trade.pair.rightSideName + }, + takerMatchedAmount, + trade.takerUuid, + "exchange", + trade.makerUuid, + "main", + LocalDateTime.now() + ) + log.info("trade event takerTransferAction {}", takerTransferAction) + financialActions.add(takerTransferAction) + val makerFeeAction = if (makerTotalFeeWithPlatformCoin > BigDecimal.ZERO && + walletProxy.canFulfil(platformCoin, "main", trade.makerUuid, makerTotalFeeWithPlatformCoin) + ) { + FinancialAction( + makerParentFinancialAction, + TradeEvent::class.simpleName!!, + trade.takerOuid, + platformCoin, + makerTotalFeeWithPlatformCoin, + trade.makerUuid, + "main", + platformAddress, + "exchange", + LocalDateTime.now() + ) + } else { + FinancialAction( + makerParentFinancialAction, + TradeEvent::class.simpleName!!, + trade.takerOuid, + if (takerOrder.direction == OrderDirection.ASK) { + trade.pair.leftSideName + } else { + trade.pair.rightSideName + }, + takerMatchedAmount.multiply(makerFee.toBigDecimal()), + trade.makerUuid, + "main", + platformAddress, + "exchange", + LocalDateTime.now() + ) + } + log.info("trade event makerFeeAction {}", makerFeeAction) + financialActions.add(makerFeeAction) + //update taker order status + takerOrder.remainedTransferAmount -= takerMatchedAmount + if (takerOrder.filledQuantity == takerOrder.quantity) { + takerOrder.status = 1 + } + orderPersister.save(takerOrder) + log.info("taker order saved {}", takerOrder) + + //calculate taker fee + val takerFee = takerOrder.takerFee + val takerTotalFeeWithPlatformCoin = takerMatchedAmount + .multiply(takerFee.toBigDecimal()) + .multiply(takerPCFeeCoefficient.toBigDecimal()) + + //create fa for transfer makeruuid symbol exchange wallet to taker symbol main wallet + /* + amount for sell (ask): match_quantity (if not pay by platform coin then - taker fee) + amount for buy (bid): match_quantity * maker price (if not pay by platform coin then - taker fee) + */ + val makerTransferAction = FinancialAction( + makerParentFinancialAction, + TradeEvent::class.simpleName!!, + trade.makerOuid, + if (makerOrder.direction == OrderDirection.ASK) { + trade.pair.leftSideName + } else { + trade.pair.rightSideName + }, + makerMatchedAmount, + trade.makerUuid, + "exchange", + trade.takerUuid, + "main", + LocalDateTime.now() + ) + log.info("trade event makerTransferAction {}", makerTransferAction) + financialActions.add(makerTransferAction) + //check if taker uuid can pay the fee with platform coin + val takerFeeAction = if (takerTotalFeeWithPlatformCoin > BigDecimal.ZERO && + walletProxy.canFulfil(platformCoin, "main", trade.takerUuid, takerTotalFeeWithPlatformCoin) + ) { + FinancialAction( + takerParentFinancialAction, + TradeEvent::class.simpleName!!, + trade.makerOuid, + if (makerOrder.direction == OrderDirection.ASK) { + trade.pair.leftSideName + } else { + trade.pair.rightSideName + }, + takerTotalFeeWithPlatformCoin, + trade.takerUuid, + "main", + platformAddress, + "", + LocalDateTime.now() + ) + } else { + FinancialAction( + takerParentFinancialAction, + TradeEvent::class.simpleName!!, + trade.makerOuid, + if (makerOrder.direction == OrderDirection.ASK) { + trade.pair.leftSideName + } else { + trade.pair.rightSideName + }, + makerMatchedAmount.multiply(takerFee.toBigDecimal()), + trade.takerUuid, + "main", + platformAddress, + "exchange", + LocalDateTime.now() + ) + + } + log.info("trade event takerFeeAction {}", takerFeeAction) + financialActions.add(takerFeeAction) + //update maker order status + makerOrder.remainedTransferAmount -= makerMatchedAmount + if (makerOrder.filledQuantity == makerOrder.quantity) { + makerOrder.status = 1 + } + orderPersister.save(makerOrder) + log.info("maker order saved {}", makerOrder) + richTradePublisher.publish( + co.nilin.opex.accountant.core.inout.RichTrade( + trade.tradeId, + trade.pair.toString(), + trade.takerOuid, + trade.takerUuid, + trade.takerOrderId, + trade.takerDirection, + trade.takerPrice.toBigDecimal().multiply(takerOrder.rightSideFraction.toBigDecimal()), + takerOrder.origQuantity, + takerOrder.origPrice.multiply(takerOrder.origQuantity), + trade.takerRemainedQuantity.toBigDecimal().multiply(takerOrder.leftSideFraction.toBigDecimal()), + takerFeeAction.amount, + takerFeeAction.symbol, + trade.makerOuid, + trade.makerUuid, + trade.makerOrderId, + trade.makerDirection, + trade.makerPrice.toBigDecimal().multiply(makerOrder.rightSideFraction.toBigDecimal()), + makerOrder.origQuantity, + makerOrder.origPrice.multiply(makerOrder.origQuantity), + trade.makerRemainedQuantity.toBigDecimal().multiply(makerOrder.leftSideFraction.toBigDecimal()), + makerFeeAction.amount, + makerFeeAction.symbol, + trade.matchedQuantity.toBigDecimal().multiply(makerOrder.leftSideFraction.toBigDecimal()), + trade.eventDate + ) + + ) + return financeActionPersister.persist(financialActions) + } +} \ No newline at end of file diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/FinancialActionLoader.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/FinancialActionLoader.kt new file mode 100644 index 000000000..98721879d --- /dev/null +++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/FinancialActionLoader.kt @@ -0,0 +1,10 @@ +package co.nilin.opex.accountant.core.spi + +import co.nilin.opex.accountant.core.model.FinancialAction +import kotlinx.coroutines.flow.Flow + +interface FinancialActionLoader { + suspend fun findLast(uuid: String, ouid: String): FinancialAction? + suspend fun loadUnprocessed(offset: Long, size: Long): List + suspend fun countUnprocessed(uuid: String, symbol: String, eventType: String): Long +} \ No newline at end of file diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/FinancialActionPersister.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/FinancialActionPersister.kt new file mode 100644 index 000000000..f51e4eef9 --- /dev/null +++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/FinancialActionPersister.kt @@ -0,0 +1,9 @@ +package co.nilin.opex.accountant.core.spi + +import co.nilin.opex.accountant.core.model.FinancialAction +import co.nilin.opex.accountant.core.model.FinancialActionStatus + +interface FinancialActionPersister { + suspend fun persist(financialActions: List): List + suspend fun updateStatus(financialAction: FinancialAction, status: FinancialActionStatus) +} \ No newline at end of file diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/OrderPersister.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/OrderPersister.kt new file mode 100644 index 000000000..a3ae68637 --- /dev/null +++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/OrderPersister.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.accountant.core.spi + +import co.nilin.opex.accountant.core.model.Order + +interface OrderPersister { + suspend fun load(ouid: String): Order? + suspend fun save(order: Order): Order +} \ No newline at end of file diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/PairConfigLoader.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/PairConfigLoader.kt new file mode 100644 index 000000000..22a9c2518 --- /dev/null +++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/PairConfigLoader.kt @@ -0,0 +1,12 @@ +package co.nilin.opex.accountant.core.spi + +import co.nilin.opex.accountant.core.model.PairConfig +import co.nilin.opex.accountant.core.model.PairFeeConfig +import co.nilin.opex.matching.core.model.OrderDirection + +interface PairConfigLoader { + + suspend fun loadPairConfigs(): List + + suspend fun load(pair: String, direction: OrderDirection, userLevel: String): PairFeeConfig +} \ No newline at end of file diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/PairStaticRateLoader.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/PairStaticRateLoader.kt new file mode 100644 index 000000000..3708a0e18 --- /dev/null +++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/PairStaticRateLoader.kt @@ -0,0 +1,5 @@ +package co.nilin.opex.accountant.core.spi + +interface PairStaticRateLoader { + suspend fun calculateStaticRate(leftSide:String, rightSide: String): Double? +} \ No newline at end of file diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/RichOrderPublisher.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/RichOrderPublisher.kt new file mode 100644 index 000000000..ae6866675 --- /dev/null +++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/RichOrderPublisher.kt @@ -0,0 +1,7 @@ +package co.nilin.opex.accountant.core.spi + +import co.nilin.opex.accountant.core.inout.RichOrder + +interface RichOrderPublisher { + suspend fun publish(order: RichOrder) +} \ No newline at end of file diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/RichTradePublisher.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/RichTradePublisher.kt new file mode 100644 index 000000000..da9f860d3 --- /dev/null +++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/RichTradePublisher.kt @@ -0,0 +1,7 @@ +package co.nilin.opex.accountant.core.spi + +import co.nilin.opex.accountant.core.inout.RichTrade + +interface RichTradePublisher { + suspend fun publish(trade: RichTrade) +} \ No newline at end of file diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/TempEventPersister.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/TempEventPersister.kt new file mode 100644 index 000000000..a949f1c95 --- /dev/null +++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/TempEventPersister.kt @@ -0,0 +1,12 @@ +package co.nilin.opex.accountant.core.spi + +import co.nilin.opex.accountant.core.model.TempEvent +import co.nilin.opex.matching.core.eventh.events.CoreEvent + +interface TempEventPersister { + suspend fun saveTempEvent(ouid: String, event: CoreEvent) + suspend fun loadTempEvents(ouid: String): List + suspend fun removeTempEvents(ouid: String) + suspend fun removeTempEvents(tempEvents: List) + suspend fun fetchTempEvents(offset: Long, size: Long): List +} \ No newline at end of file diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/TempEventRepublisher.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/TempEventRepublisher.kt new file mode 100644 index 000000000..f86f23901 --- /dev/null +++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/TempEventRepublisher.kt @@ -0,0 +1,7 @@ +package co.nilin.opex.accountant.core.spi + +import co.nilin.opex.matching.core.eventh.events.CoreEvent + +interface TempEventRepublisher { + suspend fun republish(events: List) +} \ No newline at end of file diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/WalletProxy.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/WalletProxy.kt new file mode 100644 index 000000000..ed27996ca --- /dev/null +++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/WalletProxy.kt @@ -0,0 +1,19 @@ +package co.nilin.opex.accountant.core.spi + +import java.math.BigDecimal + +interface WalletProxy { + + suspend fun transfer( + symbol: String, + senderWalletType: String, + senderUuid: String, + receiverWalletType: String, + receiverUuid: String, + amount: BigDecimal, + description: String?, + transferRef: String? + ) + + suspend fun canFulfil(symbol: String, walletType: String, uuid: String, amount: BigDecimal): Boolean +} \ No newline at end of file diff --git a/Accountant/accountant-core/src/test/kotlin/co/nilin/opex/accountant/core/service/MockitoHelper.kt b/Accountant/accountant-core/src/test/kotlin/co/nilin/opex/accountant/core/service/MockitoHelper.kt new file mode 100644 index 000000000..d05650e71 --- /dev/null +++ b/Accountant/accountant-core/src/test/kotlin/co/nilin/opex/accountant/core/service/MockitoHelper.kt @@ -0,0 +1,12 @@ +package co.nilin.opex.accountant.core.service + +import org.mockito.Mockito + +object MockitoHelper { + fun anyObject(): T { + Mockito.any() + return uninitialized() + } + @Suppress("UNCHECKED_CAST") + fun uninitialized(): T = null as T +} \ No newline at end of file diff --git a/Accountant/accountant-core/src/test/kotlin/co/nilin/opex/accountant/core/service/OrderManagerImplTest.kt b/Accountant/accountant-core/src/test/kotlin/co/nilin/opex/accountant/core/service/OrderManagerImplTest.kt new file mode 100644 index 000000000..15f54a86c --- /dev/null +++ b/Accountant/accountant-core/src/test/kotlin/co/nilin/opex/accountant/core/service/OrderManagerImplTest.kt @@ -0,0 +1,180 @@ +package co.nilin.opex.accountant.core.service + +import co.nilin.opex.accountant.core.api.OrderManager +import co.nilin.opex.accountant.core.model.FinancialAction +import co.nilin.opex.accountant.core.model.PairConfig +import co.nilin.opex.accountant.core.model.PairFeeConfig +import co.nilin.opex.accountant.core.spi.* +import co.nilin.opex.matching.core.eventh.events.SubmitOrderEvent +import co.nilin.opex.matching.core.model.MatchConstraint +import co.nilin.opex.matching.core.model.OrderDirection +import co.nilin.opex.matching.core.model.OrderType +import co.nilin.opex.matching.core.model.Pair +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.mockito.ArgumentMatchers.anyString +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.MockitoAnnotations +import java.time.LocalDateTime + +internal class OrderManagerImplTest() { + + @Mock + lateinit var financialActionPersister: FinancialActionPersister + + @Mock + lateinit var financialActionLoader: FinancialActionLoader + + @Mock + lateinit var orderPersister: OrderPersister + + @Mock + lateinit var tempEventPersister: TempEventPersister + + @Mock + lateinit var tempEventRepublisher: TempEventRepublisher + + @Mock + lateinit var pairConfigLoader: PairConfigLoader + + @Mock + lateinit var richOrderPublisher: RichOrderPublisher + + val orderManager: OrderManager + + init { + MockitoAnnotations.openMocks(this) + orderManager = OrderManagerImpl( + pairConfigLoader, financialActionPersister, financialActionLoader, orderPersister, tempEventPersister, tempEventRepublisher, richOrderPublisher + ) + runBlocking { + Mockito.`when`(tempEventPersister.loadTempEvents(anyString())).thenReturn(emptyList()) + } + } + + @Test + fun givenAskOrder_whenHandleRequestOrder_thenFAMatch() { + runBlocking { + //given + val pair = Pair("eth", "btc") + val pairConfig = PairConfig( + pair.toString(), pair.leftSideName, pair.rightSideName, 1.0, 0.001 + ) + val submitOrderEvent = SubmitOrderEvent( + "ouid", "uuid", null, pair, 30, 60, 0, OrderDirection.ASK, MatchConstraint.GTC, OrderType.LIMIT_ORDER + ) + Mockito.`when`(pairConfigLoader.load(pair.toString(), submitOrderEvent.direction, "")) + .thenReturn( + PairFeeConfig( + pairConfig, + submitOrderEvent.direction.toString(), + "", + 0.1, + 0.12 + ) + ) + Mockito.`when`(financialActionPersister.persist(MockitoHelper.anyObject())) + .then { + return@then it.getArgument>(0) + } + + //when + val financialActions = orderManager.handleRequestOrder(submitOrderEvent) + + //then + assertEquals(1, financialActions.size) + val expectedFinancialAction = FinancialAction( + null, + SubmitOrderEvent::class.simpleName!!, + submitOrderEvent.ouid, + pair.leftSideName, + pairConfig.leftSideFraction.toBigDecimal().multiply(submitOrderEvent.quantity.toBigDecimal()), + submitOrderEvent.uuid, + "main", + submitOrderEvent.uuid, + "exchange", + LocalDateTime.now() + ) + assertEquals(expectedFinancialAction.eventType, financialActions[0].eventType) + assertEquals(expectedFinancialAction.symbol, financialActions[0].symbol) + assertEquals(expectedFinancialAction.amount, financialActions[0].amount) + assertEquals(expectedFinancialAction.sender, financialActions[0].sender) + assertEquals(expectedFinancialAction.senderWalletType, financialActions[0].senderWalletType) + assertEquals(expectedFinancialAction.receiver, financialActions[0].receiver) + assertEquals(expectedFinancialAction.receiverWalletType, financialActions[0].receiverWalletType) + } + } + + @Test + fun givenBidOrder_whenHandleRequestOrder_thenFAMatch() { + runBlocking { + //given + val pair = Pair("eth", "btc") + val pairConfig = PairConfig( + pair.toString(), pair.leftSideName, pair.rightSideName, 1.0, 0.001 + ) + val submitOrderEvent = SubmitOrderEvent( + "ouid", "uuid", null, pair, 35, 14, 0, OrderDirection.BID, MatchConstraint.GTC, OrderType.LIMIT_ORDER + ) + Mockito.`when`(pairConfigLoader.load(pair.toString(), submitOrderEvent.direction, "")) + .thenReturn( + PairFeeConfig( + pairConfig, submitOrderEvent.direction.toString(), "", 0.08, 0.1 + ) + ) + Mockito.`when`(financialActionPersister.persist(MockitoHelper.anyObject())) + .then { + return@then it.getArgument>(0) + } + + //when + val financialActions = runBlocking { + orderManager.handleRequestOrder(submitOrderEvent) + } + + //then + assertEquals(1, financialActions.size) + val expectedFinancialAction = FinancialAction( + null, + SubmitOrderEvent::class.simpleName!!, + submitOrderEvent.ouid, + pair.rightSideName, + pairConfig.leftSideFraction.toBigDecimal().multiply(submitOrderEvent.quantity.toBigDecimal()) + .multiply(pairConfig.rightSideFraction.toBigDecimal()) + .multiply(submitOrderEvent.price.toBigDecimal()), + submitOrderEvent.uuid, + "main", + submitOrderEvent.uuid, + "exchange", + LocalDateTime.now() + ) + assertEquals(expectedFinancialAction.eventType, financialActions[0].eventType) + assertEquals(expectedFinancialAction.symbol, financialActions[0].symbol) + assertEquals(expectedFinancialAction.amount, financialActions[0].amount) + assertEquals(expectedFinancialAction.sender, financialActions[0].sender) + assertEquals(expectedFinancialAction.senderWalletType, financialActions[0].senderWalletType) + assertEquals(expectedFinancialAction.receiver, financialActions[0].receiver) + assertEquals(expectedFinancialAction.receiverWalletType, financialActions[0].receiverWalletType) + } + } + + @Test + fun handleNewOrder() { + } + + @Test + fun handleUpdateOrder() { + } + + @Test + fun handleRejectOrder() { + } + + @Test + fun handleCancelOrder() { + } +} + + diff --git a/Accountant/accountant-core/src/test/kotlin/co/nilin/opex/accountant/core/service/TradeManagerImplTest.kt b/Accountant/accountant-core/src/test/kotlin/co/nilin/opex/accountant/core/service/TradeManagerImplTest.kt new file mode 100644 index 000000000..2982d59aa --- /dev/null +++ b/Accountant/accountant-core/src/test/kotlin/co/nilin/opex/accountant/core/service/TradeManagerImplTest.kt @@ -0,0 +1,239 @@ +package co.nilin.opex.accountant.core.service + +import co.nilin.opex.accountant.core.api.OrderManager +import co.nilin.opex.accountant.core.api.TradeManager +import co.nilin.opex.accountant.core.model.FinancialAction +import co.nilin.opex.accountant.core.model.Order +import co.nilin.opex.accountant.core.model.PairConfig +import co.nilin.opex.accountant.core.model.PairFeeConfig +import co.nilin.opex.accountant.core.spi.* +import co.nilin.opex.matching.core.eventh.events.SubmitOrderEvent +import co.nilin.opex.matching.core.eventh.events.TradeEvent +import co.nilin.opex.matching.core.model.MatchConstraint +import co.nilin.opex.matching.core.model.OrderDirection +import co.nilin.opex.matching.core.model.OrderType +import co.nilin.opex.matching.core.model.Pair +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import org.mockito.ArgumentMatchers + +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.MockitoAnnotations + +internal class TradeManagerImplTest() { + @Mock + lateinit var financialActionPersister: FinancialActionPersister + + @Mock + lateinit var financeActionLoader: FinancialActionLoader + + @Mock + lateinit var orderPersister: OrderPersister + + @Mock + lateinit var pairConfigLoader: PairConfigLoader + + @Mock + lateinit var pairStaticRateLoader: PairStaticRateLoader + + @Mock + lateinit var walletProxy: WalletProxy + + @Mock + lateinit var tempEventPersister: TempEventPersister + + @Mock + lateinit var tempEventRepublisher: TempEventRepublisher + + @Mock + lateinit var richOrderPublisher: RichOrderPublisher + + @Mock + lateinit var richTradePublisher: RichTradePublisher + + val orderManager: OrderManager + + val tradeManager: TradeManager + + + init { + MockitoAnnotations.openMocks(this) + orderManager = OrderManagerImpl( + pairConfigLoader, + financialActionPersister, + financeActionLoader, + orderPersister, + tempEventPersister, + tempEventRepublisher, + richOrderPublisher + ) + tradeManager = TradeManagerImpl( + pairStaticRateLoader, + financialActionPersister, + financeActionLoader, + orderPersister, + tempEventPersister, + richTradePublisher, + walletProxy, + "pcoin", + "0x0" + ) + runBlocking { + Mockito.`when`(tempEventPersister.loadTempEvents(ArgumentMatchers.anyString())).thenReturn(emptyList()) + } + } + + @Test + fun givenMatchOrders_whenTradeCreated_thenFAMatched() { + runBlocking { + //given + val pair = Pair("eth", "btc") + val pairConfig = co.nilin.opex.accountant.core.model.PairConfig( + pair.toString(), pair.leftSideName, pair.rightSideName, 1.0, 0.001 + ) + val makerSubmitOrderEvent = SubmitOrderEvent( + "mouid", "muuid", null, pair, 29, 60, 0, OrderDirection.ASK, MatchConstraint.GTC, OrderType.LIMIT_ORDER + ) + prepareOrder(pair, pairConfig, makerSubmitOrderEvent, 0.1, 0.12) + + val takerSubmitOrderEvent = SubmitOrderEvent( + "touid", "tuuid", null, pair, 30, 14, 0, OrderDirection.BID, MatchConstraint.GTC, OrderType.LIMIT_ORDER + ) + + prepareOrder(pair, pairConfig, takerSubmitOrderEvent, 0.08, 0.1) + + val tradeEvent = makeTradeEvent(pair, takerSubmitOrderEvent, makerSubmitOrderEvent) + //when + val tradeFinancialActions = tradeManager.handleTrade(tradeEvent) + Assertions.assertEquals(4, tradeFinancialActions.size) + } + } + + @Test + fun givenMatchOrders2_whenTradeCreated_thenFAMatched() { + runBlocking { + //given + val pair = Pair("eth", "btc") + val pairConfig = co.nilin.opex.accountant.core.model.PairConfig( + pair.toString(), pair.leftSideName, pair.rightSideName, 0.000001, 0.000001 + ) + val makerSubmitOrderEvent = SubmitOrderEvent( + "mouid", + "muuid", + null, + pair, + 33333, + 5000000, + 0, + OrderDirection.ASK, + MatchConstraint.GTC, + OrderType.LIMIT_ORDER + ) + prepareOrder(pair, pairConfig, makerSubmitOrderEvent, 0.8 * 0.001, 1.0 * 0.001) + + val takerSubmitOrderEvent = SubmitOrderEvent( + "touid", + "tuuid", + null, + pair, + 34482, + 1000000, + 0, + OrderDirection.BID, + MatchConstraint.GTC, + OrderType.LIMIT_ORDER + ) + + prepareOrder(pair, pairConfig, takerSubmitOrderEvent, 0.8 * 0.001, 1.0 * 0.001) + + val tradeEvent = makeTradeEvent(pair, takerSubmitOrderEvent, makerSubmitOrderEvent) + //when + val tradeFinancialActions = tradeManager.handleTrade(tradeEvent) + Assertions.assertEquals(4, tradeFinancialActions.size) + } + } + + private fun makeTradeEvent( + pair: Pair, + takerSubmitOrderEvent: SubmitOrderEvent, + makerSubmitOrderEvent: SubmitOrderEvent + ): TradeEvent { + val tradeEvent = TradeEvent( + 0, + pair, + takerSubmitOrderEvent.ouid, + takerSubmitOrderEvent.uuid, + takerSubmitOrderEvent.orderId ?: -1, + takerSubmitOrderEvent.direction, + takerSubmitOrderEvent.price, + 0, + makerSubmitOrderEvent.ouid, + makerSubmitOrderEvent.uuid, + makerSubmitOrderEvent.orderId ?: 1, + makerSubmitOrderEvent.direction, + makerSubmitOrderEvent.price, + makerSubmitOrderEvent.quantity - takerSubmitOrderEvent.quantity, + takerSubmitOrderEvent.quantity + ) + return tradeEvent + } + + private fun prepareOrder( + pair: Pair, + pairConfig: co.nilin.opex.accountant.core.model.PairConfig, + submitOrderEvent: SubmitOrderEvent, + makerFee: Double, + takerFee: Double + ) { + runBlocking { + Mockito.`when`(pairConfigLoader.load(pair.toString(), submitOrderEvent.direction, "")) + .thenReturn( + co.nilin.opex.accountant.core.model.PairFeeConfig( + pairConfig, + submitOrderEvent.direction.toString(), + "", + makerFee, + takerFee + ) + ) + Mockito.`when`(financialActionPersister.persist(MockitoHelper.anyObject())) + .then { + return@then it.getArgument>(0) + } + + val financialActions = orderManager.handleRequestOrder(submitOrderEvent) + + val orderPairFeeConfig = + pairConfigLoader.load(submitOrderEvent.pair.toString(), submitOrderEvent.direction, "") + val orderMakerFee = orderPairFeeConfig.makerFee * 1 //user level formula + val orderTakerFee = orderPairFeeConfig.takerFee * 1 //user level formula + Mockito.`when`(orderPersister.load(submitOrderEvent.ouid)).thenReturn( + Order( + submitOrderEvent.pair.toString(), + submitOrderEvent.ouid, + null, + orderMakerFee, + orderTakerFee, + orderPairFeeConfig.pairConfig.leftSideFraction, + orderPairFeeConfig.pairConfig.rightSideFraction, + submitOrderEvent.uuid, + "", + submitOrderEvent.direction, + submitOrderEvent.matchConstraint, + submitOrderEvent.orderType, + submitOrderEvent.price, + submitOrderEvent.quantity, + submitOrderEvent.quantity - submitOrderEvent.remainedQuantity, + submitOrderEvent.price.toBigDecimal(), + submitOrderEvent.quantity.toBigDecimal(), + (submitOrderEvent.quantity - submitOrderEvent.remainedQuantity).toBigDecimal(), + financialActions[0].amount, + financialActions[0].amount, + 0 + ) + ) + } + } +} \ No newline at end of file diff --git a/Accountant/accountant-ports/.gitignore b/Accountant/accountant-ports/.gitignore new file mode 100644 index 000000000..f3a8317d6 --- /dev/null +++ b/Accountant/accountant-ports/.gitignore @@ -0,0 +1,4 @@ +*.iml +target/ +.mvn/ +.idea/ \ No newline at end of file diff --git a/Accountant/accountant-ports/accountant-eventlistener-kafka/.gitignore b/Accountant/accountant-ports/accountant-eventlistener-kafka/.gitignore new file mode 100644 index 000000000..bb9840a17 --- /dev/null +++ b/Accountant/accountant-ports/accountant-eventlistener-kafka/.gitignore @@ -0,0 +1,34 @@ +HELP.md +target/ +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +.mvn/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ + + +### VS Code ### +.vscode/ + +.DS_Store diff --git a/Accountant/accountant-ports/accountant-eventlistener-kafka/mvnw b/Accountant/accountant-ports/accountant-eventlistener-kafka/mvnw new file mode 100644 index 000000000..a16b5431b --- /dev/null +++ b/Accountant/accountant-ports/accountant-eventlistener-kafka/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/Accountant/accountant-ports/accountant-eventlistener-kafka/mvnw.cmd b/Accountant/accountant-ports/accountant-eventlistener-kafka/mvnw.cmd new file mode 100644 index 000000000..c8d43372c --- /dev/null +++ b/Accountant/accountant-ports/accountant-eventlistener-kafka/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/Accountant/accountant-ports/accountant-eventlistener-kafka/pom.xml b/Accountant/accountant-ports/accountant-eventlistener-kafka/pom.xml new file mode 100644 index 000000000..e85bab28d --- /dev/null +++ b/Accountant/accountant-ports/accountant-eventlistener-kafka/pom.xml @@ -0,0 +1,113 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.4 + + + co.nilin.opex + accountant-eventlistener-kafka + 1.0-SNAPSHOT + accountant-eventlistener-kafka + Accountant kafka listener of Opex + + + 1.8 + 1.4.31 + ${version} + ${version} + + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-webflux + + + co.nilin.opex + matching-core + ${matching.version} + provided + + + co.nilin.opex + accountant-core + ${accountant.version} + provided + + + org.springframework.kafka + spring-kafka + + + io.projectreactor.kotlin + reactor-kotlin-extensions + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + + + org.jetbrains.kotlinx + kotlinx-coroutines-core + + + org.springframework.kafka + spring-kafka-test + test + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + -Xjsr305=strict + + + spring + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + + + diff --git a/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/config/AccountantKafkaConfig.kt b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/config/AccountantKafkaConfig.kt new file mode 100644 index 000000000..6c4781a87 --- /dev/null +++ b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/config/AccountantKafkaConfig.kt @@ -0,0 +1,128 @@ +package co.nilin.opex.port.accountant.kafka.config + + +import co.nilin.opex.matching.core.eventh.events.CoreEvent +import co.nilin.opex.port.accountant.kafka.consumer.OrderKafkaListener +import co.nilin.opex.port.accountant.kafka.consumer.EventKafkaListener +import co.nilin.opex.port.accountant.kafka.consumer.TempEventKafkaListener +import co.nilin.opex.port.accountant.kafka.consumer.TradeKafkaListener +import org.apache.kafka.clients.admin.NewTopic +import org.apache.kafka.clients.consumer.ConsumerConfig +import org.apache.kafka.clients.producer.ProducerConfig +import org.apache.kafka.common.serialization.StringDeserializer +import org.apache.kafka.common.serialization.StringSerializer +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.beans.factory.annotation.Value +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.context.support.GenericApplicationContext +import org.springframework.kafka.core.ConsumerFactory +import org.springframework.kafka.core.DefaultKafkaConsumerFactory +import org.springframework.kafka.core.DefaultKafkaProducerFactory +import org.springframework.kafka.core.KafkaTemplate +import org.springframework.kafka.core.ProducerFactory +import org.springframework.kafka.listener.ConcurrentMessageListenerContainer +import org.springframework.kafka.listener.ContainerProperties +import org.springframework.kafka.support.serializer.JsonDeserializer +import org.springframework.kafka.support.serializer.JsonSerializer +import java.util.* +import java.util.regex.Pattern + + +@Configuration +class AccountantKafkaConfig { + @Value("\${spring.kafka.bootstrap-servers}") + private val bootstrapServers: String? = null + + @Value("\${spring.kafka.consumer.group-id}") + private val groupId: String? = null + + @Bean("accountantConsumerConfig") + fun consumerConfigs(): Map? { + val props: MutableMap = HashMap() + props[ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG] = bootstrapServers + props[ConsumerConfig.GROUP_ID_CONFIG] = groupId + props[ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG] = StringDeserializer::class.java + props[ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG] = JsonDeserializer::class.java + props[JsonDeserializer.TRUSTED_PACKAGES] = "co.nilin.opex.*" + return props + } + + @Bean("accountantConsumerFactory") + fun consumerFactory(@Qualifier("accountantConsumerConfig")consumerConfigs: Map): ConsumerFactory { + return DefaultKafkaConsumerFactory(consumerConfigs) + } + + @Bean("accountantProducerConfig") + fun producerConfigs(): Map { + val props: MutableMap = HashMap() + props[ProducerConfig.BOOTSTRAP_SERVERS_CONFIG] = bootstrapServers + props[ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG] = StringSerializer::class.java + props[ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG] = JsonSerializer::class.java + return props + } + + @Bean("accountantProducerFactory") + fun producerFactory(@Qualifier("accountantProducerConfig") producerConfigs: Map): ProducerFactory { + return DefaultKafkaProducerFactory(producerConfigs) + } + + @Bean("accountantKafkaTemplate") + fun kafkaTemplate(@Qualifier("accountantProducerFactory") producerFactory: ProducerFactory): KafkaTemplate { + return KafkaTemplate(producerFactory) + } + + + @Autowired + @ConditionalOnBean(TradeKafkaListener::class) + fun configureTradeListener(tradeListener: TradeKafkaListener + , @Qualifier("accountantConsumerFactory") consumerFactory: ConsumerFactory) { + val containerProps = ContainerProperties(Pattern.compile("trades_.*")) + containerProps.messageListener = tradeListener + val container = ConcurrentMessageListenerContainer(consumerFactory, containerProps) + container.setBeanName("TradeKafkaListenerContainer") + container.start() + } + + @Autowired + @ConditionalOnBean(EventKafkaListener::class) + fun configureEventListener(eventListener: EventKafkaListener + , @Qualifier("accountantConsumerFactory") consumerFactory: ConsumerFactory) { + val containerProps = ContainerProperties(Pattern.compile("events_.*")) + containerProps.messageListener = eventListener + val container = ConcurrentMessageListenerContainer(consumerFactory, containerProps) + container.setBeanName("EventKafkaListenerContainer") + container.start() + } + + @Autowired + @ConditionalOnBean(OrderKafkaListener::class) + fun configureOrderListener(orderListener: OrderKafkaListener + , @Qualifier("accountantConsumerFactory") consumerFactory: ConsumerFactory) { + val containerProps = ContainerProperties(Pattern.compile("orders_.*")) + containerProps.messageListener = orderListener + val container = ConcurrentMessageListenerContainer(consumerFactory, containerProps) + container.setBeanName("OrderKafkaListenerContainer") + container.start() + } + + @Autowired + fun createTempTopics(applicationContext: GenericApplicationContext){ + applicationContext.registerBean("topic_tempevents", NewTopic::class.java, "tempevents", 1 ,1) + } + + @Autowired + @ConditionalOnBean(TempEventKafkaListener::class) + fun configureTempEventListener(eventListener: TempEventKafkaListener + , @Qualifier("accountantConsumerFactory") consumerFactory: ConsumerFactory) { + val containerProps = ContainerProperties(Pattern.compile("tempevents")) + containerProps.messageListener = eventListener + val container = ConcurrentMessageListenerContainer(consumerFactory, containerProps) + container.setBeanName("TempEventKafkaListenerContainer") + container.start() + } + + +} \ No newline at end of file diff --git a/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/consumer/EventKafkaListener.kt b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/consumer/EventKafkaListener.kt new file mode 100644 index 000000000..2f86674d6 --- /dev/null +++ b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/consumer/EventKafkaListener.kt @@ -0,0 +1,29 @@ +package co.nilin.opex.port.accountant.kafka.consumer + +import co.nilin.opex.matching.core.eventh.events.CoreEvent +import co.nilin.opex.port.accountant.kafka.spi.EventListener +import org.apache.kafka.clients.consumer.ConsumerRecord +import org.springframework.kafka.listener.MessageListener +import org.springframework.stereotype.Component + +@Component +class EventKafkaListener: MessageListener { + + val eventListeners = arrayListOf() + + override fun onMessage(data: ConsumerRecord) { + eventListeners.forEach{ + tl -> tl.onEvent(data.value(), data.partition(), data.offset(), data.timestamp()) + } + } + + fun addEventListener(tl: EventListener){ + eventListeners.add(tl) + } + + fun removeEventListener(tl: EventListener){ + eventListeners.removeIf { + item -> item.id() == tl.id() + } + } +} \ No newline at end of file diff --git a/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/consumer/OrderKafkaListener.kt b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/consumer/OrderKafkaListener.kt new file mode 100644 index 000000000..208b7e039 --- /dev/null +++ b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/consumer/OrderKafkaListener.kt @@ -0,0 +1,30 @@ +package co.nilin.opex.port.accountant.kafka.consumer + +import co.nilin.opex.port.accountant.kafka.spi.OrderSubmitRequestListener +import co.nilin.opex.port.order.kafka.inout.OrderSubmitRequest +import org.apache.kafka.clients.consumer.ConsumerRecord +import org.springframework.kafka.listener.MessageListener +import org.springframework.stereotype.Component + +@Component +class OrderKafkaListener : MessageListener { + + val orderListeners = arrayListOf() + + override fun onMessage(data: ConsumerRecord) { + orderListeners.forEach { tl -> + tl.onOrder(data.value(), data.partition(), data.offset(), data.timestamp()) + } + + } + + fun addOrderListener(tl: OrderSubmitRequestListener) { + orderListeners.add(tl) + } + + fun removeOrderListener(tl: OrderSubmitRequestListener) { + orderListeners.removeIf { item -> + item.id() == tl.id() + } + } +} \ No newline at end of file diff --git a/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/consumer/TempEventKafkaListener.kt b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/consumer/TempEventKafkaListener.kt new file mode 100644 index 000000000..2e9ef8b6a --- /dev/null +++ b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/consumer/TempEventKafkaListener.kt @@ -0,0 +1,31 @@ +package co.nilin.opex.port.accountant.kafka.consumer + + +import co.nilin.opex.matching.core.eventh.events.CoreEvent +import co.nilin.opex.port.accountant.kafka.spi.TempEventListener +import org.apache.kafka.clients.consumer.ConsumerRecord +import org.springframework.kafka.listener.MessageListener +import org.springframework.stereotype.Component + +@Component +class TempEventKafkaListener: MessageListener { + + val eventListeners = arrayListOf() + + override fun onMessage(data: ConsumerRecord) { + println("TempEvent onMessage") + eventListeners.forEach{ + tl -> tl.onEvent(data.value(), data.partition(), data.offset(), data.timestamp()) + } + } + + fun addEventListener(tl: TempEventListener){ + eventListeners.add(tl) + } + + fun removeEventListener(tl: TempEventListener){ + eventListeners.removeIf { + item -> item.id() == tl.id() + } + } +} \ No newline at end of file diff --git a/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/consumer/TradeKafkaListener.kt b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/consumer/TradeKafkaListener.kt new file mode 100644 index 000000000..6d91a0c8a --- /dev/null +++ b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/consumer/TradeKafkaListener.kt @@ -0,0 +1,29 @@ +package co.nilin.opex.port.accountant.kafka.consumer + +import co.nilin.opex.matching.core.eventh.events.TradeEvent +import co.nilin.opex.port.accountant.kafka.spi.TradeListener +import org.apache.kafka.clients.consumer.ConsumerRecord +import org.springframework.kafka.listener.MessageListener +import org.springframework.stereotype.Component + +@Component +class TradeKafkaListener: MessageListener { + + val tradeListeners = arrayListOf() + + override fun onMessage(data: ConsumerRecord) { + tradeListeners.forEach{ + tl -> tl.onTrade(data.value(), data.partition(), data.offset(), data.timestamp()) + } + } + + fun addTradeListener(tl: TradeListener){ + tradeListeners.add(tl) + } + + fun removeTradeListener(tl: TradeListener){ + tradeListeners.removeIf { + item -> item.id() == tl.id() + } + } +} \ No newline at end of file diff --git a/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/spi/EventListener.kt b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/spi/EventListener.kt new file mode 100644 index 000000000..4bc2c860f --- /dev/null +++ b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/spi/EventListener.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.port.accountant.kafka.spi + +import co.nilin.opex.matching.core.eventh.events.CoreEvent + +interface EventListener { + fun id(): String + fun onEvent(coreEvent: CoreEvent, partition: Int, offset: Long, timestamp: Long) +} \ No newline at end of file diff --git a/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/spi/OrderSubmitRequestListener.kt b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/spi/OrderSubmitRequestListener.kt new file mode 100644 index 000000000..a956db9fe --- /dev/null +++ b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/spi/OrderSubmitRequestListener.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.port.accountant.kafka.spi + +import co.nilin.opex.port.order.kafka.inout.OrderSubmitRequest + +interface OrderSubmitRequestListener { + fun id(): String + fun onOrder(order: OrderSubmitRequest, partition: Int, offset: Long, timestamp: Long) +} \ No newline at end of file diff --git a/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/spi/TempEventListener.kt b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/spi/TempEventListener.kt new file mode 100644 index 000000000..76e14cce2 --- /dev/null +++ b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/spi/TempEventListener.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.port.accountant.kafka.spi + +import co.nilin.opex.matching.core.eventh.events.CoreEvent + +interface TempEventListener { + fun id(): String + fun onEvent(coreEvent: CoreEvent, partition: Int, offset: Long, timestamp: Long) +} \ No newline at end of file diff --git a/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/spi/TradeListener.kt b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/spi/TradeListener.kt new file mode 100644 index 000000000..f4f958b97 --- /dev/null +++ b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/spi/TradeListener.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.port.accountant.kafka.spi + +import co.nilin.opex.matching.core.eventh.events.TradeEvent + +interface TradeListener { + fun id(): String + fun onTrade(tradeEvent: TradeEvent, partition: Int, offset: Long, timestamp: Long) +} \ No newline at end of file diff --git a/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/inout/OrderSubmitRequest.kt b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/inout/OrderSubmitRequest.kt new file mode 100644 index 000000000..db9f6e74f --- /dev/null +++ b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/inout/OrderSubmitRequest.kt @@ -0,0 +1,43 @@ +package co.nilin.opex.port.order.kafka.inout + +import co.nilin.opex.matching.core.model.MatchConstraint +import co.nilin.opex.matching.core.model.OrderDirection +import co.nilin.opex.matching.core.model.OrderType +import co.nilin.opex.matching.core.model.Pair + +class OrderSubmitRequest() { + + lateinit var ouid: String + lateinit var uuid: String + var orderId: Long? = null + lateinit var pair: Pair + var price: Long = 0 + var quantity: Long = 0 + var direction: OrderDirection = OrderDirection.BID + var matchConstraint: MatchConstraint = MatchConstraint.GTC + var orderType: OrderType = OrderType.LIMIT_ORDER + + constructor( + ouid: String, + uuid: String, + orderId: Long?, + pair: Pair, + price: Long, + quantity: Long, + direction: OrderDirection, + matchConstraint: MatchConstraint, + orderType: OrderType + ) : this() { + this.ouid = ouid + this.uuid = uuid + this.orderId = orderId + this.pair = pair + this.price = price + this.quantity = quantity + this.direction = direction + this.matchConstraint = matchConstraint + this.orderType = orderType + } + + +} \ No newline at end of file diff --git a/Accountant/accountant-ports/accountant-persister-postgres/.gitignore b/Accountant/accountant-ports/accountant-persister-postgres/.gitignore new file mode 100644 index 000000000..de5a9214d --- /dev/null +++ b/Accountant/accountant-ports/accountant-persister-postgres/.gitignore @@ -0,0 +1,34 @@ +HELP.md +target/ +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +.mvn/ +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +.DS_Store diff --git a/Accountant/accountant-ports/accountant-persister-postgres/mvnw b/Accountant/accountant-ports/accountant-persister-postgres/mvnw new file mode 100644 index 000000000..a16b5431b --- /dev/null +++ b/Accountant/accountant-ports/accountant-persister-postgres/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/Accountant/accountant-ports/accountant-persister-postgres/mvnw.cmd b/Accountant/accountant-ports/accountant-persister-postgres/mvnw.cmd new file mode 100644 index 000000000..c8d43372c --- /dev/null +++ b/Accountant/accountant-ports/accountant-persister-postgres/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/Accountant/accountant-ports/accountant-persister-postgres/pom.xml b/Accountant/accountant-ports/accountant-persister-postgres/pom.xml new file mode 100644 index 000000000..db635b291 --- /dev/null +++ b/Accountant/accountant-ports/accountant-persister-postgres/pom.xml @@ -0,0 +1,122 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.4 + + + co.nilin.opex + accountant-persister-postgres + 1.0-SNAPSHOT + accountant-persister-postgres + Persist items of Opex accountant on Postgres + + + 1.8 + 1.4.31 + ${version} + ${version} + + + + + co.nilin.opex + matching-core + ${matching.version} + provided + + + co.nilin.opex + accountant-core + ${accountant.version} + provided + + + org.springframework.boot + spring-boot-starter-data-r2dbc + + + io.r2dbc + r2dbc-postgresql + runtime + + + org.postgresql + postgresql + runtime + + + + io.projectreactor.kotlin + reactor-kotlin-extensions + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + + + + org.jetbrains.kotlinx + kotlinx-coroutines-core + + + com.google.code.gson + gson + + + co.nilin.opex + error-handler + 1.0-SNAPSHOT + + + io.projectreactor + reactor-test + test + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + -Xjsr305=strict + + + spring + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + + diff --git a/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/config/PostgresConfig.kt b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/config/PostgresConfig.kt new file mode 100644 index 000000000..cb04c037f --- /dev/null +++ b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/config/PostgresConfig.kt @@ -0,0 +1,99 @@ +package co.nilin.opex.port.accountant.postgres.config + +import org.springframework.context.annotation.Configuration +import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories +import org.springframework.r2dbc.core.DatabaseClient + +@Configuration +@EnableR2dbcRepositories(basePackages = ["co.nilin.opex"]) +class PostgresConfig(db: DatabaseClient) { + + init { + val sql = """ + CREATE TABLE IF NOT EXISTS orders ( + id SERIAL PRIMARY KEY, + ouid VARCHAR(72) NOT NULL UNIQUE, + uuid VARCHAR(72) NOT NULL, + pair VARCHAR(20), + matching_engine_id numeric, + maker_fee decimal, + taker_fee decimal, + left_side_fraction decimal, + right_side_fraction decimal, + user_level VARCHAR(20), + direction VARCHAR(20), + match_constraint VARCHAR(30), + order_type VARCHAR(30), + price numeric, + quantity numeric, + filled_quantity numeric, + orig_price decimal, + orig_quantity decimal, + filled_orig_quantity decimal, + first_transfer_amount numeric, + remained_transfer_amount numeric, + status integer, + agent VARCHAR(20), + ip VARCHAR(11), + create_date TIMESTAMP + ); + + CREATE TABLE IF NOT EXISTS fi_actions ( + id SERIAL PRIMARY KEY, + parent_id numeric, + event_type VARCHAR(72) NOT NULL, + pointer VARCHAR(72) NOT NULL, + symbol VARCHAR(36), + amount decimal, + sender VARCHAR(36), + sender_wallet_type VARCHAR(36), + receiver VARCHAR(36), + receiver_wallet_type VARCHAR(36), + agent VARCHAR(20), + ip VARCHAR(11), + create_date TIMESTAMP, + status VARCHAR(20), + retry_count numeric, + last_try_date TIMESTAMP + ); + CREATE TABLE IF NOT EXISTS pair_config ( + pair VARCHAR(72) PRIMARY KEY, + left_side_wallet_symbol VARCHAR(36) NOT NULL, + right_side_wallet_symbol VARCHAR(36) NOT NULL, + left_side_fraction decimal, + right_side_fraction decimal, + rate decimal + ); + CREATE TABLE IF NOT EXISTS pair_fee_config ( + id SERIAL PRIMARY KEY, + pair_config_id VARCHAR(72), + direction VARCHAR(36) NOT NULL, + user_level VARCHAR(36) NOT NULL, + maker_fee decimal, + taker_fee decimal + ); + + CREATE TABLE IF NOT EXISTS temp_events ( + id SERIAL PRIMARY KEY, + ouid VARCHAR(72) NOT NULL, + event_type VARCHAR(72) NOT NULL, + event_body TEXT, + event_date TIMESTAMP + ); + insert into pair_config values('btc_usdt', 'btc', 'usdt', 0.000001, 0.01, 55000)ON CONFLICT DO NOTHING; + insert into pair_fee_config values(1, 'btc_usdt', 'ASK', '*', 0.01, 0.01) ON CONFLICT DO NOTHING; + insert into pair_fee_config values(2, 'btc_usdt', 'BID', '*', 0.01, 0.01) ON CONFLICT DO NOTHING; + insert into pair_config values('nln_usdt', 'nln', 'usdt', 1.0, 0.01, 0.01) ON CONFLICT DO NOTHING; + insert into pair_fee_config values(3, 'nln_usdt', 'ASK', '*', 0.01, 0.01) ON CONFLICT DO NOTHING; + insert into pair_fee_config values(4, 'nln_usdt', 'BID', '*', 0.01, 0.01) ON CONFLICT DO NOTHING; + insert into pair_config values('nln_btc', 'nln', 'btc', 1.0, 0.000001, 1/5500000) ON CONFLICT DO NOTHING; + insert into pair_fee_config values(5, 'nln_btc', 'ASK', '*', 0.01, 0.01) ON CONFLICT DO NOTHING; + insert into pair_fee_config values(6, 'nln_btc', 'BID', '*', 0.01, 0.01) ON CONFLICT DO NOTHING; + commit; + """ + val initDb = db.sql { sql } + initDb // initialize the database + .then() + .subscribe() // execute + } +} diff --git a/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/dao/FinancialActionRepository.kt b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/dao/FinancialActionRepository.kt new file mode 100644 index 000000000..3113cdcde --- /dev/null +++ b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/dao/FinancialActionRepository.kt @@ -0,0 +1,32 @@ +package co.nilin.opex.port.accountant.postgres.dao + +import co.nilin.opex.accountant.core.model.FinancialActionStatus +import co.nilin.opex.port.accountant.postgres.model.FinancialActionModel +import kotlinx.coroutines.flow.Flow +import org.springframework.data.domain.Pageable +import org.springframework.data.r2dbc.repository.Query +import org.springframework.data.repository.query.Param +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository +import reactor.core.publisher.Mono +import java.math.BigDecimal + +@Repository +interface FinancialActionRepository : ReactiveCrudRepository { + + @Query("select * from fi_actions fi where pointer = :ouid and :uuid in (fi.sender, fi.receiver)") + fun findByOuidAndUuid( + @Param("ouid") ouid: String, @Param("uuid") uuid: String, paging: Pageable + ): Flow + + @Query("select count(1) from fi_actions fi where fi.sender = :uuid and fi.symbol = :symbol and fi.event_type = :eventType and fi.status = :status") + fun findByUuidAndSymbolAndEventTypeAndStatus( + @Param("uuid") uuid: String, + @Param("symbol") symbol: String, + @Param("eventType") eventType: String, + @Param("status") financialActionStatus: FinancialActionStatus + ): Mono + + @Query("select * from fi_actions fi where status = :status") + fun findByStatus(@Param("status") status: String, paging: Pageable): Flow +} \ No newline at end of file diff --git a/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/dao/OrderRepository.kt b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/dao/OrderRepository.kt new file mode 100644 index 000000000..adf817a93 --- /dev/null +++ b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/dao/OrderRepository.kt @@ -0,0 +1,14 @@ +package co.nilin.opex.port.accountant.postgres.dao + +import co.nilin.opex.port.accountant.postgres.model.OrderModel +import org.springframework.data.r2dbc.repository.Query +import org.springframework.data.repository.query.Param +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository +import reactor.core.publisher.Mono + +@Repository +interface OrderRepository: ReactiveCrudRepository { + @Query("select * from orders where ouid = :ouid") + fun findByOuid(@Param("ouid") ouid: String): Mono +} \ No newline at end of file diff --git a/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/dao/PairConfigRepository.kt b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/dao/PairConfigRepository.kt new file mode 100644 index 000000000..b43a4bb84 --- /dev/null +++ b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/dao/PairConfigRepository.kt @@ -0,0 +1,9 @@ +package co.nilin.opex.port.accountant.postgres.dao + +import co.nilin.opex.port.accountant.postgres.model.PairConfigModel +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository + +@Repository +interface PairConfigRepository: ReactiveCrudRepository { +} \ No newline at end of file diff --git a/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/dao/PairFeeConfigRepository.kt b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/dao/PairFeeConfigRepository.kt new file mode 100644 index 000000000..60903c0c9 --- /dev/null +++ b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/dao/PairFeeConfigRepository.kt @@ -0,0 +1,20 @@ +package co.nilin.opex.port.accountant.postgres.dao + +import co.nilin.opex.matching.core.model.OrderDirection +import co.nilin.opex.port.accountant.postgres.model.PairConfigModel +import co.nilin.opex.port.accountant.postgres.model.PairFeeConfigModel +import org.springframework.data.r2dbc.repository.Query +import org.springframework.data.repository.query.Param +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository +import reactor.core.publisher.Mono + +@Repository +interface PairFeeConfigRepository : ReactiveCrudRepository { + @Query("select * from pair_fee_config where pair_config_id = :pair and direction = :direction and user_level = :userLevel") + fun findByPairAndDirectionAndUserLevel( + @Param("pair") pair: String, + @Param("direction") direction: OrderDirection, + @Param("userLevel") userLevel: String + ): Mono +} \ No newline at end of file diff --git a/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/dao/TempEventRepository.kt b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/dao/TempEventRepository.kt new file mode 100644 index 000000000..6f89925f2 --- /dev/null +++ b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/dao/TempEventRepository.kt @@ -0,0 +1,19 @@ +package co.nilin.opex.port.accountant.postgres.dao + +import co.nilin.opex.port.accountant.postgres.model.TempEventModel +import kotlinx.coroutines.flow.Flow +import org.springframework.data.domain.Pageable +import org.springframework.data.r2dbc.repository.Query +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository +import reactor.core.publisher.Mono + +@Repository +interface TempEventRepository : ReactiveCrudRepository { + + fun findByOuid(ouid: String): Flow + fun deleteByOuid(ouid: String): Mono + + @Query("select * from temp_events") + fun findAll(paging: Pageable): Flow +} \ No newline at end of file diff --git a/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/impl/FinancialActionLoaderImpl.kt b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/impl/FinancialActionLoaderImpl.kt new file mode 100644 index 000000000..e7cba4d18 --- /dev/null +++ b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/impl/FinancialActionLoaderImpl.kt @@ -0,0 +1,66 @@ +package co.nilin.opex.port.accountant.postgres.impl + +import co.nilin.opex.accountant.core.model.FinancialAction +import co.nilin.opex.accountant.core.model.FinancialActionStatus +import co.nilin.opex.accountant.core.spi.FinancialActionLoader +import co.nilin.opex.port.accountant.postgres.dao.FinancialActionRepository +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.reactive.awaitFirst +import kotlinx.coroutines.reactive.awaitFirstOrElse +import org.springframework.data.domain.PageRequest +import org.springframework.data.domain.Sort +import org.springframework.stereotype.Component +import java.math.BigDecimal + +@Component +class FinancialActionLoaderImpl(val financialActionRepository: FinancialActionRepository) : FinancialActionLoader { + + override suspend fun loadUnprocessed(offset: Long, size: Long): List { + return financialActionRepository.findByStatus( + FinancialActionStatus.CREATED.name, + PageRequest.of(offset.toInt(), size.toInt(), Sort.by(Sort.Direction.ASC, "createDate")) + ).map { fim -> + loadFinancialAction(fim.id)!! + }.toList() + } + + override suspend fun findLast(uuid: String, ouid: String): FinancialAction? { + return financialActionRepository.findByOuidAndUuid( + ouid, uuid, PageRequest.of(0, 1, Sort.by(Sort.Direction.DESC, "createDate")) + ).map { fim -> + loadFinancialAction(fim.id) + }.firstOrNull() + } + + private suspend fun loadFinancialAction(id: Long?): FinancialAction? { + if (id != null) { + val fim = financialActionRepository.findById(id).awaitFirst() + return FinancialAction( + fim.id, + loadFinancialAction(fim.parentId), + fim.eventType, + fim.pointer, + fim.symbol, + BigDecimal.valueOf(fim.amount), + fim.sender, + fim.senderWalletType, + fim.receiver, + fim.receiverWalletType, + fim.createDate + ) + } + return null + } + + override suspend fun countUnprocessed(uuid: String, symbol: String, eventType: String): Long { + return financialActionRepository.findByUuidAndSymbolAndEventTypeAndStatus( + uuid, + symbol, + eventType, + FinancialActionStatus.CREATED + ).awaitFirstOrElse { BigDecimal.ZERO } + .toLong() + } +} \ No newline at end of file diff --git a/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/impl/FinancialActionPersisterImpl.kt b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/impl/FinancialActionPersisterImpl.kt new file mode 100644 index 000000000..6af5107d7 --- /dev/null +++ b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/impl/FinancialActionPersisterImpl.kt @@ -0,0 +1,65 @@ +package co.nilin.opex.port.accountant.postgres.impl + +import co.nilin.opex.accountant.core.model.FinancialAction +import co.nilin.opex.accountant.core.model.FinancialActionStatus +import co.nilin.opex.accountant.core.spi.FinancialActionPersister +import co.nilin.opex.port.accountant.postgres.dao.FinancialActionRepository +import co.nilin.opex.port.accountant.postgres.model.FinancialActionModel +import kotlinx.coroutines.reactive.awaitFirst +import kotlinx.coroutines.reactive.awaitFirstOrElse +import kotlinx.coroutines.reactive.awaitLast +import org.springframework.stereotype.Component +import java.lang.IllegalArgumentException +import java.time.LocalDateTime + +@Component +class FinancialActionPersisterImpl(val financialActionRepository: FinancialActionRepository) : + FinancialActionPersister { + + override suspend fun persist(financialActions: List): List { + financialActionRepository.saveAll(financialActions.map { fa -> + FinancialActionModel( + null, + fa.parent?.id, + fa.eventType, + fa.pointer, + fa.symbol, + fa.amount.toDouble(), + fa.sender, + fa.senderWalletType, + fa.receiver, + fa.receiverWalletType, + "", + "", + fa.createDate + ) + }).awaitLast() + return financialActions + } + + override suspend fun updateStatus(financialAction: FinancialAction, status: FinancialActionStatus) { + val existing = financialActionRepository.findById(financialAction.id!!).awaitFirstOrElse { + throw IllegalArgumentException() + } + financialActionRepository.save( + FinancialActionModel( + existing.id, + existing.parentId, + existing.eventType, + existing.pointer, + existing.symbol, + existing.amount, + existing.sender, + existing.senderWalletType, + existing.receiver, + existing.receiverWalletType, + existing.agent, + existing.ip, + existing.createDate, + status, + 1 + (existing.retryCount ?: 0), + LocalDateTime.now() + ) + ).awaitFirst() + } +} \ No newline at end of file diff --git a/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/impl/OrderPersisterImpl.kt b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/impl/OrderPersisterImpl.kt new file mode 100644 index 000000000..094d49e67 --- /dev/null +++ b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/impl/OrderPersisterImpl.kt @@ -0,0 +1,74 @@ +package co.nilin.opex.port.accountant.postgres.impl + +import co.nilin.opex.accountant.core.model.Order +import co.nilin.opex.accountant.core.spi.OrderPersister +import co.nilin.opex.port.accountant.postgres.dao.OrderRepository +import co.nilin.opex.port.accountant.postgres.model.OrderModel +import kotlinx.coroutines.reactive.awaitFirstOrNull +import org.springframework.stereotype.Component +import java.time.LocalDateTime + +@Component +class OrderPersisterImpl(val orderRepository: OrderRepository) : OrderPersister { + + override suspend fun load(ouid: String): Order? { + val model = orderRepository.findByOuid(ouid).awaitFirstOrNull() ?: return null + return Order( + model.pair, + model.ouid, + model.matchingEngineId, + model.makerFee, + model.takerFee, + model.leftSideFraction, + model.rightSideFraction, + model.uuid, + model.userLevel, + model.direction, + model.matchConstraint, + model.orderType, + model.price, + model.quantity, + model.filledQuantity, + model.origPrice, + model.origQuantity, + model.filledOrigQuantity, + model.firstTransferAmount, + model.remainedTransferAmount, + model.status, + model.id + ) + } + + override suspend fun save(order: Order): Order { + orderRepository.save( + OrderModel( + order.id, + order.ouid, + order.uuid, + order.pair, + order.matchingEngineId, + order.makerFee, + order.takerFee, + order.leftSideFraction, + order.rightSideFraction, + order.userLevel, + order.direction, + order.matchConstraint, + order.orderType, + order.price, + order.quantity, + order.filledQuantity, + order.origPrice, + order.origQuantity, + order.filledOrigQuantity, + order.firstTransferAmount, + order.remainedTransferAmount, + order.status, + "", + "", + LocalDateTime.now() + ) + ).awaitFirstOrNull() + return order + } +} \ No newline at end of file diff --git a/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/impl/PairConfigLoaderImpl.kt b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/impl/PairConfigLoaderImpl.kt new file mode 100644 index 000000000..5c499242c --- /dev/null +++ b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/impl/PairConfigLoaderImpl.kt @@ -0,0 +1,75 @@ +package co.nilin.opex.port.accountant.postgres.impl + +import co.nilin.opex.accountant.core.model.PairConfig +import co.nilin.opex.accountant.core.model.PairFeeConfig +import co.nilin.opex.accountant.core.spi.PairConfigLoader +import co.nilin.opex.matching.core.model.OrderDirection +import co.nilin.opex.port.accountant.postgres.dao.PairConfigRepository +import co.nilin.opex.port.accountant.postgres.dao.PairFeeConfigRepository +import co.nilin.opex.port.accountant.postgres.model.PairFeeConfigModel +import co.nilin.opex.utility.error.data.OpexError +import co.nilin.opex.utility.error.data.OpexException +import kotlinx.coroutines.reactive.awaitFirstOrElse +import kotlinx.coroutines.reactive.awaitFirstOrNull +import org.springframework.stereotype.Component + +@Component +class PairConfigLoaderImpl( + val pairConfigRepository: PairConfigRepository, val pairFeeConfigRepository: PairFeeConfigRepository +) : PairConfigLoader { + + override suspend fun loadPairConfigs(): List { + return pairConfigRepository.findAll() + .collectList() + .awaitFirstOrElse { emptyList() } + .map { + PairConfig( + it.pair, + it.leftSideWalletSymbol, + it.rightSideWalletSymbol, + it.leftSideFraction, + it.rightSideFraction + ) + } + } + + override suspend fun load(pair: String, direction: OrderDirection, userLevel: String): PairFeeConfig { + val pairConfig = pairConfigRepository + .findById(pair).awaitFirstOrElse { + val error = OpexError.InvalidPair + throw OpexException(error, String.format(error.message!!, pair)) + } + var pairFeeConfig: PairFeeConfigModel? + if (userLevel.isEmpty()) { + pairFeeConfig = pairFeeConfigRepository + .findByPairAndDirectionAndUserLevel(pair, direction, "*") + .awaitFirstOrElse { + val error = OpexError.InvalidPair + throw OpexException(error, String.format(error.message!!, pair)) + } + } else { + pairFeeConfig = pairFeeConfigRepository + .findByPairAndDirectionAndUserLevel(pair, direction, userLevel) + .awaitFirstOrNull() + if (pairFeeConfig == null) { + pairFeeConfig = pairFeeConfigRepository + .findByPairAndDirectionAndUserLevel(pair, direction, "*") + .awaitFirstOrElse { + val error = OpexError.InvalidPairFee + throw OpexException(error, String.format(error.message!!, pair)) + } + } + } + + return PairFeeConfig( + PairConfig( + pair, + pairConfig.leftSideWalletSymbol, + pairConfig.rightSideWalletSymbol, + pairConfig.leftSideFraction, + pairConfig.rightSideFraction + ), pairFeeConfig!!.direction, pairFeeConfig.userLevel, pairFeeConfig.makerFee, pairFeeConfig.takerFee + ) + + } +} \ No newline at end of file diff --git a/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/impl/PairStaticRateLoaderImpl.kt b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/impl/PairStaticRateLoaderImpl.kt new file mode 100644 index 000000000..ea4345ff9 --- /dev/null +++ b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/impl/PairStaticRateLoaderImpl.kt @@ -0,0 +1,18 @@ +package co.nilin.opex.port.accountant.postgres.impl + +import co.nilin.opex.accountant.core.spi.PairStaticRateLoader +import co.nilin.opex.port.accountant.postgres.dao.PairConfigRepository +import kotlinx.coroutines.reactive.awaitFirstOrElse +import org.springframework.stereotype.Component +import java.lang.IllegalArgumentException + +@Component +class PairStaticRateLoaderImpl(val pairConfigRepository: PairConfigRepository) : PairStaticRateLoader { + + override suspend fun calculateStaticRate(leftSide: String, rightSide: String): Double? { + val pairConfig = pairConfigRepository + .findById("${leftSide}_$rightSide") + .awaitFirstOrElse { throw IllegalArgumentException("${leftSide}_$rightSide is not available") } + return pairConfig.rate + } +} \ No newline at end of file diff --git a/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/impl/TempEventPersisterImpl.kt b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/impl/TempEventPersisterImpl.kt new file mode 100644 index 000000000..7c723b392 --- /dev/null +++ b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/impl/TempEventPersisterImpl.kt @@ -0,0 +1,67 @@ +package co.nilin.opex.port.accountant.postgres.impl + +import co.nilin.opex.accountant.core.model.TempEvent +import co.nilin.opex.accountant.core.spi.TempEventPersister +import co.nilin.opex.matching.core.eventh.events.CoreEvent +import co.nilin.opex.port.accountant.postgres.dao.TempEventRepository +import co.nilin.opex.port.accountant.postgres.model.TempEventModel +import com.google.gson.Gson +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.reactive.awaitFirstOrNull +import kotlinx.coroutines.reactive.awaitSingle +import org.springframework.data.domain.PageRequest +import org.springframework.data.domain.Sort +import org.springframework.stereotype.Component +import java.time.LocalDateTime + +@Component +class TempEventPersisterImpl(val tempEventRepository: TempEventRepository) : TempEventPersister { + + override suspend fun saveTempEvent(ouid: String, event: CoreEvent) { + tempEventRepository.save( + TempEventModel( + null, ouid, event.javaClass.name, + Gson().toJson(event), LocalDateTime.now() + ) + ).awaitSingle() + } + + override suspend fun loadTempEvents(ouid: String): List { + return tempEventRepository + .findByOuid(ouid) + .map { value: TempEventModel -> + Gson().fromJson(value.eventBody, Class.forName(value.eventType)) as CoreEvent + } + .toList() + } + + override suspend fun removeTempEvents(ouid: String) { + tempEventRepository.deleteByOuid(ouid).awaitFirstOrNull() + } + + override suspend fun removeTempEvents(tempEvents: List) { + tempEventRepository.deleteAll(tempEvents.map { event -> + TempEventModel( + event.id, event.ouid, event.eventBody.javaClass.name, + "", event.eventDate + ) + }).awaitFirstOrNull() + } + + override suspend fun fetchTempEvents( + offset: Long, + size: Long + ): List { + return tempEventRepository + .findAll(PageRequest.of(offset.toInt(), size.toInt(), Sort.by(Sort.Direction.ASC, "eventDate"))) + .map { value: TempEventModel -> + TempEvent( + value.id!!, value.ouid, Gson().fromJson(value.eventBody, Class.forName(value.eventType)) + as + CoreEvent, value.eventDate + ) + } + .toList() + } +} \ No newline at end of file diff --git a/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/model/FinancialActionModel.kt b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/model/FinancialActionModel.kt new file mode 100644 index 000000000..eefd9e7ad --- /dev/null +++ b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/model/FinancialActionModel.kt @@ -0,0 +1,29 @@ +package co.nilin.opex.port.accountant.postgres.model + +import co.nilin.opex.accountant.core.model.FinancialActionStatus +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Column +import org.springframework.data.relational.core.mapping.Table +import java.time.LocalDateTime + +@Table("fi_actions") +class FinancialActionModel( + @Id var id: Long?, + @Column("parent_id") var parentId: Long?, + @Column("event_type") val eventType: String, + val pointer: String, + val symbol: String, + @Column("amount") val amount: Double, + val sender: String, + @Column("sender_wallet_type") val senderWalletType: String, + val receiver: String, + @Column("receiver_wallet_type") val receiverWalletType: String, + val agent: String, + val ip: String, + @Column("create_date") val createDate: LocalDateTime, + val status: FinancialActionStatus = FinancialActionStatus.CREATED, + @Column("retry_count") val retryCount: Int? = null, + @Column("last_try_date") val lastTryDate: LocalDateTime? = null +) + + diff --git a/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/model/OrderModel.kt b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/model/OrderModel.kt new file mode 100644 index 000000000..8bf12cde4 --- /dev/null +++ b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/model/OrderModel.kt @@ -0,0 +1,39 @@ +package co.nilin.opex.port.accountant.postgres.model + +import co.nilin.opex.matching.core.model.MatchConstraint +import co.nilin.opex.matching.core.model.OrderDirection +import co.nilin.opex.matching.core.model.OrderType +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Column +import org.springframework.data.relational.core.mapping.Table +import java.math.BigDecimal +import java.time.LocalDateTime + +@Table("orders") +class OrderModel( + @Id var id: Long?, + val ouid: String, + val uuid: String, + val pair: String, + @Column(value = "matching_engine_id") val matchingEngineId: Long?, + @Column("maker_fee") val makerFee: Double, + @Column("taker_fee") val takerFee: Double, + @Column("left_side_fraction") val leftSideFraction: Double, + @Column("right_side_fraction") val rightSideFraction: Double, + @Column("user_level") val userLevel: String, + @Column("direction") val direction: OrderDirection, + @Column("match_constraint") val matchConstraint: MatchConstraint, + @Column("order_type") val orderType: OrderType, + @Column("price") val price: Long, + @Column("quantity") val quantity: Long, + @Column("filled_quantity") val filledQuantity: Long, + @Column("orig_price") val origPrice: BigDecimal, + @Column("orig_quantity") val origQuantity: BigDecimal, + @Column("filled_orig_quantity") val filledOrigQuantity: BigDecimal, + @Column("first_transfer_amount") val firstTransferAmount: BigDecimal, + @Column("remained_transfer_amount") val remainedTransferAmount: BigDecimal, + @Column("status") val status: Int, + val agent: String, + val ip: String, + @Column("create_date") val createDate: LocalDateTime +) \ No newline at end of file diff --git a/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/model/PairConfigModel.kt b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/model/PairConfigModel.kt new file mode 100644 index 000000000..c0c0726df --- /dev/null +++ b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/model/PairConfigModel.kt @@ -0,0 +1,15 @@ +package co.nilin.opex.port.accountant.postgres.model + +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Column +import org.springframework.data.relational.core.mapping.Table + +@Table("pair_config") +data class PairConfigModel( + @Id val pair: String, + @Column("left_side_wallet_symbol") val leftSideWalletSymbol: String, //can be same as pair left side + @Column("right_side_wallet_symbol") val rightSideWalletSymbol: String, //can be same as pair right side + @Column("left_side_fraction") val leftSideFraction: Double, + @Column("right_side_fraction") val rightSideFraction: Double, + @Column("rate") val rate: Double +) \ No newline at end of file diff --git a/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/model/PairFeeConfigModel.kt b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/model/PairFeeConfigModel.kt new file mode 100644 index 000000000..8426f4190 --- /dev/null +++ b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/model/PairFeeConfigModel.kt @@ -0,0 +1,14 @@ +package co.nilin.opex.port.accountant.postgres.model + +import org.springframework.data.relational.core.mapping.Column +import org.springframework.data.relational.core.mapping.Table + +@Table("pair_fee_config") +class PairFeeConfigModel( + val id: Long?, + @Column("pair_config_id") val pairConfigId: String, + @Column("direction") val direction: String?, + @Column("user_level") val userLevel: String?, + @Column("maker_fee") val makerFee: Double, + @Column("taker_fee") val takerFee: Double +) \ No newline at end of file diff --git a/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/model/TempEventModel.kt b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/model/TempEventModel.kt new file mode 100644 index 000000000..61ce47ec2 --- /dev/null +++ b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/model/TempEventModel.kt @@ -0,0 +1,16 @@ +package co.nilin.opex.port.accountant.postgres.model + +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Column +import org.springframework.data.relational.core.mapping.Table +import java.time.LocalDateTime + +@Table("temp_events") +data class TempEventModel( + @Id val id: Long?, + val ouid: String, + @Column("event_type") val eventType: String, + @Column("event_body") val eventBody: String, + @Column("event_date") val eventDate: LocalDateTime +) { +} \ No newline at end of file diff --git a/Accountant/accountant-ports/accountant-submitter-kafka/.gitignore b/Accountant/accountant-ports/accountant-submitter-kafka/.gitignore new file mode 100644 index 000000000..d98fa6485 --- /dev/null +++ b/Accountant/accountant-ports/accountant-submitter-kafka/.gitignore @@ -0,0 +1,50 @@ +# Created by .ignore support plugin (hsz.mobi) +### Kotlin template +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +.idea +*.iml +*.ipr + +# File-based project format +*.iws + +# IntelliJ +target/ + +.DS_Store + + + + diff --git a/Accountant/accountant-ports/accountant-submitter-kafka/mvnw b/Accountant/accountant-ports/accountant-submitter-kafka/mvnw new file mode 100644 index 000000000..a16b5431b --- /dev/null +++ b/Accountant/accountant-ports/accountant-submitter-kafka/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/Accountant/accountant-ports/accountant-submitter-kafka/mvnw.cmd b/Accountant/accountant-ports/accountant-submitter-kafka/mvnw.cmd new file mode 100644 index 000000000..c8d43372c --- /dev/null +++ b/Accountant/accountant-ports/accountant-submitter-kafka/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/Accountant/accountant-ports/accountant-submitter-kafka/pom.xml b/Accountant/accountant-ports/accountant-submitter-kafka/pom.xml new file mode 100644 index 000000000..f7d3e70ab --- /dev/null +++ b/Accountant/accountant-ports/accountant-submitter-kafka/pom.xml @@ -0,0 +1,120 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.4 + + + co.nilin.opex + accountant-submitter-kafka + 1.0-SNAPSHOT + accountant-temp-submitter-kafka + Accountant kafka event submitter of Opex + + + 1.8 + 1.4.31 + ${version} + ${version} + + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-webflux + + + co.nilin.opex + matching-core + ${matching.version} + provided + + + co.nilin.opex + accountant-core + ${accountant.version} + provided + + + org.springframework.kafka + spring-kafka + + + io.projectreactor.kotlin + reactor-kotlin-extensions + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + + + org.jetbrains.kotlinx + kotlinx-coroutines-core + + + org.springframework.kafka + spring-kafka-test + test + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + -Xjsr305=strict + + + spring + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + + diff --git a/Accountant/accountant-ports/accountant-submitter-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/config/SubmitterKafkaConfig.kt b/Accountant/accountant-ports/accountant-submitter-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/config/SubmitterKafkaConfig.kt new file mode 100644 index 000000000..f643d6bc7 --- /dev/null +++ b/Accountant/accountant-ports/accountant-submitter-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/config/SubmitterKafkaConfig.kt @@ -0,0 +1,76 @@ +package co.nilin.opex.port.accountant.kafka.config + +import co.nilin.opex.accountant.core.inout.RichOrder +import co.nilin.opex.accountant.core.inout.RichTrade +import co.nilin.opex.matching.core.eventh.events.CoreEvent +import org.apache.kafka.clients.admin.NewTopic +import org.apache.kafka.clients.producer.ProducerConfig +import org.apache.kafka.common.serialization.StringSerializer +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.context.support.GenericApplicationContext +import org.springframework.kafka.core.DefaultKafkaProducerFactory +import org.springframework.kafka.core.KafkaTemplate +import org.springframework.kafka.core.ProducerFactory +import org.springframework.kafka.support.serializer.JsonSerializer + +@Configuration +class SubmitterKafkaConfig() { + @Value("\${spring.kafka.bootstrap-servers}") + private lateinit var bootstrapServers: String + + @Value("\${spring.kafka.consumer.group-id}") + private val groupId: String? = null + + @Autowired + private val applicationContext: GenericApplicationContext? = null + + + @Bean("accountantProducerConfigs") + fun producerConfigs(): Map? { + val props: MutableMap = HashMap() + props[ProducerConfig.BOOTSTRAP_SERVERS_CONFIG] = bootstrapServers + props[ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG] = StringSerializer::class.java + props[ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG] = JsonSerializer::class.java + return props + } + + @Bean("accountantEventProducerFactory") + fun producerFactory(@Qualifier("accountantProducerConfigs") producerConfigs: Map): ProducerFactory { + return DefaultKafkaProducerFactory(producerConfigs) + } + + @Bean("accountantEventKafkaTemplate") + fun kafkaTemplate(@Qualifier("accountantEventProducerFactory") producerFactory: ProducerFactory): KafkaTemplate { + return KafkaTemplate(producerFactory) + } + + @Bean("richTradeProducerFactory") + fun richTradeProducerFactory(@Qualifier("accountantProducerConfigs") producerConfigs: Map): ProducerFactory { + return DefaultKafkaProducerFactory(producerConfigs) + } + + @Bean("richTradeKafkaTemplate") + fun richTradeKafkaTemplate(@Qualifier("richTradeProducerFactory") producerFactory: ProducerFactory): KafkaTemplate { + return KafkaTemplate(producerFactory) + } + + @Bean("richOrderProducerFactory") + fun richOrderProducerFactory(@Qualifier("accountantProducerConfigs") producerConfigs: Map): ProducerFactory { + return DefaultKafkaProducerFactory(producerConfigs) + } + + @Bean("richOrderKafkaTemplate") + fun richOrderKafkaTemplate(@Qualifier("richOrderProducerFactory") producerFactory: ProducerFactory): KafkaTemplate { + return KafkaTemplate(producerFactory) + } + + @Autowired + fun createTopics(applicationContext: GenericApplicationContext) { + applicationContext.registerBean("topic_richOrder", NewTopic::class.java, "richOrder", 10, 1) + applicationContext.registerBean("topic_richTrade", NewTopic::class.java, "richTrade", 10, 1) + } +} \ No newline at end of file diff --git a/Accountant/accountant-ports/accountant-submitter-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/service/RichOrderSubmitter.kt b/Accountant/accountant-ports/accountant-submitter-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/service/RichOrderSubmitter.kt new file mode 100644 index 000000000..52c0ecf22 --- /dev/null +++ b/Accountant/accountant-ports/accountant-submitter-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/service/RichOrderSubmitter.kt @@ -0,0 +1,24 @@ +package co.nilin.opex.port.accountant.kafka.service + +import co.nilin.opex.accountant.core.inout.RichOrder +import co.nilin.opex.accountant.core.spi.RichOrderPublisher +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.kafka.core.KafkaTemplate +import org.springframework.stereotype.Component +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + +@Component +class RichOrderSubmitter(@Qualifier("richOrderKafkaTemplate") val kafkaTemplate: KafkaTemplate) : + RichOrderPublisher { + override suspend fun publish(order: RichOrder): Unit = suspendCoroutine { cont -> + println("richOrderSubmit!") + val sendFuture = kafkaTemplate.send("richOrder", order) + sendFuture.addCallback({ sendResult -> + cont.resume(Unit) + }, { exception -> + cont.resumeWithException(exception) + }) + } +} \ No newline at end of file diff --git a/Accountant/accountant-ports/accountant-submitter-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/service/RichTradeSubmitter.kt b/Accountant/accountant-ports/accountant-submitter-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/service/RichTradeSubmitter.kt new file mode 100644 index 000000000..0336e91b7 --- /dev/null +++ b/Accountant/accountant-ports/accountant-submitter-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/service/RichTradeSubmitter.kt @@ -0,0 +1,24 @@ +package co.nilin.opex.port.accountant.kafka.service + +import co.nilin.opex.accountant.core.inout.RichTrade +import co.nilin.opex.accountant.core.spi.RichTradePublisher +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.kafka.core.KafkaTemplate +import org.springframework.stereotype.Component +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + +@Component +class RichTradeSubmitter(@Qualifier("richTradeKafkaTemplate") val kafkaTemplate: KafkaTemplate) : + RichTradePublisher { + override suspend fun publish(trade: RichTrade): Unit = suspendCoroutine { cont -> + println("richTradeSubmit!") + val sendFuture = kafkaTemplate.send("richTrade", trade) + sendFuture.addCallback({ sendResult -> + cont.resume(Unit) + }, { exception -> + cont.resumeWithException(exception) + }) + } +} \ No newline at end of file diff --git a/Accountant/accountant-ports/accountant-submitter-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/service/TempEventSubmitter.kt b/Accountant/accountant-ports/accountant-submitter-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/service/TempEventSubmitter.kt new file mode 100644 index 000000000..4c4c0a977 --- /dev/null +++ b/Accountant/accountant-ports/accountant-submitter-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/service/TempEventSubmitter.kt @@ -0,0 +1,26 @@ +package co.nilin.opex.port.accountant.kafka.service + +import co.nilin.opex.accountant.core.spi.TempEventRepublisher +import co.nilin.opex.matching.core.eventh.events.CoreEvent +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.kafka.core.KafkaTemplate +import org.springframework.stereotype.Component +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + +@Component +class TempEventSubmitter(@Qualifier("accountantEventKafkaTemplate") val kafkaTemplate: KafkaTemplate) : + TempEventRepublisher { + override suspend fun republish(events: List): Unit = suspendCoroutine { cont -> + println("accountantEventSubmit!") + events.forEach { event -> + val sendFuture = kafkaTemplate.send("tempevents", event) + sendFuture.addCallback({ sendResult -> + cont.resume(Unit) + }, { exception -> + cont.resumeWithException(exception) + }) + } + } +} \ No newline at end of file diff --git a/Accountant/accountant-ports/accountant-wallet-proxy/.gitignore b/Accountant/accountant-ports/accountant-wallet-proxy/.gitignore new file mode 100644 index 000000000..f3a8317d6 --- /dev/null +++ b/Accountant/accountant-ports/accountant-wallet-proxy/.gitignore @@ -0,0 +1,4 @@ +*.iml +target/ +.mvn/ +.idea/ \ No newline at end of file diff --git a/Accountant/accountant-ports/accountant-wallet-proxy/mvnw b/Accountant/accountant-ports/accountant-wallet-proxy/mvnw new file mode 100644 index 000000000..a16b5431b --- /dev/null +++ b/Accountant/accountant-ports/accountant-wallet-proxy/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/Accountant/accountant-ports/accountant-wallet-proxy/mvnw.cmd b/Accountant/accountant-ports/accountant-wallet-proxy/mvnw.cmd new file mode 100644 index 000000000..c8d43372c --- /dev/null +++ b/Accountant/accountant-ports/accountant-wallet-proxy/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/Accountant/accountant-ports/accountant-wallet-proxy/pom.xml b/Accountant/accountant-ports/accountant-wallet-proxy/pom.xml new file mode 100644 index 000000000..d536d061c --- /dev/null +++ b/Accountant/accountant-ports/accountant-wallet-proxy/pom.xml @@ -0,0 +1,146 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.4 + + + co.nilin.opex + accountant-wallet-proxy + 1.0-SNAPSHOT + accountant-wallet-proxy + Opex wallet proxy + + + 1.8 + 1.4.31 + 2020.0.2 + ${version} + ${version} + ${version} + + + + + co.nilin.opex + matching-core + ${matching.version} + provided + + + co.nilin.opex + accountant-core + ${accountant.version} + provided + + + co.nilin.opex + logging-handler + ${utility.version} + provided + + + org.springframework.boot + spring-boot-starter-webflux + + + org.springframework.cloud + spring-cloud-starter-consul-all + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.boot + spring-boot-starter-data-r2dbc + + + io.r2dbc + r2dbc-postgresql + runtime + + + org.postgresql + postgresql + runtime + + + io.projectreactor.kotlin + reactor-kotlin-extensions + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + + + + org.jetbrains.kotlinx + kotlinx-coroutines-core + + + io.projectreactor + reactor-test + test + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + -Xjsr305=strict + + + spring + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + diff --git a/Accountant/accountant-ports/accountant-wallet-proxy/src/main/kotlin/co/nilin/opex/port/accountant/wallet/config/WebClientConfig.kt b/Accountant/accountant-ports/accountant-wallet-proxy/src/main/kotlin/co/nilin/opex/port/accountant/wallet/config/WebClientConfig.kt new file mode 100644 index 000000000..ce2bca7fd --- /dev/null +++ b/Accountant/accountant-ports/accountant-wallet-proxy/src/main/kotlin/co/nilin/opex/port/accountant/wallet/config/WebClientConfig.kt @@ -0,0 +1,38 @@ +package co.nilin.opex.port.accountant.wallet.config + +import co.nilin.opex.utility.log.interceptor.CustomLogger +import org.springframework.cloud.client.ServiceInstance +import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties +import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer +import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerExchangeFilterFunction +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.http.client.reactive.ReactorClientHttpConnector +import org.springframework.web.reactive.function.client.WebClient +import reactor.netty.http.client.HttpClient + +@Configuration +class WebClientConfig { + + @Bean + fun webClient(loadBalancerFactory: ReactiveLoadBalancer.Factory): WebClient { + val logger = CustomLogger(HttpClient::class.java) + return WebClient.builder() + .clientConnector( + ReactorClientHttpConnector( + HttpClient + .create() + .doOnRequest { request, connection -> + connection.addHandlerFirst(logger) + } + ) + ) + .filter( + ReactorLoadBalancerExchangeFilterFunction( + loadBalancerFactory, LoadBalancerProperties(), emptyList() + ) + ) + .build() + } + +} diff --git a/Accountant/accountant-ports/accountant-wallet-proxy/src/main/kotlin/co/nilin/opex/port/accountant/wallet/proxy/WalletProxyImpl.kt b/Accountant/accountant-ports/accountant-wallet-proxy/src/main/kotlin/co/nilin/opex/port/accountant/wallet/proxy/WalletProxyImpl.kt new file mode 100644 index 000000000..480ee1f1d --- /dev/null +++ b/Accountant/accountant-ports/accountant-wallet-proxy/src/main/kotlin/co/nilin/opex/port/accountant/wallet/proxy/WalletProxyImpl.kt @@ -0,0 +1,67 @@ +package co.nilin.opex.port.accountant.wallet.proxy + +import co.nilin.opex.accountant.core.spi.WalletProxy +import kotlinx.coroutines.reactive.awaitFirst +import org.springframework.beans.factory.annotation.Value +import org.springframework.core.ParameterizedTypeReference +import org.springframework.stereotype.Component +import org.springframework.web.reactive.function.client.WebClient +import java.math.BigDecimal +import java.net.URI +import java.time.LocalDateTime + +inline fun typeRef(): ParameterizedTypeReference = object : ParameterizedTypeReference() {} +data class TransferResult( + val date: Long, + val sourceBalanceBeforeAction: Amount, + val sourceBalanceAfterAction: Amount, + val amount: Amount +) + +data class Amount(val currency: Currency, val amount: BigDecimal) +data class Currency(val name: String, val symbol: String, val precision: Int) + +@Component +class WalletProxyImpl( + @Value("\${app.wallet.url}") val walletBaseUrl: String, val webClient: WebClient +) : WalletProxy { + override suspend fun transfer( + symbol: String, + senderWalletType: String, + senderUuid: String, + receiverWalletType: String, + receiverUuid: String, + amount: BigDecimal, + description: String?, + transferRef: String? + ) { + webClient.post() + .uri(URI.create("$walletBaseUrl/transfer/${amount}_${symbol}/from/${senderUuid}_${senderWalletType}/to/${receiverUuid}_${receiverWalletType}")) + .header("Content-Type", "application/json") + .retrieve() + .onStatus({ t -> t.isError }, { p -> + /* + p.bodyToMono(typeRef>()).map { t -> KycSejamException(p.statusCode().value().toString(), t.error?.errorCode.toString() + + "-" + t.error?.customMessage) } + */ + throw RuntimeException() + }) + .bodyToMono(typeRef()) + .log() + .awaitFirst() + + } + + override suspend fun canFulfil(symbol: String, walletType: String, uuid: String, amount: BigDecimal): Boolean { + data class BooleanResponse(val result: Boolean) + return webClient.get() + .uri(URI.create("$walletBaseUrl/$uuid/wallet_type/${walletType}/can_withdraw/${amount}_${symbol}")) + .header("Content-Type", "application/json") + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToMono(typeRef()) + .log() + .awaitFirst() + .result + } +} \ No newline at end of file diff --git a/Accountant/pom.xml b/Accountant/pom.xml new file mode 100644 index 000000000..436f574ac --- /dev/null +++ b/Accountant/pom.xml @@ -0,0 +1,19 @@ + + + 4.0.0 + co.nilin.opex + accountant-root + 1.0-SNAPSHOT + accountant-root + pom + Accountant root of Opex + + accountant-core + accountant-app + accountant-ports/accountant-persister-postgres + accountant-ports/accountant-wallet-proxy + accountant-ports/accountant-submitter-kafka + accountant-ports/accountant-eventlistener-kafka + + diff --git a/Api/.gitignore b/Api/.gitignore new file mode 100644 index 000000000..d6953c444 --- /dev/null +++ b/Api/.gitignore @@ -0,0 +1,3 @@ +*.iml +.idea +/.idea/ diff --git a/Api/.idea/.gitignore b/Api/.idea/.gitignore new file mode 100644 index 000000000..73f69e095 --- /dev/null +++ b/Api/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/Api/api-app/.gitignore b/Api/api-app/.gitignore new file mode 100644 index 000000000..e7c460346 --- /dev/null +++ b/Api/api-app/.gitignore @@ -0,0 +1,80 @@ +# Created by .ignore support plugin (hsz.mobi) +### Kotlin template +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +.idea/artifacts +.idea/compiler.xml +.idea/jarRepositories.xml +.idea/modules.xml +.idea/*.iml +.idea/modules +*.iml +*.ipr +.mvn/ + +# File-based project format +*.iws + +# IntelliJ +out/ + +target/ + +.DS_Store + + + + diff --git a/Api/api-app/Dockerfile b/Api/api-app/Dockerfile new file mode 100644 index 000000000..f2cbd4c26 --- /dev/null +++ b/Api/api-app/Dockerfile @@ -0,0 +1,5 @@ +FROM openjdk:8-jdk-alpine +VOLUME /tmp +ARG JAR_FILE=target/*.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"] \ No newline at end of file diff --git a/Api/api-app/pom.xml b/Api/api-app/pom.xml new file mode 100644 index 000000000..f430eb9c5 --- /dev/null +++ b/Api/api-app/pom.xml @@ -0,0 +1,213 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.4 + + + co.nilin.opex + api-app + 1.0-SNAPSHOT + api-app + Api app Opex + + + 1.8 + 1.4.31 + ${version} + ${version} + ${version} + ${version} + + + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + com.fasterxml.jackson.module + jackson-module-kotlin + + + org.springframework.boot + spring-boot-starter + + + co.nilin.opex + logging-handler + ${utility.version} + + + co.nilin.opex + error-handler + ${utility.version} + + + co.nilin.opex + interceptors + ${utility.version} + provided + + + co.nilin.opex + accountant-core + ${accountant.version} + + + co.nilin.opex + api-core + ${api.version} + + + co.nilin.opex + api-eventlistener-kafka + ${api.version} + + + co.nilin.opex + api-binance-rest + ${api.version} + + + co.nilin.opex + api-persister-postgres + ${api.version} + + + io.springfox + springfox-boot-starter + 3.0.0 + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + 2.18 + + + ${skip.unit.tests} + + + **/*IntegrationTest.java + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-test-source + generate-test-sources + + add-test-source + + + + src/test/java + + + + + compile + + add-source + + + + src/main/java + + + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + compile + + + + test-compile + test-compile + + test-compile + + + + + + -Xjsr305=strict + + + spring + + 1.8 + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + compile + compile + + compile + + + + testCompile + test-compile + + testCompile + + + + + + opex-accountant + + diff --git a/Api/api-app/src/main/kotlin/co/nilin/opex/app/ApiApp.kt b/Api/api-app/src/main/kotlin/co/nilin/opex/app/ApiApp.kt new file mode 100644 index 000000000..338e2bab1 --- /dev/null +++ b/Api/api-app/src/main/kotlin/co/nilin/opex/app/ApiApp.kt @@ -0,0 +1,29 @@ +package co.nilin.opex.app + +import org.springframework.beans.factory.annotation.Value +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.ComponentScan +import org.springframework.security.core.annotation.AuthenticationPrincipal +import springfox.documentation.builders.* +import springfox.documentation.builders.PathSelectors.regex +import springfox.documentation.service.* +import springfox.documentation.spi.DocumentationType +import springfox.documentation.spi.service.contexts.SecurityContext +import springfox.documentation.spring.web.plugins.Docket +import springfox.documentation.swagger.web.SecurityConfiguration +import springfox.documentation.swagger.web.SecurityConfigurationBuilder +import springfox.documentation.swagger2.annotations.EnableSwagger2 +import java.security.Principal +import java.util.Collections.singletonList + + +@SpringBootApplication +@ComponentScan("co.nilin.opex") +@EnableSwagger2 +class ApiApp + +fun main(args: Array) { + runApplication(*args) +} diff --git a/Api/api-app/src/main/kotlin/co/nilin/opex/app/config/AppConfig.kt b/Api/api-app/src/main/kotlin/co/nilin/opex/app/config/AppConfig.kt new file mode 100644 index 000000000..d544cd605 --- /dev/null +++ b/Api/api-app/src/main/kotlin/co/nilin/opex/app/config/AppConfig.kt @@ -0,0 +1,71 @@ +package co.nilin.opex.app.config + +import co.nilin.opex.accountant.core.inout.RichOrder +import co.nilin.opex.accountant.core.inout.RichTrade +import co.nilin.opex.api.core.spi.OrderPersister +import co.nilin.opex.api.core.spi.TradePersister +import co.nilin.opex.port.api.kafka.consumer.OrderKafkaListener +import co.nilin.opex.port.api.kafka.spi.RichOrderListener +import co.nilin.opex.port.api.kafka.consumer.TradeKafkaListener +import co.nilin.opex.port.api.kafka.spi.RichTradeListener +import kotlinx.coroutines.runBlocking +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +class AppConfig { + + @Bean + fun apiListener( + richOrderPersister: OrderPersister, + richTradePersister: TradePersister + ): ApiListenerImpl { + return ApiListenerImpl(richOrderPersister, richTradePersister) + } + + @Autowired + fun configureListeners( + orderKafkaListener: OrderKafkaListener, + tradeKafkaListener: TradeKafkaListener, + appListener: ApiListenerImpl + ) { + orderKafkaListener.addOrderListener(appListener) + tradeKafkaListener.addTradeListener(appListener) + } + + class ApiListenerImpl( + val richOrderPersister: OrderPersister, + val richTradePersister: TradePersister + ) : RichTradeListener, RichOrderListener { + + override fun id(): String { + return "AppListener" + } + + override fun onTrade( + trade: RichTrade, + partition: Int, + offset: Long, + timestamp: Long + ) { + println("RichTrade received") + runBlocking(AppDispatchers.kafkaExecutor) { + richTradePersister.save(trade) + } + } + + override fun onOrder( + order: RichOrder, + partition: Int, + offset: Long, + timestamp: Long + ) { + runBlocking(AppDispatchers.kafkaExecutor) { + richOrderPersister.save(order) + } + } + } + + +} \ No newline at end of file diff --git a/Api/api-app/src/main/kotlin/co/nilin/opex/app/config/AppDispatchers.kt b/Api/api-app/src/main/kotlin/co/nilin/opex/app/config/AppDispatchers.kt new file mode 100644 index 000000000..a63f2d6a6 --- /dev/null +++ b/Api/api-app/src/main/kotlin/co/nilin/opex/app/config/AppDispatchers.kt @@ -0,0 +1,9 @@ +package co.nilin.opex.app.config + +import kotlinx.coroutines.asCoroutineDispatcher +import java.util.concurrent.Executors + +object AppDispatchers { + val apiExecutor = Executors.newSingleThreadExecutor().asCoroutineDispatcher() + val kafkaExecutor = Executors.newSingleThreadExecutor().asCoroutineDispatcher() +} \ No newline at end of file diff --git a/Api/api-app/src/main/kotlin/co/nilin/opex/app/config/CorsConfig.kt b/Api/api-app/src/main/kotlin/co/nilin/opex/app/config/CorsConfig.kt new file mode 100644 index 000000000..e6e7a3480 --- /dev/null +++ b/Api/api-app/src/main/kotlin/co/nilin/opex/app/config/CorsConfig.kt @@ -0,0 +1,21 @@ +package co.nilin.opex.app.config + +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Configuration +import org.springframework.web.reactive.config.CorsRegistry +import org.springframework.web.reactive.config.WebFluxConfigurer + +@Configuration +class CorsConfig : WebFluxConfigurer { + + @Value("\${app.cors.allowed-hosts}") + private lateinit var hosts: Array + + override fun addCorsMappings(registry: CorsRegistry) { + registry.addMapping("/**") + .allowedOrigins(*hosts) + .allowedHeaders("*") + .allowedMethods("*") + } + +} \ No newline at end of file diff --git a/Api/api-app/src/main/kotlin/co/nilin/opex/app/config/SwaggerConfig.kt b/Api/api-app/src/main/kotlin/co/nilin/opex/app/config/SwaggerConfig.kt new file mode 100644 index 000000000..c9fe3d51b --- /dev/null +++ b/Api/api-app/src/main/kotlin/co/nilin/opex/app/config/SwaggerConfig.kt @@ -0,0 +1,97 @@ +package co.nilin.opex.app.config + +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.security.core.annotation.AuthenticationPrincipal +import springfox.documentation.builders.ApiInfoBuilder +import springfox.documentation.builders.OAuthBuilder +import springfox.documentation.builders.PathSelectors +import springfox.documentation.builders.RequestParameterBuilder +import springfox.documentation.service.* +import springfox.documentation.spi.DocumentationType +import springfox.documentation.spi.service.contexts.SecurityContext +import springfox.documentation.spring.web.plugins.Docket +import springfox.documentation.swagger.web.SecurityConfiguration +import springfox.documentation.swagger.web.SecurityConfigurationBuilder +import java.security.Principal +import java.util.* + +@Configuration +class SwaggerConfig { + @Value("\${swagger.authUrl}") + val authUrl: String = "" + + @Bean + fun opexApi(): Docket { + return Docket(DocumentationType.SWAGGER_2) + .groupName("opex-api") + .apiInfo(apiInfo()) + .select() + .paths(PathSelectors.regex("^/api/v3.*")) + .build() + .globalRequestParameters( + Collections.singletonList( + RequestParameterBuilder() + .name("content-type") + .description("content-type") + .`in`(ParameterType.HEADER) + .required(true) + .build() + ) + ) + .ignoredParameterTypes(AuthenticationPrincipal::class.java, Principal::class.java) + .useDefaultResponseMessages(false) + .securitySchemes(Collections.singletonList(oauth())) + .securityContexts(Collections.singletonList(securityContext())) + } + + private fun apiInfo(): ApiInfo { + return ApiInfoBuilder() + .title("OPEX API") + .description("Backend for opex exchange.") + .license("MIT License") + .licenseUrl("https://github.com/opexdev/Back-end/blob/feature/1-MVP/LICENSE") + .version("0.1") + .build() + } + + private fun oauth(): SecurityScheme { + return OAuthBuilder() + .name("opex") + .grantTypes(grantTypes()) + .scopes(scopes()) + .build() + } + + private fun scopes(): List { + return listOf(AuthorizationScope("openid", "OpenId")) + } + + private fun grantTypes(): List { + val tokenUrl = "$authUrl/auth/realms/opex/protocol/openid-connect/token" + val grantType = ResourceOwnerPasswordCredentialsGrant(tokenUrl) + return Collections.singletonList(grantType) + } + + private fun securityContext(): SecurityContext { + val securityReference = SecurityReference.builder() + .reference("opex") + .scopes(emptyArray()) + .build() + return SecurityContext.builder() + .securityReferences(Collections.singletonList(securityReference)) + .operationSelector { true } + .build() + } + + @Bean + fun securityInfo(): SecurityConfiguration { + return SecurityConfigurationBuilder.builder() + .clientId("admin-cli") + .realm("opex") + .appName("opex") + .scopeSeparator(",") + .build() + } +} \ No newline at end of file diff --git a/Api/api-app/src/main/resources/application-docker.yml b/Api/api-app/src/main/resources/application-docker.yml new file mode 100644 index 000000000..2c3e93788 --- /dev/null +++ b/Api/api-app/src/main/resources/application-docker.yml @@ -0,0 +1,25 @@ +spring: + kafka: + bootstrap-servers: ${KAFKA_IP_PORT} + redis: + host: ${REDIS_HOST} + r2dbc: + url: r2dbc:postgresql://${DB_IP_PORT}/opex_api + username: opex + password: hiopex + cloud: + consul: + host: ${CONSUL_HOST} + port: 8500 + +app: + cors: + allowed-hosts: https://opex.dev, http://localhost:3000 + accountant: + url: lb://opex-accountant + matching-gateway: + url: lb://opex-gateway + wallet: + url: lb://opex-wallet + auth: + cert-url: lb://opex-auth/auth/realms/opex/protocol/openid-connect/certs diff --git a/Api/api-app/src/main/resources/application.yml b/Api/api-app/src/main/resources/application.yml new file mode 100644 index 000000000..c1517ab0d --- /dev/null +++ b/Api/api-app/src/main/resources/application.yml @@ -0,0 +1,42 @@ +server.port: 8094 +logging: + level: + co.nilin: DEBUG + reactor.netty.http.client: DEBUG +spring: + application: + name: opex-api + main: + allow-bean-definition-overriding: false + kafka: + bootstrap-servers: 192.168.178.29:9092 + consumer: + group-id: api + redis: + host: 127.0.0.1 + port: 6379 + r2dbc: + url: r2dbc:postgresql://localhost/opex_api + username: opex + password: hiopex + initialization-mode: always + cloud: + bootstrap: + enabled: true + consul: + port: 8500 + discovery: + #healthCheckPath: ${management.context-path}/health + instance-id: ${spring.application.name}:${server.port} + healthCheckInterval: 20s + prefer-ip-address: true + +app: + accountant: + url: lb://opex-accountant + matching-gateway: + url: lb://opex-gateway + wallet: + url: lb://opex-wallet + +swagger.authUrl: https://api.opex.dev \ No newline at end of file diff --git a/Api/api-core/.gitignore b/Api/api-core/.gitignore new file mode 100644 index 000000000..f3a8317d6 --- /dev/null +++ b/Api/api-core/.gitignore @@ -0,0 +1,4 @@ +*.iml +target/ +.mvn/ +.idea/ \ No newline at end of file diff --git a/Api/api-core/mvnw b/Api/api-core/mvnw new file mode 100644 index 000000000..3c8a55373 --- /dev/null +++ b/Api/api-core/mvnw @@ -0,0 +1,322 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ]; then + + if [ -f /etc/mavenrc ]; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ]; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false +darwin=false +mingw=false +case "$(uname)" in +CYGWIN*) cygwin=true ;; +MINGW*) mingw=true ;; +Darwin*) + darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="$(/usr/libexec/java_home)" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ]; then + if [ -r /etc/gentoo-release ]; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +if [ -z "$M2_HOME" ]; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ]; do + ls=$(ls -ld "$PRG") + link=$(expr "$ls" : '.*-> \(.*\)$') + if expr "$link" : '/.*' >/dev/null; then + PRG="$link" + else + PRG="$(dirname "$PRG")/$link" + fi + done + + saveddir=$(pwd) + + M2_HOME=$(dirname "$PRG")/.. + + # make it fully qualified + M2_HOME=$(cd "$M2_HOME" && pwd) + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=$(cygpath --unix "$M2_HOME") + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw; then + [ -n "$M2_HOME" ] && + M2_HOME="$( ( + cd "$M2_HOME" + pwd + ))" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="$( ( + cd "$JAVA_HOME" + pwd + ))" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr \"$javaExecutable\" : '\([^ ]*\)')" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=$(which readlink) + if [ ! $(expr "$readLink" : '\([^ ]*\)') = "no" ]; then + if $darwin; then + javaHome="$(dirname \"$javaExecutable\")" + javaExecutable="$(cd \"$javaHome\" && pwd -P)/javac" + else + javaExecutable="$(readlink -f \"$javaExecutable\")" + fi + javaHome="$(dirname \"$javaExecutable\")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ]; then + if [ -n "$JAVA_HOME" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="$(which java)" + fi +fi + +if [ ! -x "$JAVACMD" ]; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ]; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ]; then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ]; do + if [ -d "$wdir"/.mvn ]; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$( + cd "$wdir/.." + pwd + ) + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' <"$1")" + fi +} + +BASE_DIR=$(find_maven_basedir "$(pwd)") +if [ -z "$BASE_DIR" ]; then + exit 1 +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in wrapperUrl) + jarUrl="$value" + break + ;; + esac + done <"$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget >/dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl >/dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=$(cygpath --path --windows "$M2_HOME") + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/Api/api-core/mvnw.cmd b/Api/api-core/mvnw.cmd new file mode 100644 index 000000000..c8d43372c --- /dev/null +++ b/Api/api-core/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/Api/api-core/pom.xml b/Api/api-core/pom.xml new file mode 100644 index 000000000..b8ded6631 --- /dev/null +++ b/Api/api-core/pom.xml @@ -0,0 +1,104 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.4 + + + co.nilin.opex + api-core + 1.0-SNAPSHOT + api-core + Api logic of Opex + + + 1.8 + 1.4.31 + ${version} + ${version} + + + + + org.springframework.boot + spring-boot-starter + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + io.projectreactor.kotlin + reactor-kotlin-extensions + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + + + org.jetbrains.kotlinx + kotlinx-coroutines-core + + + co.nilin.opex + matching-core + ${matching.version} + + + co.nilin.opex + accountant-core + ${accountant.version} + provided + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + org.springframework + spring-tx + provided + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + -Xjsr305=strict + + + spring + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + + + diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/AggregatedOrderPriceModel.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/AggregatedOrderPriceModel.kt new file mode 100644 index 000000000..4537ff914 --- /dev/null +++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/AggregatedOrderPriceModel.kt @@ -0,0 +1,6 @@ +package co.nilin.opex.api.core.inout + +data class AggregatedOrderPriceModel( + val price: Double?, + val quantity: Double? +) \ No newline at end of file diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/AllOrderRequest.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/AllOrderRequest.kt new file mode 100644 index 000000000..6eb74b1f1 --- /dev/null +++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/AllOrderRequest.kt @@ -0,0 +1,10 @@ +package co.nilin.opex.api.core.inout + +import java.util.* + +class AllOrderRequest( + val symbol: String?, + val startTime: Date?, + val endTime: Date?, + val limit: Int? = 500, //Default 500; max 1000. +) \ No newline at end of file diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/CancelOrderRequest.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/CancelOrderRequest.kt new file mode 100644 index 000000000..874e5ff99 --- /dev/null +++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/CancelOrderRequest.kt @@ -0,0 +1,3 @@ +package co.nilin.opex.api.core.inout + +class CancelOrderRequest(val ouid: String, val uuid: String, val orderId: Long, val symbol: String) \ No newline at end of file diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/CreateOrderRequest.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/CreateOrderRequest.kt new file mode 100644 index 000000000..7b51525c6 --- /dev/null +++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/CreateOrderRequest.kt @@ -0,0 +1,19 @@ +package co.nilin.opex.api.core.inout + +import java.math.BigDecimal + +data class CreateOrderRequest( + val symbol: String, + val side: OrderSide, + val type: OrderType, + val timeInForce: TimeInForce?, + val quantity: BigDecimal?, + val quoteOrderQty: BigDecimal?, + val price: BigDecimal?, + val newClientOrderId: String?, /* A unique id among open orders. Automatically generated if not sent. + Orders with the same newClientOrderID can be accepted only when the previous one is filled, otherwise the order will be rejected. + */ + val stopPrice: BigDecimal?, //Used with STOP_LOSS, STOP_LOSS_LIMIT, TAKE_PROFIT, and TAKE_PROFIT_LIMIT orders. + val icebergQty: BigDecimal?, //Used with LIMIT, STOP_LOSS_LIMIT, and TAKE_PROFIT_LIMIT to create an iceberg order. + val newOrderRespType: OrderResponseType?, //Set the response JSON. ACK, RESULT, or FULL; MARKET and LIMIT order types default to FULL, all other orders default to ACK. +) \ No newline at end of file diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/CreateOrderResponse.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/CreateOrderResponse.kt new file mode 100644 index 000000000..9d2f64078 --- /dev/null +++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/CreateOrderResponse.kt @@ -0,0 +1,21 @@ +package co.nilin.opex.api.core.inout + +import java.math.BigDecimal +import java.util.* + +data class CreateOrderResponse( + val symbol: String, + val orderId: Long, + val orderListId: Long, //Unless OCO, value will be -1 + val clientOrderId: String, + val transactTime: Date, + val price: BigDecimal?, + val origQty: BigDecimal?, + val executedQty: BigDecimal?, + val cummulativeQuoteQty: BigDecimal, + val status: OrderStatus?, + val timeInForce: TimeInForce?, + val type: OrderType?, + val side: OrderSide?, + val fills: List? +) \ No newline at end of file diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/MarketTradeResponse.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/MarketTradeResponse.kt new file mode 100644 index 000000000..c8baafb1f --- /dev/null +++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/MarketTradeResponse.kt @@ -0,0 +1,15 @@ +package co.nilin.opex.api.core.inout + +import java.math.BigDecimal +import java.util.* + +data class MarketTradeResponse( + val symbol: String, + val id: Long, + val price: BigDecimal, + val qty: BigDecimal, + val quoteQty: BigDecimal, + val time: Date, + val isBestMatch: Boolean, + val isMakerBuyer: Boolean +) \ No newline at end of file diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderBookResponse.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderBookResponse.kt new file mode 100644 index 000000000..53e8f0c01 --- /dev/null +++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderBookResponse.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.api.core.inout + +import java.math.BigDecimal + +data class OrderBookResponse( + val price: BigDecimal?, + val quantity: BigDecimal? +) \ No newline at end of file diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderEnums.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderEnums.kt new file mode 100644 index 000000000..70c43fec1 --- /dev/null +++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderEnums.kt @@ -0,0 +1,41 @@ +package co.nilin.opex.api.core.inout + +enum class TimeInForce { + GTC, //Good Til Canceled, An order will be on the book unless the order is canceled. + IOC, //Immediate Or Cancel, An order will try to fill the order as much as it can before the order expires. + FOK, //Fill or Kill, An order will expire if the full order cannot be filled upon execution. +} + +enum class OrderStatus(val code: Int) { + + NEW(1), //The order has been accepted by the engine. + PARTIALLY_FILLED(4), //A part of the order has been filled. + FILLED(5), //The order has been completed. + CANCELED(2), //The order has been canceled by the user. + PENDING_CANCEL(7), //Currently unused + REJECTED(3), //The order was not accepted by the engine and not processed. + EXPIRED(6) //The order was canceled according to the order type's rules (e.g. LIMIT FOK orders with no fill, LIMIT IOC or MARKET orders that partially fill) or by the exchange, (e.g. orders canceled during liquidation, orders canceled during maintenance) +} + +enum class OrderType { + LIMIT, // timeInForce, quantity, price + MARKET, // quantity or quoteOrderQty + STOP_LOSS, // quantity, stopPrice + STOP_LOSS_LIMIT, // timeInForce, quantity, price, stopPrice + TAKE_PROFIT, // quantity, stopPrice + TAKE_PROFIT_LIMIT, // timeInForce, quantity, price, stopPrice + LIMIT_MAKER; // quantity, price + + companion object { + fun activeTypes() = listOf(LIMIT, MARKET) + } +} + +enum class OrderSide { + BUY, + SELL +} + +enum class OrderResponseType { + ACK, RESULT, FULL +} \ No newline at end of file diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderSubmitResult.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderSubmitResult.kt new file mode 100644 index 000000000..1e6d864c6 --- /dev/null +++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderSubmitResult.kt @@ -0,0 +1,3 @@ +package co.nilin.opex.api.core.inout + +data class OrderSubmitResult(val offset: Long?) \ No newline at end of file diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderTradeData.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderTradeData.kt new file mode 100644 index 000000000..86d45b08a --- /dev/null +++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderTradeData.kt @@ -0,0 +1,10 @@ +package co.nilin.opex.api.core.inout + +import java.math.BigDecimal + +data class OrderTradeData( + val price: BigDecimal, + val qty: BigDecimal, + val commission: BigDecimal, + val commissionAsset: String +) \ No newline at end of file diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OwnerLimitsResponse.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OwnerLimitsResponse.kt new file mode 100644 index 000000000..84267db98 --- /dev/null +++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OwnerLimitsResponse.kt @@ -0,0 +1,7 @@ +package co.nilin.opex.api.core.inout + +data class OwnerLimitsResponse( + val canTrade: Boolean, + val canWithdraw: Boolean, + val canDeposit: Boolean +) \ No newline at end of file diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PairInfoResponse.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PairInfoResponse.kt new file mode 100644 index 000000000..8d4c8ab2c --- /dev/null +++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PairInfoResponse.kt @@ -0,0 +1,9 @@ +package co.nilin.opex.api.core.inout + +data class PairInfoResponse( + val pair: String, + val leftSideWalletSymbol: String, + val rightSideWalletSymbol: String, + val leftSideFraction: Double, + val rightSideFraction: Double +) \ No newline at end of file diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PriceChangeResponse.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PriceChangeResponse.kt new file mode 100644 index 000000000..666c38ded --- /dev/null +++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PriceChangeResponse.kt @@ -0,0 +1,21 @@ +package co.nilin.opex.api.core.inout + +data class PriceChangeResponse( + val symbol: String, + val priceChange: Double = 0.0, + val priceChangePercent: Double = 0.0, + val weightedAvgPrice: Double = 0.0, + val lastPrice: Double = 0.0, + val lastQty: Double = 0.0, + val bidPrice: Double = 0.0, + val askPrice: Double = 0.0, + val openPrice: Double = 0.0, + val highPrice: Double = 0.0, + val lowPrice: Double = 0.0, + val volume: Double = 0.0, + val openTime: Long, + val closeTime: Long, + val firstId: Long = 0, + val lastId: Long = 0, + val count: Long = 0, +) diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PriceTickerResponse.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PriceTickerResponse.kt new file mode 100644 index 000000000..d370637ef --- /dev/null +++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PriceTickerResponse.kt @@ -0,0 +1,6 @@ +package co.nilin.opex.api.core.inout + +data class PriceTickerResponse( + val symbol: String?, + val price: String? +) \ No newline at end of file diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/QueryOrderRequest.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/QueryOrderRequest.kt new file mode 100644 index 000000000..5b0890e65 --- /dev/null +++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/QueryOrderRequest.kt @@ -0,0 +1,7 @@ +package co.nilin.opex.api.core.inout + +data class QueryOrderRequest( + val symbol: String, + val orderId: Long?, + val origClientOrderId: String? +) \ No newline at end of file diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/QueryOrderResponse.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/QueryOrderResponse.kt new file mode 100644 index 000000000..72aa1a0d6 --- /dev/null +++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/QueryOrderResponse.kt @@ -0,0 +1,26 @@ +package co.nilin.opex.api.core.inout + +import java.math.BigDecimal +import java.util.* + +data class QueryOrderResponse( + val symbol: String, + val ouid: String, + val orderId: Long, + val orderListId: Long, //Unless part of an OCO, the value will always be -1. + val clientOrderId: String, + val price: BigDecimal, + val origQty: BigDecimal, + val executedQty: BigDecimal, + val cummulativeQuoteQty: BigDecimal, + val status: OrderStatus, + val timeInForce: TimeInForce, + val type: OrderType, + val side: OrderSide, + val stopPrice: BigDecimal?, + val icebergQty: BigDecimal?, + val time: Date, + val updateTime: Date, + val isWorking: Boolean, + val origQuoteOrderQty: BigDecimal +) \ No newline at end of file diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/RateLimit.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/RateLimit.kt new file mode 100644 index 000000000..7de6e91c3 --- /dev/null +++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/RateLimit.kt @@ -0,0 +1,10 @@ +package co.nilin.opex.api.core.inout + +enum class RateLimit(val rateLimitType: RateLimitType, val interval: String, val intervalNum: Int, val limit: Int) { + + REQUEST_WEIGHT(RateLimitType.REQUEST_WEIGHT, "MINUTE", 1, 1200), + ORDERS_SECOND(RateLimitType.ORDERS, "SECOND", 10, 50), + ORDERS_DAY(RateLimitType.ORDERS, "DAY", 1, 16000), + RAW_REQUESTS(RateLimitType.RAW_REQUESTS, "MINUTE", 5, 6100) + +} \ No newline at end of file diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/RateLimitType.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/RateLimitType.kt new file mode 100644 index 000000000..63231003a --- /dev/null +++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/RateLimitType.kt @@ -0,0 +1,9 @@ +package co.nilin.opex.api.core.inout + +enum class RateLimitType { + + REQUEST_WEIGHT, + ORDERS, + RAW_REQUESTS + +} \ No newline at end of file diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/TradeRequest.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/TradeRequest.kt new file mode 100644 index 000000000..511949d47 --- /dev/null +++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/TradeRequest.kt @@ -0,0 +1,11 @@ +package co.nilin.opex.api.core.inout + +import java.util.* + +class TradeRequest( + val symbol: String?, + val fromTrade: Long?, + val startTime: Date?, + val endTime: Date?, + val limit: Int? = 500 //Default 500; max 1000. +) \ No newline at end of file diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/TradeResponse.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/TradeResponse.kt new file mode 100644 index 000000000..ffbb1d604 --- /dev/null +++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/TradeResponse.kt @@ -0,0 +1,21 @@ +package co.nilin.opex.api.core.inout + +import java.math.BigDecimal +import java.util.* + +data class TradeResponse( + val symbol: String, + val id: Long, + val orderId: Long, + val orderListId: Long = -1, + val price: BigDecimal, + val qty: BigDecimal, + val quoteQty: BigDecimal, + val commission: BigDecimal, + val commissionAsset: String, + val time: Date, + val isBuyer: Boolean, + val isMaker: Boolean, + val isBestMatch: Boolean, + val isMakerBuyer: Boolean +) \ No newline at end of file diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/Wallet.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/Wallet.kt new file mode 100644 index 000000000..72e6d2f1b --- /dev/null +++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/Wallet.kt @@ -0,0 +1,9 @@ +package co.nilin.opex.api.core.inout + +import java.math.BigDecimal + +data class Wallet( + val asset: String, + val balance: BigDecimal, + val type: String +) \ No newline at end of file diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/AccountantProxy.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/AccountantProxy.kt new file mode 100644 index 000000000..16b831307 --- /dev/null +++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/AccountantProxy.kt @@ -0,0 +1,9 @@ +package co.nilin.opex.api.core.spi + +import co.nilin.opex.api.core.inout.PairInfoResponse + +interface AccountantProxy { + + suspend fun getPairConfigs(): List + +} \ No newline at end of file diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MEGatewayProxy.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MEGatewayProxy.kt new file mode 100644 index 000000000..7a427891d --- /dev/null +++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MEGatewayProxy.kt @@ -0,0 +1,25 @@ +package co.nilin.opex.api.core.spi + +import co.nilin.opex.api.core.inout.CancelOrderRequest +import co.nilin.opex.api.core.inout.OrderSubmitResult +import co.nilin.opex.matching.core.model.MatchConstraint +import co.nilin.opex.matching.core.model.OrderDirection +import co.nilin.opex.matching.core.model.OrderType +import reactor.core.publisher.Mono +import java.math.BigDecimal + +interface MEGatewayProxy { + data class CreateOrderRequest( + var uuid: String?, + val pair: String, + val price: BigDecimal, + val quantity: BigDecimal, + val direction: OrderDirection, + val matchConstraint: MatchConstraint?, + val orderType: OrderType + ) + + suspend fun createNewOrder(order: CreateOrderRequest, token: String?): OrderSubmitResult? + + suspend fun cancelOrder(request: CancelOrderRequest, token: String?): OrderSubmitResult? +} \ No newline at end of file diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MarketQueryHandler.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MarketQueryHandler.kt new file mode 100644 index 000000000..c14938dc4 --- /dev/null +++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MarketQueryHandler.kt @@ -0,0 +1,23 @@ +package co.nilin.opex.api.core.spi + +import co.nilin.opex.api.core.inout.* +import kotlinx.coroutines.flow.Flow +import java.time.LocalDateTime + +interface MarketQueryHandler { + + suspend fun getTradeTickerData(startFrom: LocalDateTime): List + + suspend fun getTradeTickerDataBySymbol(symbol: String, startFrom: LocalDateTime): PriceChangeResponse + + suspend fun openBidOrders(symbol: String, limit: Int): List + + suspend fun openAskOrders(symbol: String, limit: Int): List + + suspend fun lastOrder(symbol: String): QueryOrderResponse? + + suspend fun recentTrades(symbol: String, limit: Int): Flow + + suspend fun lastPrice(symbol: String?): List + +} \ No newline at end of file diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/OrderPersister.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/OrderPersister.kt new file mode 100644 index 000000000..a8a128d04 --- /dev/null +++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/OrderPersister.kt @@ -0,0 +1,7 @@ +package co.nilin.opex.api.core.spi + +import co.nilin.opex.accountant.core.inout.RichOrder + +interface OrderPersister { + suspend fun save(order: RichOrder) +} \ No newline at end of file diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/SymbolMapper.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/SymbolMapper.kt new file mode 100644 index 000000000..6e36e717c --- /dev/null +++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/SymbolMapper.kt @@ -0,0 +1,10 @@ +package co.nilin.opex.api.core.spi + +interface SymbolMapper { + + suspend fun map(symbol: String?): String? + + suspend fun unmap(value: String?): String? + + suspend fun getKeyValues(): Map +} \ No newline at end of file diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/TradePersister.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/TradePersister.kt new file mode 100644 index 000000000..d07d91a1f --- /dev/null +++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/TradePersister.kt @@ -0,0 +1,7 @@ +package co.nilin.opex.api.core.spi + +import co.nilin.opex.accountant.core.inout.RichTrade + +interface TradePersister { + suspend fun save(trade: RichTrade) +} \ No newline at end of file diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/UserQueryHandler.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/UserQueryHandler.kt new file mode 100644 index 000000000..2c986aed3 --- /dev/null +++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/UserQueryHandler.kt @@ -0,0 +1,13 @@ +package co.nilin.opex.api.core.spi + +import co.nilin.opex.api.core.inout.* +import co.nilin.opex.api.core.inout.* +import kotlinx.coroutines.flow.Flow +import java.security.Principal + +interface UserQueryHandler { + suspend fun queryOrder(principal: Principal, request: QueryOrderRequest): QueryOrderResponse? + suspend fun openOrders(principal: Principal, symbol: String?): Flow + suspend fun allOrders(principal: Principal, allOrderRequest: AllOrderRequest): Flow + suspend fun allTrades(principal: Principal, request: TradeRequest): Flow +} \ No newline at end of file diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/WalletProxy.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/WalletProxy.kt new file mode 100644 index 000000000..4ab3063b2 --- /dev/null +++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/WalletProxy.kt @@ -0,0 +1,12 @@ +package co.nilin.opex.api.core.spi + +import co.nilin.opex.api.core.inout.OwnerLimitsResponse +import co.nilin.opex.api.core.inout.Wallet + +interface WalletProxy { + + suspend fun getWallets(uuid: String?, token: String?): List + + suspend fun getOwnerLimits(uuid: String?, token: String?): OwnerLimitsResponse + +} \ No newline at end of file diff --git a/Api/api-ports/.gitignore b/Api/api-ports/.gitignore new file mode 100644 index 000000000..f3a8317d6 --- /dev/null +++ b/Api/api-ports/.gitignore @@ -0,0 +1,4 @@ +*.iml +target/ +.mvn/ +.idea/ \ No newline at end of file diff --git a/Api/api-ports/api-binance-rest/.gitignore b/Api/api-ports/api-binance-rest/.gitignore new file mode 100644 index 000000000..f3a8317d6 --- /dev/null +++ b/Api/api-ports/api-binance-rest/.gitignore @@ -0,0 +1,4 @@ +*.iml +target/ +.mvn/ +.idea/ \ No newline at end of file diff --git a/Api/api-ports/api-binance-rest/mvnw b/Api/api-ports/api-binance-rest/mvnw new file mode 100644 index 000000000..3c8a55373 --- /dev/null +++ b/Api/api-ports/api-binance-rest/mvnw @@ -0,0 +1,322 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ]; then + + if [ -f /etc/mavenrc ]; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ]; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false +darwin=false +mingw=false +case "$(uname)" in +CYGWIN*) cygwin=true ;; +MINGW*) mingw=true ;; +Darwin*) + darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="$(/usr/libexec/java_home)" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ]; then + if [ -r /etc/gentoo-release ]; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +if [ -z "$M2_HOME" ]; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ]; do + ls=$(ls -ld "$PRG") + link=$(expr "$ls" : '.*-> \(.*\)$') + if expr "$link" : '/.*' >/dev/null; then + PRG="$link" + else + PRG="$(dirname "$PRG")/$link" + fi + done + + saveddir=$(pwd) + + M2_HOME=$(dirname "$PRG")/.. + + # make it fully qualified + M2_HOME=$(cd "$M2_HOME" && pwd) + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=$(cygpath --unix "$M2_HOME") + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw; then + [ -n "$M2_HOME" ] && + M2_HOME="$( ( + cd "$M2_HOME" + pwd + ))" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="$( ( + cd "$JAVA_HOME" + pwd + ))" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr \"$javaExecutable\" : '\([^ ]*\)')" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=$(which readlink) + if [ ! $(expr "$readLink" : '\([^ ]*\)') = "no" ]; then + if $darwin; then + javaHome="$(dirname \"$javaExecutable\")" + javaExecutable="$(cd \"$javaHome\" && pwd -P)/javac" + else + javaExecutable="$(readlink -f \"$javaExecutable\")" + fi + javaHome="$(dirname \"$javaExecutable\")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ]; then + if [ -n "$JAVA_HOME" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="$(which java)" + fi +fi + +if [ ! -x "$JAVACMD" ]; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ]; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ]; then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ]; do + if [ -d "$wdir"/.mvn ]; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$( + cd "$wdir/.." + pwd + ) + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' <"$1")" + fi +} + +BASE_DIR=$(find_maven_basedir "$(pwd)") +if [ -z "$BASE_DIR" ]; then + exit 1 +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in wrapperUrl) + jarUrl="$value" + break + ;; + esac + done <"$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget >/dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl >/dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=$(cygpath --path --windows "$M2_HOME") + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/Api/api-ports/api-binance-rest/mvnw.cmd b/Api/api-ports/api-binance-rest/mvnw.cmd new file mode 100644 index 000000000..c8d43372c --- /dev/null +++ b/Api/api-ports/api-binance-rest/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/Api/api-ports/api-binance-rest/pom.xml b/Api/api-ports/api-binance-rest/pom.xml new file mode 100644 index 000000000..1484b63cc --- /dev/null +++ b/Api/api-ports/api-binance-rest/pom.xml @@ -0,0 +1,176 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.4 + + + co.nilin.opex + api-binance-rest + 1.0-SNAPSHOT + api-binance-rest + Api Binance Rest + + + 1.8 + 1.4.31 + ${version} + ${version} + 2020.0.2 + ${version} + + + + + co.nilin.opex + matching-core + ${matching.version} + provided + + + co.nilin.opex + api-core + ${api.version} + provided + + + co.nilin.opex + logging-handler + ${utility.version} + provided + + + co.nilin.opex + error-handler + ${utility.version} + provided + + + co.nilin.opex + interceptors + ${utility.version} + provided + + + org.springframework.boot + spring-boot-starter-webflux + + + org.springframework.cloud + spring-cloud-starter-consul-all + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-data-r2dbc + + + io.r2dbc + r2dbc-postgresql + runtime + + + org.postgresql + postgresql + runtime + + + io.projectreactor.kotlin + reactor-kotlin-extensions + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-oauth2-resource-server + + + org.bouncycastle + bcprov-jdk15on + 1.60 + + + + org.jetbrains.kotlinx + kotlinx-coroutines-core + + + io.projectreactor + reactor-test + test + + + io.swagger + swagger-annotations + 1.5.20 + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + -Xjsr305=strict + + + spring + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/config/ErrorHandlerConfig.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/config/ErrorHandlerConfig.kt new file mode 100644 index 000000000..f500e712b --- /dev/null +++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/config/ErrorHandlerConfig.kt @@ -0,0 +1,10 @@ +package co.nilin.opex.port.api.binance.config + +import co.nilin.opex.utility.error.EnableOpexErrorHandler +import org.springframework.context.annotation.Configuration + +@Configuration +@EnableOpexErrorHandler +class ErrorHandlerConfig { + +} \ No newline at end of file diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/config/RestConfig.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/config/RestConfig.kt new file mode 100644 index 000000000..4c6a23140 --- /dev/null +++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/config/RestConfig.kt @@ -0,0 +1,29 @@ +package co.nilin.opex.port.api.binance.config + +import co.nilin.opex.utility.interceptor.FormDataWorkaroundFilter +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.format.Formatter +import java.util.* +import org.springframework.web.server.WebFilter + +@Configuration +class RestConfig { + @Bean + fun dateFormatter(): Formatter? { + return object : Formatter { + override fun print(date: Date, locale: Locale): String { + return date.time.toString() + } + + override fun parse(date: String, locale: Locale): Date { + return Date(date.toLong()) + } + } + } + + @Bean + fun formDataWebFilter(): WebFilter { + return FormDataWorkaroundFilter() + } +} \ No newline at end of file diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/config/SecurityConfig.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/config/SecurityConfig.kt new file mode 100644 index 000000000..9d7bec883 --- /dev/null +++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/config/SecurityConfig.kt @@ -0,0 +1,49 @@ +package co.nilin.opex.port.api.binance.config + +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Bean +import org.springframework.http.HttpMethod +import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity +import org.springframework.security.config.web.server.ServerHttpSecurity +import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder +import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder +import org.springframework.security.web.server.SecurityWebFilterChain +import org.springframework.web.reactive.function.client.WebClient + +@EnableWebFluxSecurity +class SecurityConfig(private val webClient: WebClient) { + + @Value("\${app.auth.cert-url}") + private lateinit var jwkUrl: String + + @Bean + fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain? { + http.csrf().disable() + .cors().and() + .authorizeExchange() + .pathMatchers("/hello").permitAll() + .pathMatchers("/actuator/**").permitAll() + .pathMatchers("/swagger-ui/**").permitAll() + .pathMatchers("/swagger-resources/**").permitAll() + .pathMatchers("/v2/api-docs").permitAll() + .pathMatchers("/v3/depth").permitAll() + .pathMatchers("/v3/trades").permitAll() + .pathMatchers("/v3/ticker/**").permitAll() + .pathMatchers("/v3/exchangeInfo").permitAll() + .pathMatchers(HttpMethod.OPTIONS, "/**").permitAll() + .pathMatchers("/**").hasAuthority("SCOPE_trust") + .anyExchange().authenticated() + .and() + .oauth2ResourceServer() + .jwt() + return http.build() + } + + @Bean + @Throws(Exception::class) + fun reactiveJwtDecoder(): ReactiveJwtDecoder? { + return NimbusReactiveJwtDecoder.withJwkSetUri(jwkUrl) + .webClient(webClient) + .build() + } +} diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/config/WebClientConfig.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/config/WebClientConfig.kt new file mode 100644 index 000000000..84aa19305 --- /dev/null +++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/config/WebClientConfig.kt @@ -0,0 +1,38 @@ +package co.nilin.opex.port.api.binance.config + +import co.nilin.opex.utility.log.interceptor.CustomLogger +import org.springframework.cloud.client.ServiceInstance +import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties +import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer +import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerExchangeFilterFunction +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.http.client.reactive.ReactorClientHttpConnector +import org.springframework.web.reactive.function.client.WebClient +import reactor.netty.http.client.HttpClient + +@Configuration +class WebClientConfig { + + @Bean + fun webClient(loadBalancerFactory: ReactiveLoadBalancer.Factory): WebClient { + val logger = CustomLogger(HttpClient::class.java) + return WebClient.builder() + .clientConnector( + ReactorClientHttpConnector( + HttpClient + .create() + .doOnRequest { request, connection -> + connection.addHandlerFirst(logger) + } + ) + ) + .filter( + ReactorLoadBalancerExchangeFilterFunction( + loadBalancerFactory, LoadBalancerProperties(), emptyList() + ) + ) + .build() + } + +} diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/controller/AccountController.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/controller/AccountController.kt new file mode 100644 index 000000000..080e82456 --- /dev/null +++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/controller/AccountController.kt @@ -0,0 +1,556 @@ +package co.nilin.opex.port.api.binance.controller + +import co.nilin.opex.api.core.inout.* +import co.nilin.opex.api.core.spi.MEGatewayProxy +import co.nilin.opex.api.core.spi.SymbolMapper +import co.nilin.opex.api.core.spi.UserQueryHandler +import co.nilin.opex.api.core.spi.WalletProxy +import co.nilin.opex.port.api.binance.data.AccountInfoResponse +import co.nilin.opex.port.api.binance.util.* +import co.nilin.opex.utility.error.data.OpexError +import co.nilin.opex.utility.error.data.OpexException +import com.fasterxml.jackson.annotation.JsonInclude +import io.swagger.annotations.* +import java.math.BigDecimal +import java.security.Principal +import java.util.* +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import org.springframework.http.MediaType +import org.springframework.security.core.annotation.CurrentSecurityContext +import org.springframework.security.core.context.SecurityContext +import org.springframework.web.bind.annotation.* + +@RestController +class AccountController( + val queryHandler: UserQueryHandler, + val matchingGatewayProxy: MEGatewayProxy, + val walletProxy: WalletProxy, + val symbolMapper: SymbolMapper +) { + + data class FillsData( + val price: BigDecimal, + val qty: BigDecimal, + val commission: BigDecimal, + val commissionAsset: String + ) + + @JsonInclude(JsonInclude.Include.NON_NULL) + data class NewOrderResponse( + val symbol: String, + val orderId: Long, + val orderListId: Long, //Unless OCO, value will be -1 + val clientOrderId: String?, + val transactTime: Date, + val price: BigDecimal?, + val origQty: BigDecimal?, + val executedQty: BigDecimal?, + val cummulativeQuoteQty: BigDecimal?, + val status: OrderStatus?, + val timeInForce: TimeInForce?, + val type: OrderType?, + val side: OrderSide?, + val fills: List? + ) + + @JsonInclude(JsonInclude.Include.NON_NULL) + data class CancelOrderResponse( + val symbol: String, + val origClientOrderId: String?, + val orderId: Long?, + val orderListId: Long, //Unless OCO, value will be -1 + val clientOrderId: String?, + val price: BigDecimal?, + val origQty: BigDecimal?, + val executedQty: BigDecimal?, + val cummulativeQuoteQty: BigDecimal?, + val status: OrderStatus?, + val timeInForce: TimeInForce?, + val type: OrderType?, + val side: OrderSide? + ) + + @JsonInclude(JsonInclude.Include.NON_NULL) + data class QueryOrderResponse( + val symbol: String, + val orderId: Long, + val orderListId: Long, //Unless part of an OCO, the value will always be -1. + val clientOrderId: String, + val price: BigDecimal, + val origQty: BigDecimal, + val executedQty: BigDecimal, + val cummulativeQuoteQty: BigDecimal, + val status: OrderStatus, + val timeInForce: TimeInForce, + val type: OrderType, + val side: OrderSide, + val stopPrice: BigDecimal?, + val icebergQty: BigDecimal?, + val time: Date, + val updateTime: Date, + val isWorking: Boolean, + val origQuoteOrderQty: BigDecimal + ) + + @JsonInclude(JsonInclude.Include.NON_NULL) + data class TradeResponse( + val symbol: String, + val id: Long, + val orderId: Long, + val orderListId: Long = -1, + val price: BigDecimal, + val qty: BigDecimal, + val quoteQty: BigDecimal, + val commission: BigDecimal, + val commissionAsset: String, + val time: Date, + val isBuyer: Boolean, + val isMaker: Boolean, + val isBestMatch: Boolean + ) + + private val logger by LoggerDelegate() + + /* + Send in a new order. + Weight: 1 + Data Source: Matching Engine + */ + @PostMapping( + "/v3/order", + consumes = [MediaType.APPLICATION_FORM_URLENCODED_VALUE], + produces = [MediaType.APPLICATION_JSON_VALUE] + ) + @ApiResponse( + message = "OK", + code = 200, + examples = Example( + ExampleProperty( + value = "{ \"symbol\": \"btc_usdt\", \"orderId\": -1, \"orderListId\": -1, \"transactTime\": \"2021-08-03T11:09:23.190+00:00\" }", + mediaType = "application/json" + ) + ) + ) + suspend fun createNewOrder( + @RequestParam(name = "symbol") + symbol: String, + @RequestParam(name = "side") + side: OrderSide, + @RequestParam(name = "type") + type: OrderType, + @RequestParam(name = "timeInForce", required = false) + timeInForce: TimeInForce?, + @RequestParam(name = "quantity", required = false) + quantity: BigDecimal?, + @RequestParam(name = "quoteOrderQty", required = false) + quoteOrderQty: BigDecimal?, + @RequestParam(name = "price", required = false) + price: BigDecimal?, + @ApiParam( + value = "A unique id among open orders. Automatically generated if not sent.\n" + + "Orders with the same newClientOrderID can be accepted only when the previous one is filled, otherwise the order will be rejected." + ) + @RequestParam(name = "newClientOrderId", required = false) + newClientOrderId: String?, /* A unique id among open orders. Automatically generated if not sent. + Orders with the same newClientOrderID can be accepted only when the previous one is filled, otherwise the order will be rejected. + */ + @ApiParam(value = "Used with STOP_LOSS, STOP_LOSS_LIMIT, TAKE_PROFIT, and TAKE_PROFIT_LIMIT orders.") + @RequestParam(name = "stopPrice", required = false) + stopPrice: BigDecimal?, //Used with STOP_LOSS, STOP_LOSS_LIMIT, TAKE_PROFIT, and TAKE_PROFIT_LIMIT orders. + @RequestParam(name = "icebergQty", required = false) + @ApiParam(value = "Used with LIMIT, STOP_LOSS_LIMIT, and TAKE_PROFIT_LIMIT to create an iceberg order.") + icebergQty: BigDecimal?, //Used with LIMIT, STOP_LOSS_LIMIT, and TAKE_PROFIT_LIMIT to create an iceberg order. + @RequestParam(name = "newOrderRespType", required = false) + @ApiParam(value = "Set the response JSON. ACK, RESULT, or FULL; MARKET and LIMIT order types default to FULL, all other orders default to ACK.") + newOrderRespType: OrderResponseType?, //Set the response JSON. ACK, RESULT, or FULL; MARKET and LIMIT order types default to FULL, all other orders default to ACK. + @ApiParam(value = "The value cannot be greater than 60000") + @RequestParam(name = "recvWindow", required = false) + recvWindow: Long?, //The value cannot be greater than 60000 + @RequestParam(name = "timestamp") + timestamp: Long, + @CurrentSecurityContext securityContext: SecurityContext + ): NewOrderResponse { + val internalSymbol = symbolMapper.unmap(symbol) ?: throw OpexException(OpexError.SymbolNotFound) + + val request = MEGatewayProxy.CreateOrderRequest( + securityContext.jwtAuthentication().name, + internalSymbol, + price ?: BigDecimal.ZERO, // Maybe make this nullable as well? + quantity ?: BigDecimal.ZERO, + side.asOrderDirection(), + timeInForce?.asMatchConstraint(), + type.asMatchingOrderType() + ) + + matchingGatewayProxy.createNewOrder(request, securityContext.jwtAuthentication().tokenValue()) + return NewOrderResponse( + symbol, + -1, + -1, + null, + Date(), + null, + null, + null, + null, + null, + null, + null, + null, + null + ) + } + + @DeleteMapping( + "/v3/order", + consumes = [MediaType.APPLICATION_FORM_URLENCODED_VALUE], + produces = [MediaType.APPLICATION_JSON_VALUE] + ) + suspend fun cancelOrder( + principal: Principal, + @RequestParam(name = "symbol") + symbol: String, + @RequestParam(name = "orderId", required = false) + orderId: Long?, //Either orderId or origClientOrderId must be sent. + @RequestParam(name = "origClientOrderId", required = false) + origClientOrderId: String?, + @RequestParam(name = "newClientOrderId", required = false) + newClientOrderId: String?, + @ApiParam(value = "The value cannot be greater than 60000") + @RequestParam(name = "recvWindow", required = false) + recvWindow: Long?, //The value cannot be greater than 60000 + @RequestParam(name = "timestamp") + timestamp: Long, + @CurrentSecurityContext securityContext: SecurityContext + ): CancelOrderResponse { + val localSymbol = symbolMapper.unmap(symbol) ?: throw OpexException(OpexError.SymbolNotFound) + if (orderId == null && origClientOrderId == null) + throw OpexException(OpexError.BadRequest, message = "'orderId' or 'origClientOrderId' must be sent") + + val order = queryHandler.queryOrder(principal, QueryOrderRequest(localSymbol, orderId, origClientOrderId)) + ?: throw OpexException(OpexError.OrderNotFound) + + val response = CancelOrderResponse( + symbol, + origClientOrderId, + orderId, + -1, + null, + order.price, + order.origQty, + order.executedQty, + order.cummulativeQuoteQty, + OrderStatus.CANCELED, + order.timeInForce, + order.type, + order.side + ) + + if (order.status == OrderStatus.CANCELED) + return response + + if (order.status.equalsAny(OrderStatus.REJECTED, OrderStatus.EXPIRED, OrderStatus.FILLED)) + throw OpexException(OpexError.CancelOrderNotAllowed) + + + val request = CancelOrderRequest(order.ouid, principal.name, order.orderId, localSymbol) + matchingGatewayProxy.cancelOrder(request, securityContext.jwtAuthentication().tokenValue()) + return response + } + + /* + Check an order's status. + + Weight: 2 + Data Source: Database + */ + @GetMapping( + "/v3/order", + consumes = [MediaType.APPLICATION_FORM_URLENCODED_VALUE], + produces = [MediaType.APPLICATION_JSON_VALUE] + ) + @ApiResponse( + message = "OK", + code = 200, + examples = Example( + ExampleProperty( + value = "{ \"symbol\": \"btc_usdt\", \"orderId\": 12, \"orderListId\": -1, \"clientOrderId\": \"\", \"price\": 1, \"origQty\": 10, \"executedQty\": 0, \"cummulativeQuoteQty\": 0, \"status\": \"NEW\", \"timeInForce\": \"GTC\", \"type\": \"LIMIT\", \"side\": \"SELL\", \"time\": \"2021-08-04T12:10:13.488+00:00\", \"updateTime\": \"2021-08-04T12:10:13.488+00:00\", \"isWorking\": true, \"origQuoteOrderQty\": 10 }", + mediaType = "application/json" + ) + ) + ) + suspend fun queryOrder( + principal: Principal, + @RequestParam(name = "symbol") + symbol: String, + @RequestParam(name = "orderId", required = false) + orderId: Long?, + @RequestParam(name = "origClientOrderId", required = false) + origClientOrderId: String?, + @ApiParam(value = "The value cannot be greater than 60000") + @RequestParam(name = "recvWindow", required = false) + recvWindow: Long?, //The value cannot be greater than 60000 + @RequestParam(name = "timestamp") + timestamp: Long + ): QueryOrderResponse { + val internalSymbol = symbolMapper.unmap(symbol) ?: throw OpexException(OpexError.SymbolNotFound) + + val response = queryHandler.queryOrder(principal, QueryOrderRequest(internalSymbol, orderId, origClientOrderId)) + ?: throw OpexException(OpexError.OrderNotFound) + + return QueryOrderResponse( + symbol, + response.orderId, + response.orderListId, + response.clientOrderId, + response.price, + response.origQty, + response.executedQty, + response.cummulativeQuoteQty, + response.status, + response.timeInForce, + response.type, + response.side, + response.stopPrice, + response.icebergQty, + response.time, + response.updateTime, + response.isWorking, + response.origQuoteOrderQty + ) + } + + + /* + Get all open orders on a symbol. Careful when accessing this with no symbol. + + Weight: 3 for a single symbol; 40 when the symbol parameter is omitted + + Data Source: Database + */ + @GetMapping( + "/v3/openOrders", + consumes = [MediaType.APPLICATION_FORM_URLENCODED_VALUE], + produces = [MediaType.APPLICATION_JSON_VALUE] + ) + @ApiResponse( + message = "OK", + code = 200, + examples = Example( + ExampleProperty( + value = "[ { \"symbol\": \"btc_usdt\", \"orderId\": 12, \"orderListId\": -1, \"clientOrderId\": \"\", \"price\": 1, \"origQty\": 10, \"executedQty\": 0, \"cummulativeQuoteQty\": 0, \"status\": \"NEW\", \"timeInForce\": \"GTC\", \"type\": \"LIMIT\", \"side\": \"SELL\", \"time\": \"2021-08-04T12:10:13.488+00:00\", \"updateTime\": \"2021-08-04T12:10:13.488+00:00\", \"isWorking\": true, \"origQuoteOrderQty\": 10 } ]", + mediaType = "application/json" + ) + ) + ) + suspend fun fetchOpenOrders( + principal: Principal, + @RequestParam(name = "symbol", required = false) + symbol: String?, + @ApiParam(value = "The value cannot be greater than 60000") + @RequestParam(name = "recvWindow", required = false) + recvWindow: Long?, //The value cannot be greater than 60000 + @RequestParam(name = "timestamp") + timestamp: Long + ): Flow { + val internalSymbol = symbolMapper.unmap(symbol) ?: throw OpexException(OpexError.SymbolNotFound) + + return queryHandler.openOrders(principal, internalSymbol) + .map { response -> + QueryOrderResponse( + symbol ?: "", + response.orderId, + response.orderListId, + response.clientOrderId, + response.price, + response.origQty, + response.executedQty, + response.cummulativeQuoteQty, + response.status, + response.timeInForce, + response.type, + response.side, + response.stopPrice, + response.icebergQty, + response.time, + response.updateTime, + response.isWorking, + response.origQuoteOrderQty + ) + } + } + + /* + Get all account orders; active, canceled, or filled. + Weight: 10 with symbol + Data Source: Database + */ + @GetMapping( + "/v3/allOrders", + consumes = [MediaType.APPLICATION_FORM_URLENCODED_VALUE], + produces = [MediaType.APPLICATION_JSON_VALUE] + ) + @ApiResponse( + message = "OK", + code = 200, + examples = Example( + ExampleProperty( + value = "{ }", + mediaType = "application/json" + ) + ) + ) + suspend fun fetchAllOrders( + principal: Principal, + @RequestParam(name = "symbol", required = false) + symbol: String?, + @RequestParam(name = "startTime", required = false) + startTime: Date?, + @RequestParam(name = "endTime", required = false) + endTime: Date?, + @ApiParam(value = "Default 500; max 1000.") + @RequestParam(name = "limit", required = false) + limit: Int? = 500, //Default 500; max 1000. + @ApiParam(value = "The value cannot be greater than 60000") + @RequestParam(name = "recvWindow", required = false) + recvWindow: Long?, //The value cannot be greater than 60000 + @RequestParam(name = "timestamp") + timestamp: Long + ): Flow { + val internalSymbol = symbolMapper.unmap(symbol) ?: throw OpexException(OpexError.SymbolNotFound) + + return queryHandler.allOrders(principal, AllOrderRequest(internalSymbol, startTime, endTime, limit)) + .map { response -> + QueryOrderResponse( + symbol ?: "", + response.orderId, + response.orderListId, + response.clientOrderId, + response.price, + response.origQty, + response.executedQty, + response.cummulativeQuoteQty, + response.status, + response.timeInForce, + response.type, + response.side, + response.stopPrice, + response.icebergQty, + response.time, + response.updateTime, + response.isWorking, + response.origQuoteOrderQty + ) + } + } + + /* + Get trades for a specific account and symbol. + If fromId is set, it will get trades >= that fromId. Otherwise most recent trades are returned. + Weight: 10 with symbol + Data Source: Database + */ + @GetMapping( + "/v3/myTrades", + consumes = [MediaType.APPLICATION_FORM_URLENCODED_VALUE], + produces = [MediaType.APPLICATION_JSON_VALUE] + ) + @ApiResponse( + message = "OK", + code = 200, + examples = Example( + ExampleProperty( + value = "{ }", + mediaType = "application/json" + ) + ) + ) + suspend fun fetchAllTrades( + principal: Principal, + @RequestParam(name = "symbol") + symbol: String?, + @RequestParam(name = "startTime", required = false) + startTime: Date?, + @RequestParam(name = "endTime", required = false) + endTime: Date?, + @ApiParam(value = "TradeId to fetch from. Default gets most recent trades.") + @RequestParam(name = "fromId", required = false) + fromId: Long?,//TradeId to fetch from. Default gets most recent trades. + @ApiParam(value = "Default 500; max 1000.") + @RequestParam(name = "limit", required = false) + limit: Int? = 500, //Default 500; max 1000. + @ApiParam(value = "The value cannot be greater than 60000") + @RequestParam(name = "recvWindow", required = false) + recvWindow: Long?, //The value cannot be greater than 60000 + @RequestParam(name = "timestamp") + timestamp: Long + ): Flow { + val internalSymbol = symbolMapper.unmap(symbol) ?: throw OpexException(OpexError.SymbolNotFound) + + return queryHandler.allTrades(principal, TradeRequest(internalSymbol, fromId, startTime, endTime, limit)) + .map { response -> + TradeResponse( + symbol ?: "", + response.id, + response.orderId, + -1, + response.price, + response.qty, + response.quoteQty, + response.commission, + response.commissionAsset, + response.time, + response.isBuyer, + response.isMaker, + response.isBestMatch + ) + } + } + + @GetMapping( + "/v3/account", + consumes = [MediaType.APPLICATION_FORM_URLENCODED_VALUE], + produces = [MediaType.APPLICATION_JSON_VALUE] + ) + @ApiResponse( + message = "OK", + code = 200, + examples = Example( + ExampleProperty( + value = "{ \"makerCommission\": 0, \"takerCommission\": 0, \"buyerCommission\": 0, \"sellerCommission\": 0, \"canTrade\": true, \"canWithdraw\": true, \"canDeposit\": true, \"updateTime\": 1628420513843, \"accountType\": \"SPOT\", \"balances\": [ { \"asset\": \"usdt\", \"free\": 1000, \"locked\": 0 } ], \"permissions\": [ \"SPOT\" ] }", + mediaType = "application/json" + ) + ) + ) + suspend fun accountInfo( + @CurrentSecurityContext securityContext: SecurityContext, + @ApiParam(value = "The value cannot be greater than 60000") + @RequestParam(name = "recvWindow", required = false) + recvWindow: Long?, //The value cannot be greater than 60000 + @RequestParam(name = "timestamp") + timestamp: Long + ): AccountInfoResponse { + val auth = securityContext.jwtAuthentication() + val wallets = walletProxy.getWallets(auth.name, auth.tokenValue()) + val limits = walletProxy.getOwnerLimits(auth.name, auth.tokenValue()) + val parsedBalances = BalanceParser.parse(wallets) + val accountType = "SPOT" + + //TODO replace commissions and accountType with actual data + return AccountInfoResponse( + 0, + 0, + 0, + 0, + limits.canTrade, + limits.canWithdraw, + limits.canDeposit, + Date().time, + accountType, + parsedBalances, + listOf(accountType) + ) + } + +} \ No newline at end of file diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/controller/FiltersController.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/controller/FiltersController.kt new file mode 100644 index 000000000..19ef78033 --- /dev/null +++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/controller/FiltersController.kt @@ -0,0 +1,7 @@ +package co.nilin.opex.port.api.binance.controller + +import org.springframework.web.bind.annotation.RestController + +@RestController +class FiltersController { +} \ No newline at end of file diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/controller/MarketController.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/controller/MarketController.kt new file mode 100644 index 000000000..b0688a4ce --- /dev/null +++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/controller/MarketController.kt @@ -0,0 +1,176 @@ +package co.nilin.opex.port.api.binance.controller + +import co.nilin.opex.api.core.spi.MarketQueryHandler +import co.nilin.opex.api.core.spi.SymbolMapper +import co.nilin.opex.port.api.binance.data.OrderBookResponse +import co.nilin.opex.api.core.inout.PriceChangeResponse +import co.nilin.opex.api.core.inout.PriceTickerResponse +import co.nilin.opex.api.core.spi.AccountantProxy +import co.nilin.opex.port.api.binance.data.ExchangeInfoResponse +import co.nilin.opex.port.api.binance.data.ExchangeInfoSymbol +import co.nilin.opex.port.api.binance.data.RecentTradeResponse +import co.nilin.opex.utility.error.data.OpexError +import co.nilin.opex.utility.error.data.OpexException +import co.nilin.opex.utility.error.data.throwError +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.RestController +import java.math.BigDecimal +import java.security.Principal +import java.time.Instant +import java.time.LocalDateTime +import java.time.ZoneId +import java.util.* +import java.util.concurrent.TimeUnit +import kotlin.collections.ArrayList + +@RestController +class MarketController( + private val accountantProxy: AccountantProxy, + private val marketQueryHandler: MarketQueryHandler, + private val symbolMapper: SymbolMapper, +) { + + private val orderBookValidLimits = arrayListOf(5, 10, 20, 50, 100, 500, 1000, 5000) + private val validDurations = arrayListOf("24h", "7d", "1m") + + // Limit - Weight + // 5, 10, 20, 50, 100 - 1 + // 500 - 5 + // 1000 - 10 + // 5000 - 50 + @GetMapping("/v3/depth") + suspend fun orderBook( + @RequestParam("symbol") + symbol: String, + @RequestParam("limit", required = false) + limit: Int? // Default 100; max 5000. Valid limits:[5, 10, 20, 50, 100, 500, 1000, 5000] + ): OrderBookResponse { + val validLimit = limit ?: 100 + val localSymbol = symbolMapper.unmap(symbol) ?: throw OpexException(OpexError.SymbolNotFound) + if (!orderBookValidLimits.contains(validLimit)) + throwError(OpexError.InvalidLimitForOrderBook) + + val mappedBidOrders = ArrayList>() + val mappedAskOrders = ArrayList>() + + val bidOrders = marketQueryHandler.openBidOrders(localSymbol, validLimit) + val askOrders = marketQueryHandler.openAskOrders(localSymbol, validLimit) + + bidOrders.forEach { + val mapped = arrayListOf().apply { + add(it.price ?: BigDecimal.ZERO) + add(it.quantity ?: BigDecimal.ZERO) + } + mappedBidOrders.add(mapped) + } + + askOrders.forEach { + val mapped = arrayListOf().apply { + add(it.price ?: BigDecimal.ZERO) + add(it.quantity ?: BigDecimal.ZERO) + } + mappedAskOrders.add(mapped) + } + + val lastOrder = marketQueryHandler.lastOrder(localSymbol) + return OrderBookResponse(lastOrder?.orderId ?: -1, mappedBidOrders, mappedAskOrders) + } + + @GetMapping("/v3/trades") + suspend fun recentTrades( + principal: Principal, + @RequestParam("symbol") + symbol: String, + @RequestParam("limit", required = false) + limit: Int? // Default 500; max 1000. + ): Flow { + val validLimit = limit ?: 500 + val localSymbol = symbolMapper.unmap(symbol) ?: throw OpexException(OpexError.SymbolNotFound) + if (validLimit !in 1..1000) + throwError(OpexError.InvalidLimitForRecentTrades) + + return marketQueryHandler.recentTrades(localSymbol, validLimit) + .map { + RecentTradeResponse( + it.id, + it.price, + it.qty, + it.quoteQty, + it.time.time, + it.isMakerBuyer, + it.isBestMatch + ) + } + } + + @GetMapping("/v3/ticker/{duration:24h|7d|1m}") + suspend fun priceChange( + @PathVariable("duration") + duration: String, + @RequestParam("symbol", required = false) + symbol: String?, + ): List { + val localSymbol = if (symbol.isNullOrEmpty()) + null + else + symbolMapper.unmap(symbol) ?: throw OpexException(OpexError.SymbolNotFound) + + if (!validDurations.contains(duration)) + throwError(OpexError.InvalidPriceChangeDuration) + + val now = Date().time + val before = when (duration) { + "24h" -> Date(now - TimeUnit.DAYS.toMillis(1)) + "7d" -> Date(now - TimeUnit.DAYS.toMillis(7)) + "1m" -> Date(now - TimeUnit.DAYS.toMillis(31)) + else -> Date(now - TimeUnit.DAYS.toMillis(1)) + } + + val instant = Instant.ofEpochMilli(before.time) + val startDate = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()) + + return if (symbol.isNullOrEmpty()) + marketQueryHandler.getTradeTickerData(startDate) + else + listOf(marketQueryHandler.getTradeTickerDataBySymbol(localSymbol!!, startDate)) + } + + // Weight + // 1 for a single symbol + // 2 when the symbol parameter is omitted + @GetMapping("/v3/ticker/price") + suspend fun priceTicker(@RequestParam("symbol", required = false) symbol: String?): List { + val localSymbol = if (symbol == null) + null + else + symbolMapper.unmap(symbol) ?: throw OpexException(OpexError.SymbolNotFound) + return marketQueryHandler.lastPrice(localSymbol) + } + + @GetMapping("/v3/exchangeInfo") + suspend fun pairInfo( + @RequestParam("symbol", required = false) + symbol: String?, + @RequestParam("symbols", required = false) + symbols: String? + ): ExchangeInfoResponse { + val symbolsMap = symbolMapper.getKeyValues() + val pairConfigs = accountantProxy.getPairConfigs() + .map { + ExchangeInfoSymbol( + symbolsMap[it.pair] ?: it.pair, + "TRADING", + it.leftSideWalletSymbol.toUpperCase(), + BigDecimal.valueOf(it.leftSideFraction).scale(), + it.rightSideWalletSymbol.toUpperCase(), + BigDecimal.valueOf(it.rightSideFraction).scale() + ) + } + return ExchangeInfoResponse(symbols = pairConfigs) + } + +} \ No newline at end of file diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/AccountInfoResponse.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/AccountInfoResponse.kt new file mode 100644 index 000000000..0ace90f4c --- /dev/null +++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/AccountInfoResponse.kt @@ -0,0 +1,18 @@ +package co.nilin.opex.port.api.binance.data + +import com.fasterxml.jackson.annotation.JsonInclude + +@JsonInclude(JsonInclude.Include.NON_NULL) +data class AccountInfoResponse( + val makerCommission: Long, + val takerCommission: Long, + val buyerCommission: Long, + val sellerCommission: Long, + val canTrade: Boolean, + val canWithdraw: Boolean, + val canDeposit: Boolean, + val updateTime: Long, + val accountType: String, // Enum + val balances: List, + val permissions: List // Enum +) \ No newline at end of file diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/BalanceResponse.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/BalanceResponse.kt new file mode 100644 index 000000000..c81ef67b8 --- /dev/null +++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/BalanceResponse.kt @@ -0,0 +1,9 @@ +package co.nilin.opex.port.api.binance.data + +import java.math.BigDecimal + +data class BalanceResponse( + var asset: String, + var free: BigDecimal, + var locked: BigDecimal +) \ No newline at end of file diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/ExchangeInfoResponse.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/ExchangeInfoResponse.kt new file mode 100644 index 000000000..9a5cd229f --- /dev/null +++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/ExchangeInfoResponse.kt @@ -0,0 +1,13 @@ +package co.nilin.opex.port.api.binance.data + +import co.nilin.opex.api.core.inout.RateLimit +import java.util.* + +data class ExchangeInfoResponse( + val timezone: String = TimeZone.getDefault().id, + val serverTime: Long = Date().time, + val rateLimits: List = RateLimit.values() + .map { RateLimitResponse(it.rateLimitType, it.interval, it.intervalNum, it.limit) }, + val exchangeFilters: List = emptyList(), + val symbols: List +) \ No newline at end of file diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/ExchangeInfoSymbol.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/ExchangeInfoSymbol.kt new file mode 100644 index 000000000..4679e05ea --- /dev/null +++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/ExchangeInfoSymbol.kt @@ -0,0 +1,19 @@ +package co.nilin.opex.port.api.binance.data + +import co.nilin.opex.api.core.inout.OrderType + +data class ExchangeInfoSymbol( + val symbol: String, + val status: String, + val baseAsset: String, + val baseAssetPrecision: Int, + val quoteAsset: String, + val quoteAssetPrecision: Int, + val orderTypes: List = OrderType.activeTypes(), + val icebergAllowed: Boolean = false, + val ocoAllowed: Boolean = false, + val isSpotTradingAllowed: Boolean = false, + val isMarginTradingAllowed: Boolean = false, + val filters: List = emptyList(), + val permissions: List = listOf("SPOT") +) \ No newline at end of file diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/OrderBookResponse.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/OrderBookResponse.kt new file mode 100644 index 000000000..dbe8f96c5 --- /dev/null +++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/OrderBookResponse.kt @@ -0,0 +1,9 @@ +package co.nilin.opex.port.api.binance.data + +import java.math.BigDecimal + +data class OrderBookResponse( + val lastUpdateId: Long, + val bids: List>, // Inner list -> [0]: PRICE, [1]: QTY + val asks: List> // Inner list -> [0]: PRICE, [1]: QTY +) \ No newline at end of file diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/RateLimitResponse.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/RateLimitResponse.kt new file mode 100644 index 000000000..f102aeac6 --- /dev/null +++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/RateLimitResponse.kt @@ -0,0 +1,10 @@ +package co.nilin.opex.port.api.binance.data + +import co.nilin.opex.api.core.inout.RateLimitType + +data class RateLimitResponse( + val rateLimitType: RateLimitType, + val interval: String, + val intervalNum: Int, + val limit: Int +) \ No newline at end of file diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/RecentTradeResponse.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/RecentTradeResponse.kt new file mode 100644 index 000000000..2a6c76513 --- /dev/null +++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/RecentTradeResponse.kt @@ -0,0 +1,13 @@ +package co.nilin.opex.port.api.binance.data + +import java.math.BigDecimal + +data class RecentTradeResponse( + val id: Long, + val price: BigDecimal, + val qty: BigDecimal, + val quoteQty: BigDecimal, + val time: Long, + val isBuyerMaker: Boolean, + val isBestMatch: Boolean +) \ No newline at end of file diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/proxy/AccountantProxyImpl.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/proxy/AccountantProxyImpl.kt new file mode 100644 index 000000000..a457a1b75 --- /dev/null +++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/proxy/AccountantProxyImpl.kt @@ -0,0 +1,35 @@ +package co.nilin.opex.port.api.binance.proxy + +import co.nilin.opex.api.core.inout.PairInfoResponse +import co.nilin.opex.api.core.spi.AccountantProxy +import co.nilin.opex.port.api.binance.util.LoggerDelegate +import kotlinx.coroutines.reactive.awaitSingle +import org.springframework.beans.factory.annotation.Value +import org.springframework.core.ParameterizedTypeReference +import org.springframework.http.MediaType +import org.springframework.stereotype.Component +import org.springframework.web.reactive.function.client.WebClient + +private inline fun typeRef(): ParameterizedTypeReference = + object : ParameterizedTypeReference() {} + +@Component +class AccountantProxyImpl(private val webClient: WebClient) : AccountantProxy { + + private val logger by LoggerDelegate() + + @Value("\${app.accountant.url}") + private lateinit var baseUrl: String + + override suspend fun getPairConfigs(): List { + logger.info("fetching pair configs") + return webClient.get() + .uri("$baseUrl/config/all") + .accept(MediaType.APPLICATION_JSON) + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToFlux(typeRef()) + .collectList() + .awaitSingle() + } +} \ No newline at end of file diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/proxy/MEGatewayProxyImpl.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/proxy/MEGatewayProxyImpl.kt new file mode 100644 index 000000000..4b1433cd7 --- /dev/null +++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/proxy/MEGatewayProxyImpl.kt @@ -0,0 +1,55 @@ +package co.nilin.opex.port.api.binance.proxy + +import co.nilin.opex.api.core.inout.CancelOrderRequest +import co.nilin.opex.api.core.inout.OrderSubmitResult +import co.nilin.opex.api.core.spi.MEGatewayProxy +import co.nilin.opex.port.api.binance.util.LoggerDelegate +import kotlinx.coroutines.reactive.awaitSingleOrNull +import org.springframework.beans.factory.annotation.Value +import org.springframework.core.ParameterizedTypeReference +import org.springframework.http.MediaType +import org.springframework.stereotype.Component +import org.springframework.web.reactive.function.client.WebClient +import org.springframework.web.reactive.function.client.body +import reactor.core.publisher.Mono +import java.net.URI + +private inline fun typeRef(): ParameterizedTypeReference = + object : ParameterizedTypeReference() {} + +@Component +class MEGatewayProxyImpl(private val client: WebClient) : MEGatewayProxy { + + private val logger by LoggerDelegate() + + @Value("\${app.matching-gateway.url}") + private lateinit var baseUrl: String + + override suspend fun createNewOrder(order: MEGatewayProxy.CreateOrderRequest, token: String?): OrderSubmitResult? { + logger.info("calling matching-gateway order create") + return client.post() + .uri(URI.create("$baseUrl/order")) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", "Bearer $token") + .body(Mono.just(order)) + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToMono(typeRef()) + .awaitSingleOrNull() + } + + override suspend fun cancelOrder(request: CancelOrderRequest, token: String?): OrderSubmitResult? { + logger.info("calling matching-gateway order cancel") + return client.post() + .uri(URI.create("$baseUrl/order/cancel")) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", "Bearer $token") + .body(Mono.just(request)) + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToMono(typeRef()) + .awaitSingleOrNull() + } +} \ No newline at end of file diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/proxy/WalletProxyImpl.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/proxy/WalletProxyImpl.kt new file mode 100644 index 000000000..b5aedc3be --- /dev/null +++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/proxy/WalletProxyImpl.kt @@ -0,0 +1,52 @@ +package co.nilin.opex.port.api.binance.proxy + +import co.nilin.opex.api.core.inout.OwnerLimitsResponse +import co.nilin.opex.api.core.inout.Wallet +import co.nilin.opex.api.core.spi.WalletProxy +import co.nilin.opex.port.api.binance.util.LoggerDelegate +import kotlinx.coroutines.reactive.awaitSingle +import org.springframework.beans.factory.annotation.Value +import org.springframework.core.ParameterizedTypeReference +import org.springframework.http.HttpHeaders +import org.springframework.http.MediaType +import org.springframework.stereotype.Component +import org.springframework.web.reactive.function.client.WebClient +import java.math.BigDecimal +import java.time.LocalDateTime + +private inline fun typeRef(): ParameterizedTypeReference = + object : ParameterizedTypeReference() {} + +@Component +class WalletProxyImpl(private val webClient: WebClient) : WalletProxy { + + private val logger by LoggerDelegate() + + @Value("\${app.wallet.url}") + private lateinit var baseUrl: String + + override suspend fun getWallets(uuid: String?, token: String?): List { + logger.info("fetching wallets for $uuid") + return webClient.get() + .uri("$baseUrl/owner/wallet/all") + .accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, "Bearer $token") + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToFlux(typeRef()) + .collectList() + .awaitSingle() + } + + override suspend fun getOwnerLimits(uuid: String?, token: String?): OwnerLimitsResponse { + logger.info("fetching owner limits for $uuid") + return webClient.get() + .uri("$baseUrl/owner/limits") + .accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, "Bearer $token") + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToMono(typeRef()) + .awaitSingle() + } +} \ No newline at end of file diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/util/BalanceParser.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/util/BalanceParser.kt new file mode 100644 index 000000000..8e91d8dc1 --- /dev/null +++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/util/BalanceParser.kt @@ -0,0 +1,32 @@ +package co.nilin.opex.port.api.binance.util + +import co.nilin.opex.api.core.inout.Wallet +import co.nilin.opex.port.api.binance.data.BalanceResponse +import java.math.BigDecimal + +object BalanceParser { + + fun parse(list: List): List { + val result = arrayListOf() + + for (w in list) { + result.addOrGet(w.asset).apply { + if (w.type == "exchange") + locked = w.balance + else + free = w.balance + } + } + return result + } + + private fun ArrayList.addOrGet(symbol: String): BalanceResponse { + for (w in this) + if (w.asset == symbol) + return w + + add(BalanceResponse(symbol, BigDecimal.ZERO, BigDecimal.ZERO)) + return this.last() + } + +} \ No newline at end of file diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/util/EnumExtensions.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/util/EnumExtensions.kt new file mode 100644 index 000000000..9f1c1974f --- /dev/null +++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/util/EnumExtensions.kt @@ -0,0 +1,36 @@ +package co.nilin.opex.port.api.binance.util + +import co.nilin.opex.api.core.inout.OrderSide +import co.nilin.opex.api.core.inout.TimeInForce +import co.nilin.opex.matching.core.model.MatchConstraint +import co.nilin.opex.matching.core.model.OrderType +import co.nilin.opex.matching.core.model.OrderDirection + +fun OrderSide.asOrderDirection(): OrderDirection { + if (this == OrderSide.BUY) + return OrderDirection.BID + return OrderDirection.ASK +} + +fun TimeInForce.asMatchConstraint(): MatchConstraint { + return when (this) { + TimeInForce.GTC -> MatchConstraint.GTC + TimeInForce.IOC -> MatchConstraint.IOC + TimeInForce.FOK -> MatchConstraint.FOK + } +} + +fun co.nilin.opex.api.core.inout.OrderType.asMatchingOrderType(): OrderType { + return when (this) { + co.nilin.opex.api.core.inout.OrderType.LIMIT -> OrderType.LIMIT_ORDER + co.nilin.opex.api.core.inout.OrderType.MARKET -> OrderType.MARKET_ORDER + else -> OrderType.LIMIT_ORDER + } +} + +fun > R.equalsAny(vararg equals: R): Boolean { + for (e in equals) + if (this == e) + return true + return false +} \ No newline at end of file diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/util/LoggerDelegate.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/util/LoggerDelegate.kt new file mode 100644 index 000000000..4f64aa4d2 --- /dev/null +++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/util/LoggerDelegate.kt @@ -0,0 +1,24 @@ +package co.nilin.opex.port.api.binance.util + +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +class LoggerDelegate : ReadOnlyProperty { + + companion object { + private fun createLogger(clazz: Class): Logger { + return LoggerFactory.getLogger(clazz) + } + } + + private var logger: Logger? = null + + override operator fun getValue(thisRef: Any?, property: KProperty<*>): Logger { + if (logger == null) { + logger = createLogger(thisRef!!.javaClass) + } + return logger!! + } +} \ No newline at end of file diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/util/SecurityExtension.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/util/SecurityExtension.kt new file mode 100644 index 000000000..5b043512f --- /dev/null +++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/util/SecurityExtension.kt @@ -0,0 +1,12 @@ +package co.nilin.opex.port.api.binance.util + +import org.springframework.security.core.context.SecurityContext +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken + +fun SecurityContext.jwtAuthentication(): JwtAuthenticationToken { + return authentication as JwtAuthenticationToken +} + +fun JwtAuthenticationToken.tokenValue(): String { + return this.token.tokenValue +} \ No newline at end of file diff --git a/Api/api-ports/api-eventlistener-kafka/.gitignore b/Api/api-ports/api-eventlistener-kafka/.gitignore new file mode 100644 index 000000000..bb9840a17 --- /dev/null +++ b/Api/api-ports/api-eventlistener-kafka/.gitignore @@ -0,0 +1,34 @@ +HELP.md +target/ +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +.mvn/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ + + +### VS Code ### +.vscode/ + +.DS_Store diff --git a/Api/api-ports/api-eventlistener-kafka/mvnw b/Api/api-ports/api-eventlistener-kafka/mvnw new file mode 100644 index 000000000..3c8a55373 --- /dev/null +++ b/Api/api-ports/api-eventlistener-kafka/mvnw @@ -0,0 +1,322 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ]; then + + if [ -f /etc/mavenrc ]; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ]; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false +darwin=false +mingw=false +case "$(uname)" in +CYGWIN*) cygwin=true ;; +MINGW*) mingw=true ;; +Darwin*) + darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="$(/usr/libexec/java_home)" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ]; then + if [ -r /etc/gentoo-release ]; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +if [ -z "$M2_HOME" ]; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ]; do + ls=$(ls -ld "$PRG") + link=$(expr "$ls" : '.*-> \(.*\)$') + if expr "$link" : '/.*' >/dev/null; then + PRG="$link" + else + PRG="$(dirname "$PRG")/$link" + fi + done + + saveddir=$(pwd) + + M2_HOME=$(dirname "$PRG")/.. + + # make it fully qualified + M2_HOME=$(cd "$M2_HOME" && pwd) + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=$(cygpath --unix "$M2_HOME") + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw; then + [ -n "$M2_HOME" ] && + M2_HOME="$( ( + cd "$M2_HOME" + pwd + ))" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="$( ( + cd "$JAVA_HOME" + pwd + ))" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr \"$javaExecutable\" : '\([^ ]*\)')" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=$(which readlink) + if [ ! $(expr "$readLink" : '\([^ ]*\)') = "no" ]; then + if $darwin; then + javaHome="$(dirname \"$javaExecutable\")" + javaExecutable="$(cd \"$javaHome\" && pwd -P)/javac" + else + javaExecutable="$(readlink -f \"$javaExecutable\")" + fi + javaHome="$(dirname \"$javaExecutable\")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ]; then + if [ -n "$JAVA_HOME" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="$(which java)" + fi +fi + +if [ ! -x "$JAVACMD" ]; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ]; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ]; then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ]; do + if [ -d "$wdir"/.mvn ]; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$( + cd "$wdir/.." + pwd + ) + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' <"$1")" + fi +} + +BASE_DIR=$(find_maven_basedir "$(pwd)") +if [ -z "$BASE_DIR" ]; then + exit 1 +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in wrapperUrl) + jarUrl="$value" + break + ;; + esac + done <"$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget >/dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl >/dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=$(cygpath --path --windows "$M2_HOME") + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/Api/api-ports/api-eventlistener-kafka/mvnw.cmd b/Api/api-ports/api-eventlistener-kafka/mvnw.cmd new file mode 100644 index 000000000..c8d43372c --- /dev/null +++ b/Api/api-ports/api-eventlistener-kafka/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/Api/api-ports/api-eventlistener-kafka/pom.xml b/Api/api-ports/api-eventlistener-kafka/pom.xml new file mode 100644 index 000000000..02f69f254 --- /dev/null +++ b/Api/api-ports/api-eventlistener-kafka/pom.xml @@ -0,0 +1,120 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.4 + + + co.nilin.opex + api-eventlistener-kafka + 1.0-SNAPSHOT + api-eventlistener-kafka + Api kafka listener of Opex + + + 1.8 + 1.4.31 + ${version} + ${version} + ${version} + + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-webflux + + + co.nilin.opex + matching-core + ${matching.version} + provided + + + co.nilin.opex + accountant-core + ${accountant.version} + provided + + + co.nilin.opex + api-core + ${api.version} + provided + + + org.springframework.kafka + spring-kafka + + + io.projectreactor.kotlin + reactor-kotlin-extensions + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + + + org.jetbrains.kotlinx + kotlinx-coroutines-core + + + org.springframework.kafka + spring-kafka-test + test + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + -Xjsr305=strict + + + spring + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + + + diff --git a/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/config/ApiKafkaConfig.kt b/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/config/ApiKafkaConfig.kt new file mode 100644 index 000000000..5269d6acf --- /dev/null +++ b/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/config/ApiKafkaConfig.kt @@ -0,0 +1,107 @@ +package co.nilin.opex.port.api.kafka.config + +import co.nilin.opex.matching.core.eventh.events.CoreEvent +import co.nilin.opex.port.api.kafka.consumer.OrderKafkaListener +import co.nilin.opex.port.api.kafka.consumer.EventKafkaListener +import co.nilin.opex.port.api.kafka.consumer.TradeKafkaListener +import org.apache.kafka.clients.admin.NewTopic +import org.apache.kafka.clients.consumer.ConsumerConfig +import org.apache.kafka.clients.producer.ProducerConfig +import org.apache.kafka.common.serialization.StringDeserializer +import org.apache.kafka.common.serialization.StringSerializer +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.beans.factory.annotation.Value +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.context.support.GenericApplicationContext +import org.springframework.kafka.core.* +import org.springframework.kafka.listener.ConcurrentMessageListenerContainer +import org.springframework.kafka.listener.ContainerProperties +import org.springframework.kafka.support.serializer.JsonDeserializer +import org.springframework.kafka.support.serializer.JsonSerializer +import java.util.regex.Pattern + +@Configuration +class ApiKafkaConfig { + @Value("\${spring.kafka.bootstrap-servers}") + private val bootstrapServers: String? = null + + @Value("\${spring.kafka.consumer.group-id}") + private val groupId: String? = null + + @Bean("apiConsumerConfig") + fun consumerConfigs(): Map? { + val props: MutableMap = HashMap() + props[ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG] = bootstrapServers + props[ConsumerConfig.GROUP_ID_CONFIG] = groupId + props[ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG] = StringDeserializer::class.java + props[ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG] = JsonDeserializer::class.java + props[JsonDeserializer.TRUSTED_PACKAGES] = "co.nilin.opex.*" + return props + } + + @Bean("apiConsumerFactory") + fun consumerFactory(@Qualifier("apiConsumerConfig") consumerConfigs: Map): ConsumerFactory { + return DefaultKafkaConsumerFactory(consumerConfigs) + } + + @Bean("apiProducerConfig") + fun producerConfigs(): Map { + val props: MutableMap = HashMap() + props[ProducerConfig.BOOTSTRAP_SERVERS_CONFIG] = bootstrapServers + props[ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG] = StringSerializer::class.java + props[ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG] = JsonSerializer::class.java + return props + } + + @Bean("apiProducerFactory") + fun producerFactory(@Qualifier("apiProducerConfig") producerConfigs: Map): ProducerFactory { + return DefaultKafkaProducerFactory(producerConfigs) + } + + @Bean("apiKafkaTemplate") + fun kafkaTemplate(@Qualifier("apiProducerFactory") producerFactory: ProducerFactory): KafkaTemplate { + return KafkaTemplate(producerFactory) + } + + + @Autowired + @ConditionalOnBean(TradeKafkaListener::class) + fun configureTradeListener(tradeListener: TradeKafkaListener, @Qualifier("apiConsumerFactory") consumerFactory: ConsumerFactory) { + val containerProps = ContainerProperties(Pattern.compile("richTrade")) + containerProps.messageListener = tradeListener + val container = ConcurrentMessageListenerContainer(consumerFactory, containerProps) + container.setBeanName("ApiTradeKafkaListenerContainer") + container.start() + } + + @Autowired + @ConditionalOnBean(EventKafkaListener::class) + fun configureEventListener(eventListener: EventKafkaListener, @Qualifier("apiConsumerFactory") consumerFactory: ConsumerFactory) { + val containerProps = ContainerProperties(Pattern.compile("events_.*")) + containerProps.messageListener = eventListener + val container = ConcurrentMessageListenerContainer(consumerFactory, containerProps) + container.setBeanName("ApiEventKafkaListenerContainer") + container.start() + } + + @Autowired + @ConditionalOnBean(OrderKafkaListener::class) + fun configureOrderListener(orderListener: OrderKafkaListener, @Qualifier("apiConsumerFactory") consumerFactory: ConsumerFactory) { + val containerProps = ContainerProperties(Pattern.compile("richOrder")) + containerProps.messageListener = orderListener + val container = ConcurrentMessageListenerContainer(consumerFactory, containerProps) + container.setBeanName("ApiOrderKafkaListenerContainer") + container.start() + } + + @Autowired + fun createTopics(applicationContext: GenericApplicationContext) { + applicationContext.registerBean("topic_richOrder", NewTopic::class.java, "richOrder", 10, 1) + applicationContext.registerBean("topic_richTrade", NewTopic::class.java, "richTrade", 10, 1) + } + + +} \ No newline at end of file diff --git a/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/consumer/EventKafkaListener.kt b/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/consumer/EventKafkaListener.kt new file mode 100644 index 000000000..a5aafc0e0 --- /dev/null +++ b/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/consumer/EventKafkaListener.kt @@ -0,0 +1,28 @@ +package co.nilin.opex.port.api.kafka.consumer + + +import co.nilin.opex.matching.core.eventh.events.CoreEvent +import co.nilin.opex.port.api.kafka.spi.EventListener +import org.apache.kafka.clients.consumer.ConsumerRecord +import org.springframework.kafka.listener.MessageListener +import org.springframework.stereotype.Component + +@Component +class EventKafkaListener : MessageListener { + val eventListeners = arrayListOf() + override fun onMessage(data: ConsumerRecord) { + eventListeners.forEach { tl -> + tl.onEvent(data.value(), data.partition(), data.offset(), data.timestamp()) + } + } + + fun addEventListener(tl: EventListener) { + eventListeners.add(tl) + } + + fun removeEventListener(tl: EventListener) { + eventListeners.removeIf { item -> + item.id() == tl.id() + } + } +} \ No newline at end of file diff --git a/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/consumer/OrderKafkaListener.kt b/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/consumer/OrderKafkaListener.kt new file mode 100644 index 000000000..6fbe90511 --- /dev/null +++ b/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/consumer/OrderKafkaListener.kt @@ -0,0 +1,28 @@ +package co.nilin.opex.port.api.kafka.consumer + +import co.nilin.opex.accountant.core.inout.RichOrder +import co.nilin.opex.port.api.kafka.spi.RichOrderListener +import org.apache.kafka.clients.consumer.ConsumerRecord +import org.springframework.kafka.listener.MessageListener +import org.springframework.stereotype.Component + +@Component +class OrderKafkaListener : MessageListener { + val orderListeners = arrayListOf() + override fun onMessage(data: ConsumerRecord) { + orderListeners.forEach { tl -> + tl.onOrder(data.value(), data.partition(), data.offset(), data.timestamp()) + } + + } + + fun addOrderListener(tl: RichOrderListener) { + orderListeners.add(tl) + } + + fun removeOrderListener(tl: RichOrderListener) { + orderListeners.removeIf { item -> + item.id() == tl.id() + } + } +} \ No newline at end of file diff --git a/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/consumer/TradeKafkaListener.kt b/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/consumer/TradeKafkaListener.kt new file mode 100644 index 000000000..80a798b2d --- /dev/null +++ b/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/consumer/TradeKafkaListener.kt @@ -0,0 +1,27 @@ +package co.nilin.opex.port.api.kafka.consumer + +import co.nilin.opex.accountant.core.inout.RichTrade +import co.nilin.opex.port.api.kafka.spi.RichTradeListener +import org.apache.kafka.clients.consumer.ConsumerRecord +import org.springframework.kafka.listener.MessageListener +import org.springframework.stereotype.Component + +@Component +class TradeKafkaListener : MessageListener { + val tradeListeners = arrayListOf() + override fun onMessage(data: ConsumerRecord) { + tradeListeners.forEach { tl -> + tl.onTrade(data.value(), data.partition(), data.offset(), data.timestamp()) + } + } + + fun addTradeListener(tl: RichTradeListener) { + tradeListeners.add(tl) + } + + fun removeTradeListener(tl: RichTradeListener) { + tradeListeners.removeIf { item -> + item.id() == tl.id() + } + } +} \ No newline at end of file diff --git a/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/spi/EventListener.kt b/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/spi/EventListener.kt new file mode 100644 index 000000000..0d11ccf58 --- /dev/null +++ b/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/spi/EventListener.kt @@ -0,0 +1,9 @@ +package co.nilin.opex.port.api.kafka.spi + +import co.nilin.opex.matching.core.eventh.events.CoreEvent + + +interface EventListener { + fun id(): String + fun onEvent(coreEvent: CoreEvent, partition: Int, offset: Long, timestamp: Long) +} \ No newline at end of file diff --git a/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/spi/RichOrderListener.kt b/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/spi/RichOrderListener.kt new file mode 100644 index 000000000..75f759d06 --- /dev/null +++ b/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/spi/RichOrderListener.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.port.api.kafka.spi + +import co.nilin.opex.accountant.core.inout.RichOrder + +interface RichOrderListener { + fun id(): String + fun onOrder(order: RichOrder, partition: Int, offset: Long, timestamp: Long) +} \ No newline at end of file diff --git a/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/spi/RichTradeListener.kt b/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/spi/RichTradeListener.kt new file mode 100644 index 000000000..d81423890 --- /dev/null +++ b/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/spi/RichTradeListener.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.port.api.kafka.spi + +import co.nilin.opex.accountant.core.inout.RichTrade + +interface RichTradeListener { + fun id(): String + fun onTrade(trade: RichTrade, partition: Int, offset: Long, timestamp: Long) +} \ No newline at end of file diff --git a/Api/api-ports/api-persister-postgres/.gitignore b/Api/api-ports/api-persister-postgres/.gitignore new file mode 100644 index 000000000..de5a9214d --- /dev/null +++ b/Api/api-ports/api-persister-postgres/.gitignore @@ -0,0 +1,34 @@ +HELP.md +target/ +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +.mvn/ +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +.DS_Store diff --git a/Api/api-ports/api-persister-postgres/mvnw b/Api/api-ports/api-persister-postgres/mvnw new file mode 100644 index 000000000..3c8a55373 --- /dev/null +++ b/Api/api-ports/api-persister-postgres/mvnw @@ -0,0 +1,322 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ]; then + + if [ -f /etc/mavenrc ]; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ]; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false +darwin=false +mingw=false +case "$(uname)" in +CYGWIN*) cygwin=true ;; +MINGW*) mingw=true ;; +Darwin*) + darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="$(/usr/libexec/java_home)" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ]; then + if [ -r /etc/gentoo-release ]; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +if [ -z "$M2_HOME" ]; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ]; do + ls=$(ls -ld "$PRG") + link=$(expr "$ls" : '.*-> \(.*\)$') + if expr "$link" : '/.*' >/dev/null; then + PRG="$link" + else + PRG="$(dirname "$PRG")/$link" + fi + done + + saveddir=$(pwd) + + M2_HOME=$(dirname "$PRG")/.. + + # make it fully qualified + M2_HOME=$(cd "$M2_HOME" && pwd) + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=$(cygpath --unix "$M2_HOME") + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw; then + [ -n "$M2_HOME" ] && + M2_HOME="$( ( + cd "$M2_HOME" + pwd + ))" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="$( ( + cd "$JAVA_HOME" + pwd + ))" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr \"$javaExecutable\" : '\([^ ]*\)')" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=$(which readlink) + if [ ! $(expr "$readLink" : '\([^ ]*\)') = "no" ]; then + if $darwin; then + javaHome="$(dirname \"$javaExecutable\")" + javaExecutable="$(cd \"$javaHome\" && pwd -P)/javac" + else + javaExecutable="$(readlink -f \"$javaExecutable\")" + fi + javaHome="$(dirname \"$javaExecutable\")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ]; then + if [ -n "$JAVA_HOME" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="$(which java)" + fi +fi + +if [ ! -x "$JAVACMD" ]; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ]; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ]; then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ]; do + if [ -d "$wdir"/.mvn ]; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$( + cd "$wdir/.." + pwd + ) + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' <"$1")" + fi +} + +BASE_DIR=$(find_maven_basedir "$(pwd)") +if [ -z "$BASE_DIR" ]; then + exit 1 +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in wrapperUrl) + jarUrl="$value" + break + ;; + esac + done <"$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget >/dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl >/dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=$(cygpath --path --windows "$M2_HOME") + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/Api/api-ports/api-persister-postgres/mvnw.cmd b/Api/api-ports/api-persister-postgres/mvnw.cmd new file mode 100644 index 000000000..c8d43372c --- /dev/null +++ b/Api/api-ports/api-persister-postgres/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/Api/api-ports/api-persister-postgres/pom.xml b/Api/api-ports/api-persister-postgres/pom.xml new file mode 100644 index 000000000..3beabdc05 --- /dev/null +++ b/Api/api-ports/api-persister-postgres/pom.xml @@ -0,0 +1,131 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.4 + + + co.nilin.opex + api-persister-postgres + 1.0-SNAPSHOT + api-persister-postgres + Persist items of Opex api on Postgres + + + 1.8 + 1.4.31 + ${version} + ${version} + ${version} + ${version} + + + + + co.nilin.opex + matching-core + ${matching.version} + provided + + + co.nilin.opex + api-core + ${api.version} + provided + + + co.nilin.opex + accountant-core + ${accountant.version} + provided + + + co.nilin.opex + error-handler + ${utility.version} + provided + + + org.springframework.boot + spring-boot-starter-data-r2dbc + + + io.r2dbc + r2dbc-postgresql + runtime + + + org.postgresql + postgresql + runtime + + + + io.projectreactor.kotlin + reactor-kotlin-extensions + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + + + + org.jetbrains.kotlinx + kotlinx-coroutines-core + + + com.google.code.gson + gson + + + io.projectreactor + reactor-test + test + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + -Xjsr305=strict + + + spring + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + + diff --git a/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/config/PostgresConfig.kt b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/config/PostgresConfig.kt new file mode 100644 index 000000000..c88b8f921 --- /dev/null +++ b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/config/PostgresConfig.kt @@ -0,0 +1,69 @@ +package co.nilin.opex.port.api.postgres.config + +import org.springframework.context.annotation.Configuration +import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories +import org.springframework.r2dbc.core.DatabaseClient + +@Configuration +@EnableR2dbcRepositories(basePackages = ["co.nilin.opex"]) +class PostgresConfig(db: DatabaseClient) { + + init { + + val sql = """ + CREATE TABLE IF NOT EXISTS orders ( + id SERIAL PRIMARY KEY, + ouid VARCHAR(72) NOT NULL UNIQUE, + uuid VARCHAR(72) NOT NULL, + client_order_id VARCHAR(72), + symbol VARCHAR(20), + order_id numeric, + maker_fee decimal, + taker_fee decimal, + left_side_fraction decimal, + right_side_fraction decimal, + user_level VARCHAR(20), + side VARCHAR(20), + match_constraint VARCHAR(20), + order_type VARCHAR(20), + price decimal, + quantity decimal, + quote_quantity decimal, + executed_qty decimal, + accumulative_quote_qty decimal, + status integer, + create_date TIMESTAMP, + update_date TIMESTAMP + ); + CREATE TABLE IF NOT EXISTS trades ( + id SERIAL PRIMARY KEY, + trade_id numeric, + symbol VARCHAR(20), + matched_quantity decimal, + taker_price decimal, + maker_price decimal, + taker_commision decimal, + maker_commision decimal, + taker_commision_asset VARCHAR(20), + maker_commision_asset VARCHAR(20), + trade_date TIMESTAMP, + maker_ouid VARCHAR(72) NOT NULL, + taker_ouid VARCHAR(72) NOT NULL, + maker_uuid VARCHAR(72) NOT NULL, + taker_uuid VARCHAR(72) NOT NULL, + create_date TIMESTAMP + ); + CREATE TABLE IF NOT EXISTS symbol_maps ( + symbol VARCHAR(72) PRIMARY KEY, + value VARCHAR(72) UNIQUE NOT NULL + ); + INSERT INTO symbol_maps(symbol, value) VALUES('btc_usdt', 'BTCUSDT') ON CONFLICT DO NOTHING; + INSERT INTO symbol_maps(symbol, value) VALUES('eth_usdt', 'ETHUSDT') ON CONFLICT DO NOTHING; + INSERT INTO symbol_maps(symbol, value) VALUES('eth_btc', 'ETHBTC') ON CONFLICT DO NOTHING; + """ + val initDb = db.sql { sql } + initDb // initialize the database + .then() + .subscribe() // execute + } +} diff --git a/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/dao/OrderRepository.kt b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/dao/OrderRepository.kt new file mode 100644 index 000000000..1c3824c93 --- /dev/null +++ b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/dao/OrderRepository.kt @@ -0,0 +1,103 @@ +package co.nilin.opex.port.api.postgres.dao + +import co.nilin.opex.matching.core.model.OrderDirection +import co.nilin.opex.api.core.inout.AggregatedOrderPriceModel +import co.nilin.opex.port.api.postgres.model.OrderModel +import kotlinx.coroutines.flow.Flow +import org.springframework.data.r2dbc.repository.Query +import org.springframework.data.repository.query.Param +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono +import java.util.* + +@Repository +interface OrderRepository : ReactiveCrudRepository { + + @Query("select * from orders where ouid = :ouid") + fun findByOuid(@Param("ouid") ouid: String): Mono + + @Query("select * from orders where symbol = :symbol and order_id = :orderId") + fun findBySymbolAndOrderId( + @Param("symbol") + symbol: String, @Param("orderId") + orderId: Long + ): Mono + + @Query("select * from orders where symbol = :symbol and client_order_id = :origClientOrderId") + fun findBySymbolAndClientOrderId( + @Param("symbol") + symbol: String, @Param("origClientOrderId") + origClientOrderId: String + ): Mono + + @Query("select * from orders where uuid = :uuid and (:symbol is null or symbol = :symbol) and status in (:statuses)") + fun findByUuidAndSymbolAndStatus( + @Param("uuid") + uuid: String, + @Param("symbol") + symbol: String?, @Param("statuses") + status: Collection + ): Flow + + @Query( + "select * from orders where uuid = :uuid " + + "and (:symbol is null or symbol = :symbol) " + + "and (:startTime is null or update_date >= :startTime)" + + "and (:endTime is null or update_date < :endTime)" + ) + fun findByUuidAndSymbolAndTimeBetween( + @Param("uuid") + uuid: String, + @Param("symbol") + symbol: String?, + @Param("startTime") + startTime: Date?, + @Param("endTime") + endTime: Date? + ): Flow + + @Query( + """ + select price, (sum(quantity) - sum(executed_qty)) as quantity from orders + where symbol = :symbol and side = :direction and status in (:statuses) + group by price + order by price asc + limit :limit + """ + ) + fun findBySymbolAndDirectionAndStatusSortAscendingByPrice( + @Param("symbol") + symbol: String, + @Param("direction") + direction: OrderDirection, + @Param("limit") + limit: Int, + @Param("statuses") + status: Collection + ): Flux + + @Query( + """ + select price, (sum(quantity) - sum(executed_qty)) as quantity from orders + where symbol = :symbol and side = :direction and status in (:statuses) + group by price + order by price desc + limit :limit + """ + ) + fun findBySymbolAndDirectionAndStatusSortDescendingByPrice( + @Param("symbol") + symbol: String, + @Param("direction") + direction: OrderDirection, + @Param("limit") + limit: Int, + @Param("statuses") + status: Collection + ): Flux + + @Query("select * from orders where symbol = :symbol order by create_date desc limit 1") + fun findLastOrderBySymbol(@Param("symbol") symbol: String): Mono +} \ No newline at end of file diff --git a/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/dao/SymbolMapRepository.kt b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/dao/SymbolMapRepository.kt new file mode 100644 index 000000000..1a1d7dcf6 --- /dev/null +++ b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/dao/SymbolMapRepository.kt @@ -0,0 +1,18 @@ +package co.nilin.opex.port.api.postgres.dao + +import co.nilin.opex.port.api.postgres.model.SymbolMapModel +import org.springframework.data.r2dbc.repository.Query +import org.springframework.data.repository.query.Param +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository +import reactor.core.publisher.Mono + +@Repository +interface SymbolMapRepository : ReactiveCrudRepository { + + @Query("select * from symbol_maps where symbol = :symbol") + fun findBySymbol(@Param("symbol") symbol: String): Mono + + @Query("select * from symbol_maps where value = :value") + fun findByValue(@Param("value") value: String): Mono +} \ No newline at end of file diff --git a/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/dao/TradeRepository.kt b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/dao/TradeRepository.kt new file mode 100644 index 000000000..414f18091 --- /dev/null +++ b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/dao/TradeRepository.kt @@ -0,0 +1,109 @@ +package co.nilin.opex.port.api.postgres.dao + +import co.nilin.opex.port.api.postgres.model.TradeModel +import co.nilin.opex.port.api.postgres.model.TradeTickerData +import kotlinx.coroutines.flow.Flow +import org.springframework.data.r2dbc.repository.Query +import org.springframework.data.repository.query.Param +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono +import java.time.LocalDateTime +import java.util.* + +@Repository +interface TradeRepository : ReactiveCrudRepository { + + @Query("select * from trades where :ouid in (taker_ouid, maker_ouid) ") + fun findByOuid(@Param("ouid") ouid: String): Flow + + @Query( + """ + select * from trades where :uuid in (taker_uuid, maker_uuid) + and (:fromTrade is null or id > :fromTrade) + and (:symbol is null or symbol = :symbol) + and (:startTime is null or trade_date >= :startTime) + and (:endTime is null or trade_date < :endTime) + """ + ) + fun findByUuidAndSymbolAndTimeBetweenAndTradeIdGreaterThan( + @Param("uuid") + uuid: String, + @Param("symbol") + symbol: String?, + @Param("fromTrade") + fromTrade: Long?, + @Param("startTime") + startTime: Date?, + @Param("endTime") + endTime: Date? + ): Flow + + @Query("select * from trades where symbol = :symbol order by create_date desc limit :limit") + fun findBySymbolSortDescendingByCreateDate( + @Param("symbol") + symbol: String, + @Param("limit") + limit: Int + ): Flow + + @Query( + """ + select symbol, + (select taker_price from trades where create_date > :date and symbol=t.symbol order by create_date desc limit 1) - (select taker_price from trades where create_date > :date and symbol=t.symbol order by create_date asc limit 1) as price_change, + ((((select taker_price from trades where create_date > :date and symbol=t.symbol order by create_date desc limit 1) - (select taker_price from trades where create_date > :date and symbol=t.symbol order by create_date asc limit 1))/(select taker_price from trades where create_date > :date and symbol=t.symbol order by create_date asc limit 1))*100) as price_change_percent, + (sum(matched_quantity)/sum(taker_price)) as weighted_avg_price, + (select taker_price from trades where create_date > :date and symbol=t.symbol order by create_date asc limit 1) as last_price, + (select matched_quantity from trades where create_date > :date and symbol=t.symbol order by create_date asc limit 1) as last_qty, + (select price from orders where create_date > :date and symbol=t.symbol and (status=1 or status=4) and side='BID' order by create_date desc limit 1) as bid_price, + (select price from orders where create_date > :date and symbol=t.symbol and (status=1 or status=4) and side='ASK' order by create_date asc limit 1) as ask_price, + (select price from orders where create_date > :date and symbol=t.symbol and (status=1 or status=4) order by create_date desc limit 1) as open_price, + max(taker_price) as high_price, + min(taker_price) as low_price, + sum(matched_quantity) as volume, + (select id from trades where create_date > :date and symbol=t.symbol order by create_date asc limit 1) as first_id, + (select id from trades where create_date > :date and symbol=t.symbol order by create_date desc limit 1) as last_id, + count(id) as count + from trades as t + where create_date > :date + group by symbol + """ + ) + fun tradeTicker(@Param("date") createDate: LocalDateTime): Flux + + @Query( + """ + select symbol, + (select taker_price from trades where create_date > :date and symbol=:symbol order by create_date desc limit 1) - (select taker_price from trades where create_date > :date and symbol=:symbol order by create_date asc limit 1) as price_change, + ((((select taker_price from trades where create_date > :date and symbol=:symbol order by create_date desc limit 1) - (select taker_price from trades where create_date > :date and symbol=:symbol order by create_date asc limit 1))/(select taker_price from trades where create_date > :date and symbol=:symbol order by create_date asc limit 1))*100) as price_change_percent, + (sum(matched_quantity)/sum(taker_price)) as weighted_avg_price, + (select taker_price from trades where create_date > :date and symbol=:symbol order by create_date asc limit 1) as last_price, + (select matched_quantity from trades where create_date > :date and symbol=:symbol order by create_date asc limit 1) as last_qty, + (select price from orders where create_date > :date and symbol=t.symbol and (status=1 or status=4) and side='BID' order by create_date desc limit 1) as bid_price, + (select price from orders where create_date > :date and symbol=t.symbol and (status=1 or status=4) and side='ASK' order by create_date asc limit 1) as ask_price, + (select price from orders where create_date > :date and symbol=t.symbol and (status=1 or status=4) order by create_date desc limit 1) as open_price, + max(taker_price) as high_price, + min(taker_price) as low_price, + sum(matched_quantity) as volume, + (select id from trades where create_date > :date and symbol=:symbol order by create_date asc limit 1) as first_id, + (select id from trades where create_date > :date and symbol=:symbol order by create_date desc limit 1) as last_id, + count(id) as count + from trades as t + where create_date > :date and symbol = :symbol + group by symbol + """ + ) + fun tradeTickerBySymbol( + @Param("symbol") + symbol: String, + @Param("date") + createDate: LocalDateTime, + ): Mono + + @Query("select * from trades where create_date in (select max(create_date) from trades group by symbol) and symbol = :symbol") + fun findBySymbolGroupBySymbol(@Param("symbol") symbol: String): Flux + + @Query("select * from trades where create_date in (select max(create_date) from trades group by symbol)") + fun findAllGroupBySymbol(): Flux +} \ No newline at end of file diff --git a/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/impl/MarketQueryHandlerImpl.kt b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/impl/MarketQueryHandlerImpl.kt new file mode 100644 index 000000000..6d9f508b2 --- /dev/null +++ b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/impl/MarketQueryHandlerImpl.kt @@ -0,0 +1,169 @@ +package co.nilin.opex.port.api.postgres.impl + +import co.nilin.opex.api.core.inout.* +import co.nilin.opex.api.core.spi.MarketQueryHandler +import co.nilin.opex.api.core.spi.SymbolMapper +import co.nilin.opex.matching.core.model.OrderDirection +import co.nilin.opex.port.api.postgres.dao.OrderRepository +import co.nilin.opex.port.api.postgres.dao.TradeRepository +import co.nilin.opex.port.api.postgres.model.OrderModel +import co.nilin.opex.port.api.postgres.model.TradeTickerData +import co.nilin.opex.port.api.postgres.util.* +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.reactive.awaitFirst +import kotlinx.coroutines.reactive.awaitFirstOrElse +import kotlinx.coroutines.reactive.awaitFirstOrNull +import org.springframework.stereotype.Component +import java.lang.Exception +import java.time.LocalDateTime +import java.time.ZoneId +import java.time.ZoneOffset +import java.util.* +import kotlin.math.max +import kotlin.math.min + +@Component +class MarketQueryHandlerImpl( + private val orderRepository: OrderRepository, + private val tradeRepository: TradeRepository, + private val symbolMapper: SymbolMapper, +) : MarketQueryHandler { + + override suspend fun getTradeTickerData(startFrom: LocalDateTime): List { + return tradeRepository.tradeTicker(startFrom) + .collectList() + .awaitFirstOrElse { emptyList() } + .map { it.asPriceChangeResponse(Date().time, startFrom.toInstant(ZoneOffset.UTC).toEpochMilli()) } + + } + + override suspend fun getTradeTickerDataBySymbol(symbol: String, startFrom: LocalDateTime): PriceChangeResponse { + return tradeRepository.tradeTickerBySymbol(symbol, startFrom) + .awaitFirstOrNull() + ?.asPriceChangeResponse(Date().time, startFrom.toInstant(ZoneOffset.UTC).toEpochMilli()) + ?: PriceChangeResponse( + symbol = symbol, + openTime = Date().time, + closeTime = startFrom.toInstant(ZoneOffset.UTC).toEpochMilli() + ) + } + + override suspend fun openBidOrders(symbol: String, limit: Int): List { + return orderRepository.findBySymbolAndDirectionAndStatusSortDescendingByPrice( + symbol, + OrderDirection.BID, + limit, + listOf(OrderStatus.NEW.code, OrderStatus.PARTIALLY_FILLED.code) + ).collectList() + .awaitFirstOrElse { emptyList() } + .map { OrderBookResponse(it.price?.toBigDecimal(), it.quantity?.toBigDecimal()) } + } + + override suspend fun openAskOrders(symbol: String, limit: Int): List { + return orderRepository.findBySymbolAndDirectionAndStatusSortAscendingByPrice( + symbol, + OrderDirection.ASK, + limit, + listOf(OrderStatus.NEW.code, OrderStatus.PARTIALLY_FILLED.code) + ).collectList() + .awaitFirstOrElse { emptyList() } + .map { OrderBookResponse(it.price?.toBigDecimal(), it.quantity?.toBigDecimal()) } + } + + override suspend fun lastOrder(symbol: String): QueryOrderResponse? { + return orderRepository.findLastOrderBySymbol(symbol) + .awaitFirstOrNull() + ?.asQueryOrderResponse() + } + + override suspend fun recentTrades(symbol: String, limit: Int): Flow { + return tradeRepository.findBySymbolSortDescendingByCreateDate(symbol, limit) + .map { + val takerOrder = orderRepository.findByOuid(it.takerOuid).awaitFirst() + val makerOrder = orderRepository.findByOuid(it.makerOuid).awaitFirst() + val isMakerBuyer = makerOrder.direction == OrderDirection.BID + MarketTradeResponse( + it.symbol, + it.tradeId, + if (isMakerBuyer) it.makerPrice.toBigDecimal() else it.takerPrice.toBigDecimal(), + it.matchedQuantity.toBigDecimal(), + if (isMakerBuyer) + makerOrder.quoteQuantity!!.toBigDecimal() + else + takerOrder.quoteQuantity!!.toBigDecimal(), + Date.from(it.createDate.atZone(ZoneId.systemDefault()).toInstant()), + true, + isMakerBuyer + ) + } + } + + override suspend fun lastPrice(symbol: String?): List { + val list = if (symbol.isNullOrEmpty()) + tradeRepository.findAllGroupBySymbol() + else + tradeRepository.findBySymbolGroupBySymbol(symbol) + return list.collectList() + .awaitFirstOrElse { emptyList() } + .map { + val makerOrder = orderRepository.findByOuid(it.makerOuid).awaitFirst() + val apiSymbol = try { + symbolMapper.map(it.symbol) + } catch (e: Exception) { + it.symbol + } + val isMakerBuyer = makerOrder.direction == OrderDirection.BID + PriceTickerResponse( + apiSymbol, + if (isMakerBuyer) + min(it.takerPrice, it.makerPrice).toString() + else + max(it.takerPrice, it.makerPrice).toString() + ) + } + + } + + private fun OrderModel.asQueryOrderResponse() = QueryOrderResponse( + symbol, + ouid, + orderId ?: -1, + -1, + clientOrderId ?: "", + price!!.toBigDecimal(), + quantity!!.toBigDecimal(), + executedQuantity!!.toBigDecimal(), + (accumulativeQuoteQty ?: 0.0).toBigDecimal(), + status!!.toOrderStatus(), + constraint!!.toTimeInForce(), + type!!.toApiOrderType(), + direction!!.toOrderSide(), + null, + null, + Date.from(createDate!!.atZone(ZoneId.systemDefault()).toInstant()), + Date.from(updateDate.atZone(ZoneId.systemDefault()).toInstant()), + status.toOrderStatus().isWorking(), + quoteQuantity!!.toBigDecimal() + ) + + private fun TradeTickerData.asPriceChangeResponse(openTime: Long, closeTime: Long) = PriceChangeResponse( + symbol, + priceChange ?: 0.0, + priceChangePercent ?: 0.0, + weightedAvgPrice ?: 0.0, + lastPrice ?: 0.0, + lastQty ?: 0.0, + bidPrice ?: 0.0, + askPrice ?: 0.0, + openPrice ?: 0.0, + highPrice ?: 0.0, + lowPrice ?: 0.0, + volume ?: 0.0, + openTime, + closeTime, + firstId ?: -1, + lastId ?: -1, + count ?: 0 + ) +} \ No newline at end of file diff --git a/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/impl/OrderPersisterImpl.kt b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/impl/OrderPersisterImpl.kt new file mode 100644 index 000000000..04d2d5241 --- /dev/null +++ b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/impl/OrderPersisterImpl.kt @@ -0,0 +1,87 @@ +package co.nilin.opex.port.api.postgres.impl + +import co.nilin.opex.accountant.core.inout.RichOrder +import co.nilin.opex.accountant.core.inout.comesAfter +import co.nilin.opex.api.core.spi.OrderPersister +import co.nilin.opex.port.api.postgres.dao.OrderRepository +import co.nilin.opex.port.api.postgres.model.OrderModel +import kotlinx.coroutines.reactive.awaitFirstOrNull +import org.springframework.stereotype.Component +import java.time.LocalDateTime + +@Component +class OrderPersisterImpl(val orderRepository: OrderRepository) : OrderPersister { + override suspend fun save(order: RichOrder) { + var alreadySaved = false + val existingOrder = orderRepository + .findByOuid(order.ouid) + .awaitFirstOrNull() + if (existingOrder == null + || existingOrder.executedQuantity?.compareTo(order.executedQuantity.toDouble()) == -1 + || order.status.comesAfter(existingOrder.status) + ) { + alreadySaved = true + orderRepository.save( + OrderModel( + existingOrder?.id, + order.ouid, + order.uuid, + null, + order.pair, + order.orderId, + order.makerFee.toDouble(), + order.takerFee.toDouble(), + order.leftSideFraction.toDouble(), + order.rightSideFraction.toDouble(), + order.userLevel, + order.direction, + order.constraint, + order.type, + existingOrder?.price ?: order.price.toDouble(), + existingOrder?.quantity ?: order.quantity.toDouble(), + existingOrder?.quoteQuantity ?: order.quoteQuantity.toDouble(), + existingOrder?.executedQuantity ?: order.executedQuantity.toDouble(), + existingOrder?.accumulativeQuoteQty ?: order.accumulativeQuoteQty.toDouble(), + order.status, + existingOrder?.createDate ?: LocalDateTime.now(), + LocalDateTime.now() + ) + ).awaitFirstOrNull() + } + + existingOrder?.apply { + if ( + !alreadySaved && + (makerFee == null || takerFee == null || leftSideFraction == null + || rightSideFraction == null || constraint == null || type == null) + ) { + orderRepository.save( + OrderModel( + existingOrder.id, + existingOrder.ouid, + existingOrder.uuid, + null, + existingOrder.symbol, + existingOrder.orderId, + order.makerFee.toDouble(), + order.takerFee.toDouble(), + order.leftSideFraction.toDouble(), + order.rightSideFraction.toDouble(), + order.userLevel, + order.direction, + order.constraint, + order.type, + existingOrder.price, + existingOrder.quantity, + existingOrder.quoteQuantity, + existingOrder.executedQuantity, + existingOrder.accumulativeQuoteQty, + existingOrder.status, + existingOrder.createDate ?: LocalDateTime.now(), + existingOrder.updateDate + ) + ).awaitFirstOrNull() + } + } + } +} \ No newline at end of file diff --git a/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/impl/SymbolMapperImpl.kt b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/impl/SymbolMapperImpl.kt new file mode 100644 index 000000000..5f6795eb4 --- /dev/null +++ b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/impl/SymbolMapperImpl.kt @@ -0,0 +1,30 @@ +package co.nilin.opex.port.api.postgres.impl + +import co.nilin.opex.api.core.spi.SymbolMapper +import co.nilin.opex.port.api.postgres.dao.SymbolMapRepository +import kotlinx.coroutines.reactive.awaitFirstOrElse +import kotlinx.coroutines.reactive.awaitFirstOrNull +import org.springframework.stereotype.Component + +@Component +class SymbolMapperImpl(val symbolMapRepository: SymbolMapRepository) : SymbolMapper { + + override suspend fun map(symbol: String?): String? { + if (symbol == null) return null + return symbolMapRepository.findBySymbol(symbol).awaitFirstOrNull()?.value + } + + override suspend fun unmap(value: String?): String? { + if (value == null) return null + return symbolMapRepository.findByValue(value).awaitFirstOrNull()?.symbol + } + + override suspend fun getKeyValues(): Map { + val map = HashMap() + symbolMapRepository.findAll() + .collectList() + .awaitFirstOrElse { emptyList() } + .forEach { map[it.symbol] = it.value } + return map + } +} \ No newline at end of file diff --git a/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/impl/TradePersisterImpl.kt b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/impl/TradePersisterImpl.kt new file mode 100644 index 000000000..1e0ce1a41 --- /dev/null +++ b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/impl/TradePersisterImpl.kt @@ -0,0 +1,147 @@ +package co.nilin.opex.port.api.postgres.impl + +import co.nilin.opex.accountant.core.inout.OrderStatus +import co.nilin.opex.accountant.core.inout.RichTrade +import co.nilin.opex.accountant.core.inout.comesBefore +import co.nilin.opex.api.core.spi.TradePersister +import co.nilin.opex.port.api.postgres.dao.OrderRepository +import co.nilin.opex.port.api.postgres.dao.TradeRepository +import co.nilin.opex.port.api.postgres.model.OrderModel +import co.nilin.opex.port.api.postgres.model.TradeModel +import kotlinx.coroutines.reactive.awaitFirstOrNull +import org.springframework.stereotype.Component +import org.springframework.transaction.annotation.Transactional +import java.math.BigDecimal +import java.time.LocalDateTime + +@Component +class TradePersisterImpl(val tradeRepository: TradeRepository, val orderRepository: OrderRepository) : TradePersister { + + @Transactional + override suspend fun save(trade: RichTrade) { + println("RichTrade save") + tradeRepository.save( + TradeModel( + null, + trade.id, + trade.pair, + trade.matchedQuantity.toDouble(), + trade.takerPrice.toDouble(), + trade.makerPrice.toDouble(), + trade.takerCommision.toDouble(), + trade.makerCommision.toDouble(), + trade.takerCommisionAsset, + trade.makerCommisionAsset, + trade.tradeDateTime, + trade.makerOuid, + trade.takerOuid, + trade.makerUuid, + trade.takerUuid, + LocalDateTime.now() + ) + ).awaitFirstOrNull() + println("RichTrade save/update maker order") + saveMakerOrder(trade) + println("RichTrade save/update taker order") + saveTakerOrder(trade) + + } + + private suspend fun saveTakerOrder(trade: RichTrade) { + val existingOrder = orderRepository + .findByOuid(trade.takerOuid) + .awaitFirstOrNull() + + val executedQuantity = (trade.takerQuantity.minus(trade.takerRemainedQuantity)).toDouble() + val status = if (trade.takerRemainedQuantity.compareTo(BigDecimal.ZERO) == 0) { + OrderStatus.FILLED.code + } else { + OrderStatus.PARTIALLY_FILLED.code + } + + if (existingOrder == null || existingOrder.status.comesBefore(status) || (existingOrder.executedQuantity + ?: 0.0) < executedQuantity + ) + orderRepository.save( + OrderModel( + existingOrder?.id, + trade.takerOuid, + trade.takerUuid, + null, + trade.pair, + trade.takerOrderId, + existingOrder?.makerFee, + existingOrder?.takerFee, + existingOrder?.leftSideFraction, + existingOrder?.rightSideFraction, + existingOrder?.userLevel, + trade.takerDirection, + existingOrder?.constraint, + existingOrder?.type, + trade.takerPrice.toDouble(), + trade.takerQuantity.toDouble(), + trade.takerQuoteQuantity.toDouble(), + (trade.takerQuantity.minus(trade.takerRemainedQuantity)).toDouble(), + trade.takerPrice.multiply( + (trade.takerQuantity.minus(trade.takerRemainedQuantity)) + ).toDouble(), + if (trade.takerRemainedQuantity.compareTo(BigDecimal.ZERO) == 0) { + OrderStatus.FILLED.code + } else { + OrderStatus.PARTIALLY_FILLED.code + }, + existingOrder?.createDate, + LocalDateTime.now() + ) + ).awaitFirstOrNull() + } + + private suspend fun saveMakerOrder(trade: RichTrade) { + val existingOrder = orderRepository + .findByOuid(trade.makerOuid) + .awaitFirstOrNull() + + val executedQuantity = (trade.makerQuantity.minus(trade.makerRemainedQuantity)).toDouble() + val status = if (trade.makerRemainedQuantity.compareTo(BigDecimal.ZERO) == 0) { + OrderStatus.FILLED.code + } else { + OrderStatus.PARTIALLY_FILLED.code + } + + if (existingOrder == null || existingOrder.status.comesBefore(status) || (existingOrder.executedQuantity + ?: 0.0) < executedQuantity + ) + orderRepository.save( + OrderModel( + existingOrder?.id, + trade.makerOuid, + trade.makerUuid, + null, + trade.pair, + trade.makerOrderId, + existingOrder?.makerFee, + existingOrder?.takerFee, + existingOrder?.leftSideFraction, + existingOrder?.rightSideFraction, + existingOrder?.userLevel, + trade.makerDirection, + existingOrder?.constraint, + existingOrder?.type, + trade.makerPrice.toDouble(), + trade.makerQuantity.toDouble(), + trade.makerQuoteQuantity.toDouble(), + (trade.makerQuantity.minus(trade.makerRemainedQuantity)).toDouble(), + trade.makerPrice.multiply( + (trade.makerQuantity.minus(trade.makerRemainedQuantity)) + ).toDouble(), + if (trade.makerRemainedQuantity.compareTo(BigDecimal.ZERO) == 0) { + OrderStatus.FILLED.code + } else { + OrderStatus.PARTIALLY_FILLED.code + }, + existingOrder?.createDate ?: LocalDateTime.now(), + LocalDateTime.now() + ) + ).awaitFirstOrNull() + } +} \ No newline at end of file diff --git a/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/impl/UserQueryHandlerImpl.kt b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/impl/UserQueryHandlerImpl.kt new file mode 100644 index 000000000..1b967dded --- /dev/null +++ b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/impl/UserQueryHandlerImpl.kt @@ -0,0 +1,137 @@ +package co.nilin.opex.port.api.postgres.impl + +import co.nilin.opex.api.core.inout.* +import co.nilin.opex.api.core.spi.UserQueryHandler +import co.nilin.opex.matching.core.model.OrderDirection +import co.nilin.opex.port.api.postgres.dao.OrderRepository +import co.nilin.opex.port.api.postgres.dao.TradeRepository +import co.nilin.opex.port.api.postgres.model.OrderModel +import co.nilin.opex.port.api.postgres.util.* +import co.nilin.opex.api.core.inout.* +import co.nilin.opex.utility.error.data.OpexError +import co.nilin.opex.utility.error.data.OpexException +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.reactive.awaitFirst +import kotlinx.coroutines.reactive.awaitFirstOrNull +import org.springframework.stereotype.Component +import java.math.BigDecimal +import java.security.Principal +import java.time.ZoneId +import java.util.* + +@Component +class UserQueryHandlerImpl( + val orderRepository: OrderRepository, + val tradeRepository: TradeRepository +) : UserQueryHandler { + + override suspend fun queryOrder(principal: Principal, request: QueryOrderRequest): QueryOrderResponse? { + val order = (if (request.origClientOrderId != null) { + orderRepository.findBySymbolAndClientOrderId(request.symbol, request.origClientOrderId!!) + } else { + orderRepository.findBySymbolAndOrderId(request.symbol, request.orderId!!) + + }).awaitFirstOrNull() + if (order?.constraint != null) { + if (order.uuid != principal.name) + throw OpexException(OpexError.Forbidden) + return orderToQueryResponse(order) + } + return null + } + + override suspend fun openOrders(principal: Principal, symbol: String?): Flow { + return orderRepository.findByUuidAndSymbolAndStatus( + principal.name, + symbol, + listOf(OrderStatus.NEW.code, OrderStatus.PARTIALLY_FILLED.code) + ).filter { orderModel -> orderModel.constraint != null } + .map { order -> orderToQueryResponse(order) } + } + + override suspend fun allOrders(principal: Principal, allOrderRequest: AllOrderRequest): Flow { + return orderRepository.findByUuidAndSymbolAndTimeBetween( + principal.name, + allOrderRequest.symbol, + allOrderRequest.startTime, + allOrderRequest.endTime + ).filter { orderModel -> orderModel.constraint != null } + .map { order -> orderToQueryResponse(order) } + } + + override suspend fun allTrades(principal: Principal, request: TradeRequest): Flow { + return tradeRepository.findByUuidAndSymbolAndTimeBetweenAndTradeIdGreaterThan( + principal.name, request.symbol, request.fromTrade, request.startTime, request.endTime + ).map { trade -> + val takerOrder = orderRepository.findByOuid(trade.takerOuid).awaitFirst() + val makerOrder = orderRepository.findByOuid(trade.makerOuid).awaitFirst() + val isMakerBuyer = makerOrder.direction == OrderDirection.BID + TradeResponse( + trade.symbol, + trade.tradeId, + if (trade.takerUuid == principal.name) { + takerOrder.orderId!! + } else { + makerOrder.orderId!! + }, + -1, + if (trade.takerUuid == principal.name) { + trade.takerPrice.toBigDecimal() + } else { + trade.makerPrice.toBigDecimal() + }, + trade.matchedQuantity.toBigDecimal(), + if (isMakerBuyer) { + makerOrder.quoteQuantity!!.toBigDecimal() + } else { + takerOrder.quoteQuantity!!.toBigDecimal() + }, + if (trade.takerUuid == principal.name) { + trade.takerCommision!!.toBigDecimal() + } else { + trade.makerCommision!!.toBigDecimal() + }, + if (trade.takerUuid == principal.name) { + trade.takerCommisionAsset!! + } else { + trade.makerCommisionAsset!! + }, + Date.from( + trade.createDate.atZone(ZoneId.systemDefault()).toInstant() + ), + if (trade.takerUuid == principal.name) { + OrderDirection.ASK == takerOrder.direction + } else { + OrderDirection.ASK == makerOrder.direction + }, + trade.makerUuid == principal.name, + true, + isMakerBuyer + ) + } + } + + + private fun orderToQueryResponse(order: OrderModel) = QueryOrderResponse( + order.symbol, + order.ouid, + order.orderId ?: -1, + -1, + order.clientOrderId ?: "", + order.price!!.toBigDecimal(), + order.quantity!!.toBigDecimal(), + order.executedQuantity!!.toBigDecimal(), + (order.accumulativeQuoteQty ?: 0.0).toBigDecimal(), + order.status!!.toOrderStatus(), + order.constraint!!.toTimeInForce(), + order.type!!.toApiOrderType(), + order.direction!!.toOrderSide(), + null, + null, + Date.from(order.createDate!!.atZone(ZoneId.systemDefault()).toInstant()), + Date.from(order.updateDate.atZone(ZoneId.systemDefault()).toInstant()), + order.status.toOrderStatus().isWorking(), order.quoteQuantity!!.toBigDecimal() + ) +} \ No newline at end of file diff --git a/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/model/OrderModel.kt b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/model/OrderModel.kt new file mode 100644 index 000000000..291851ae8 --- /dev/null +++ b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/model/OrderModel.kt @@ -0,0 +1,38 @@ +package co.nilin.opex.port.api.postgres.model + + +import co.nilin.opex.matching.core.model.MatchConstraint +import co.nilin.opex.matching.core.model.OrderDirection +import co.nilin.opex.matching.core.model.OrderType +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Column +import org.springframework.data.relational.core.mapping.Table +import java.time.LocalDateTime + +@Table("orders") +class OrderModel( + @Id var id: Long?, + @Column(value = "ouid") + val ouid: String, + val uuid: String, + @Column(value = "client_order_id") + val clientOrderId: String?, + val symbol: String, + @Column(value = "order_id") val orderId: Long?, + @Column("maker_fee") val makerFee: Double?, + @Column("taker_fee") val takerFee: Double?, + @Column("left_side_fraction") val leftSideFraction: Double?, + @Column("right_side_fraction") val rightSideFraction: Double?, + @Column("user_level") val userLevel: String?, + @Column("side") val direction: OrderDirection?, + @Column("match_constraint") val constraint: MatchConstraint?, + @Column("order_type") val type: OrderType?, + @Column("price") val price: Double?, + @Column("quantity") val quantity: Double?, + @Column("quote_quantity") val quoteQuantity: Double?, + @Column("executed_qty") val executedQuantity: Double?, + @Column("accumulative_quote_qty") val accumulativeQuoteQty: Double?, + @Column("status") val status: Int?, + @Column("create_date") val createDate: LocalDateTime?, + @Column("update_date") val updateDate: LocalDateTime +) \ No newline at end of file diff --git a/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/model/SymbolMapModel.kt b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/model/SymbolMapModel.kt new file mode 100644 index 000000000..d43ee4e63 --- /dev/null +++ b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/model/SymbolMapModel.kt @@ -0,0 +1,12 @@ +package co.nilin.opex.port.api.postgres.model + + +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Column +import org.springframework.data.relational.core.mapping.Table + +@Table("symbol_maps") +class SymbolMapModel( + @Id val symbol: String, + @Column("value") val value: String, +) \ No newline at end of file diff --git a/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/model/TradeModel.kt b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/model/TradeModel.kt new file mode 100644 index 000000000..1fc1206cd --- /dev/null +++ b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/model/TradeModel.kt @@ -0,0 +1,27 @@ +package co.nilin.opex.port.api.postgres.model + + +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Column +import org.springframework.data.relational.core.mapping.Table +import java.time.LocalDateTime + +@Table("trades") +class TradeModel( + @Id var id: Long?, + @Column("trade_id") val tradeId: Long, + val symbol: String, + @Column("matched_quantity") val matchedQuantity: Double, + @Column("taker_price") val takerPrice: Double, + @Column("maker_price") val makerPrice: Double, + @Column("taker_commision") val takerCommision: Double?, + @Column("maker_commision") val makerCommision: Double?, + @Column("taker_commision_asset") val takerCommisionAsset: String?, + @Column("maker_commision_asset") val makerCommisionAsset: String?, + @Column("trade_date") val tradeDate: LocalDateTime, + @Column("maker_ouid") val makerOuid: String, + @Column("taker_ouid") val takerOuid: String, + @Column("maker_uuid") val makerUuid: String, + @Column("taker_uuid") val takerUuid: String, + @Column("create_date") val createDate: LocalDateTime +) \ No newline at end of file diff --git a/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/model/TradeTickerData.kt b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/model/TradeTickerData.kt new file mode 100644 index 000000000..c0e7bdd79 --- /dev/null +++ b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/model/TradeTickerData.kt @@ -0,0 +1,33 @@ +package co.nilin.opex.port.api.postgres.model + +import org.springframework.data.relational.core.mapping.Column + +data class TradeTickerData( + val symbol: String, + @Column("price_change") + val priceChange: Double?, + @Column("price_change_percent") + val priceChangePercent: Double?, + @Column("weighted_avg_price") + val weightedAvgPrice: Double?, + @Column("last_price") + val lastPrice: Double?, + @Column("last_qty") + val lastQty: Double?, + @Column("bid_price") + val bidPrice: Double?, + @Column("ask_price") + val askPrice: Double?, + @Column("open_price") + val openPrice: Double?, + @Column("high_price") + val highPrice: Double?, + @Column("low_price") + val lowPrice: Double?, + val volume: Double?, + @Column("first_id") + val firstId: Long?, + @Column("last_id") + val lastId: Long?, + val count: Long?, +) diff --git a/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/util/EnumExtensions.kt b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/util/EnumExtensions.kt new file mode 100644 index 000000000..8c993b8db --- /dev/null +++ b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/util/EnumExtensions.kt @@ -0,0 +1,45 @@ +package co.nilin.opex.port.api.postgres.util + +import co.nilin.opex.api.core.inout.OrderSide +import co.nilin.opex.api.core.inout.OrderStatus +import co.nilin.opex.api.core.inout.TimeInForce +import co.nilin.opex.matching.core.model.MatchConstraint +import co.nilin.opex.matching.core.model.OrderDirection +import co.nilin.opex.matching.core.model.OrderType + +fun MatchConstraint.toTimeInForce(): TimeInForce { + if (this == MatchConstraint.FOK_BUDGET) + return TimeInForce.FOK + if (this == MatchConstraint.IOC_BUDGET) + return TimeInForce.IOC + return TimeInForce.valueOf(this.name) +} + + +fun TimeInForce.toMatchConstraint(): MatchConstraint { + return MatchConstraint.valueOf(this.name) +} + +fun OrderType.toApiOrderType(): co.nilin.opex.api.core.inout.OrderType { + if (this == OrderType.LIMIT_ORDER) + return co.nilin.opex.api.core.inout.OrderType.LIMIT + if (this == OrderType.MARKET_ORDER) + return co.nilin.opex.api.core.inout.OrderType.MARKET + throw IllegalArgumentException("OrderType $this is not supported!") +} + +fun OrderDirection.toOrderSide(): OrderSide { + if (this == OrderDirection.BID) + return OrderSide.BUY + return OrderSide.SELL +} + +fun OrderStatus.isWorking(): Boolean { + return listOf(OrderStatus.NEW, OrderStatus.PARTIALLY_FILLED).contains(this) +} + +fun Int.toOrderStatus(): OrderStatus { + val status = co.nilin.opex.accountant.core.inout.OrderStatus.values() + .find { s -> s.code == this } + return OrderStatus.valueOf(status!!.name) +} \ No newline at end of file diff --git a/Api/pom.xml b/Api/pom.xml new file mode 100644 index 000000000..8e670c159 --- /dev/null +++ b/Api/pom.xml @@ -0,0 +1,18 @@ + + + 4.0.0 + co.nilin.opex + api-root + 1.0-SNAPSHOT + api-root + pom + Api root of Opex + + api-core + api-app + api-ports/api-persister-postgres + api-ports/api-binance-rest + api-ports/api-eventlistener-kafka + + diff --git a/BlockchainGateway/.gitignore b/BlockchainGateway/.gitignore new file mode 100644 index 000000000..785786ed7 --- /dev/null +++ b/BlockchainGateway/.gitignore @@ -0,0 +1,47 @@ +# Created by .ignore support plugin (hsz.mobi) +### Kotlin template +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### JetBrains template +.idea/** +.idea +.idea/ +*.iml +*.ipr + +# File-based project format +*.iws + +# IntelliJ +out/ + +target/ + + + + +!/.idea/ + +.DS_Store diff --git a/BlockchainGateway/bc-gateway-app/.gitignore b/BlockchainGateway/bc-gateway-app/.gitignore new file mode 100644 index 000000000..0d6c2228e --- /dev/null +++ b/BlockchainGateway/bc-gateway-app/.gitignore @@ -0,0 +1,35 @@ +HELP.md +target/ +.mvn/ +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +.DS_Store diff --git a/BlockchainGateway/bc-gateway-app/Dockerfile b/BlockchainGateway/bc-gateway-app/Dockerfile new file mode 100644 index 000000000..f2cbd4c26 --- /dev/null +++ b/BlockchainGateway/bc-gateway-app/Dockerfile @@ -0,0 +1,5 @@ +FROM openjdk:8-jdk-alpine +VOLUME /tmp +ARG JAR_FILE=target/*.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"] \ No newline at end of file diff --git a/BlockchainGateway/bc-gateway-app/mvnw b/BlockchainGateway/bc-gateway-app/mvnw new file mode 100644 index 000000000..a16b5431b --- /dev/null +++ b/BlockchainGateway/bc-gateway-app/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/BlockchainGateway/bc-gateway-app/mvnw.cmd b/BlockchainGateway/bc-gateway-app/mvnw.cmd new file mode 100644 index 000000000..c8d43372c --- /dev/null +++ b/BlockchainGateway/bc-gateway-app/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/BlockchainGateway/bc-gateway-app/pom.xml b/BlockchainGateway/bc-gateway-app/pom.xml new file mode 100644 index 000000000..71355c3df --- /dev/null +++ b/BlockchainGateway/bc-gateway-app/pom.xml @@ -0,0 +1,232 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.4 + + + co.nilin.opex.external + bc-gateway-app + 1.0-SNAPSHOT + bc-gateway-app + Blockchain gateway app of Opex + + + 1.8 + 1.4.31 + ${version} + ${version} + 2020.0.2 + + + + + org.springframework.boot + spring-boot-starter-webflux + + + com.fasterxml.jackson.module + jackson-module-kotlin + + + + org.jetbrains.kotlin + kotlin-stdlib + + + io.projectreactor.kotlin + reactor-kotlin-extensions + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + + + org.jetbrains.kotlinx + kotlinx-coroutines-core + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + io.projectreactor + reactor-test + test + + + org.springframework.cloud + spring-cloud-starter-consul-all + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-oauth2-resource-server + + + org.bouncycastle + bcprov-jdk15on + 1.60 + + + co.nilin.opex.external + bc-gateway-core + ${bc-gateway.version} + + + co.nilin.opex.external + bc-gateway-persister-postgres + ${bc-gateway.version} + + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + 2.18 + + + ${skip.unit.tests} + + + **/*IntegrationTest.java + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-test-source + generate-test-sources + + add-test-source + + + + src/test/java + + + + + compile + + add-source + + + + src/main/java + + + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + compile + + + + test-compile + test-compile + + test-compile + + + + + + -Xjsr305=strict + + + spring + + 1.8 + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + compile + compile + + compile + + + + testCompile + test-compile + + testCompile + + + + + + opex-bc-gateway + + + diff --git a/BlockchainGateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/BCGatewayApp.kt b/BlockchainGateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/BCGatewayApp.kt new file mode 100644 index 000000000..3762d89a6 --- /dev/null +++ b/BlockchainGateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/BCGatewayApp.kt @@ -0,0 +1,13 @@ +package co.nilin.opex.bcgateway.app + +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication +import org.springframework.context.annotation.ComponentScan + +@SpringBootApplication +@ComponentScan("co.nilin.opex") +class BCGatewayApp + +fun main(args: Array) { + runApplication(*args) +} diff --git a/BlockchainGateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/config/AppConfig.kt b/BlockchainGateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/config/AppConfig.kt new file mode 100644 index 000000000..831ed2b07 --- /dev/null +++ b/BlockchainGateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/config/AppConfig.kt @@ -0,0 +1,64 @@ +package co.nilin.opex.bcgateway.app.config + + +import co.nilin.opex.bcgateway.core.api.AssignAddressService +import co.nilin.opex.bcgateway.core.api.ChainSyncService +import co.nilin.opex.bcgateway.core.api.InfoService +import co.nilin.opex.bcgateway.core.api.WalletSyncService +import co.nilin.opex.bcgateway.core.service.AssignAddressServiceImpl +import co.nilin.opex.bcgateway.core.service.ChainSyncServiceImpl +import co.nilin.opex.bcgateway.core.service.InfoServiceImpl +import co.nilin.opex.bcgateway.core.service.WalletSyncServiceImpl +import co.nilin.opex.bcgateway.core.spi.* +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.transaction.reactive.TransactionalOperator + +@Configuration +class AppConfig { + + @Bean + fun assignAddressService( + currencyLoader: CurrencyLoader, + assignedAddressHandler: AssignedAddressHandler, + reservedAddressHandler: ReservedAddressHandler + ): AssignAddressService { + return AssignAddressServiceImpl(currencyLoader, assignedAddressHandler, reservedAddressHandler) + } + + @Bean + fun chainSyncService( + chainSyncSchedulerHandler: ChainSyncSchedulerHandler, + chainEndpointProxyFinder: ChainEndpointProxyFinder, + chainSyncRecordHandler: ChainSyncRecordHandler, + walletSyncRecordHandler: WalletSyncRecordHandler, + currencyLoader: CurrencyLoader, + operator: TransactionalOperator + ): ChainSyncService { + return ChainSyncServiceImpl( + chainSyncSchedulerHandler, + chainEndpointProxyFinder, + chainSyncRecordHandler, + walletSyncRecordHandler, + currencyLoader, + operator, + AppDispatchers.chainSyncExecutor + ) + } + + @Bean + fun walletSyncService( + syncSchedulerHandler: WalletSyncSchedulerHandler, + walletProxy: WalletProxy, + walletSyncRecordHandler: WalletSyncRecordHandler, + assignedAddressHandler: AssignedAddressHandler, + currencyLoader: CurrencyLoader + ): WalletSyncService { + return WalletSyncServiceImpl(syncSchedulerHandler, walletProxy, walletSyncRecordHandler, assignedAddressHandler, currencyLoader, AppDispatchers.walletSyncExecutor) + } + + @Bean + fun infoService(): InfoService { + return InfoServiceImpl() + } +} diff --git a/BlockchainGateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/config/AppDispatchers.kt b/BlockchainGateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/config/AppDispatchers.kt new file mode 100644 index 000000000..f03b24dc8 --- /dev/null +++ b/BlockchainGateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/config/AppDispatchers.kt @@ -0,0 +1,9 @@ +package co.nilin.opex.bcgateway.app.config + +import kotlinx.coroutines.asCoroutineDispatcher +import java.util.concurrent.Executors + +object AppDispatchers { + val chainSyncExecutor = Executors.newFixedThreadPool(5).asCoroutineDispatcher() + val walletSyncExecutor = Executors.newFixedThreadPool(2).asCoroutineDispatcher() +} \ No newline at end of file diff --git a/BlockchainGateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/config/SecurityConfig.kt b/BlockchainGateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/config/SecurityConfig.kt new file mode 100644 index 000000000..dfd54722a --- /dev/null +++ b/BlockchainGateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/config/SecurityConfig.kt @@ -0,0 +1,45 @@ +package co.nilin.opex.bcgateway.app.config + +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Bean +import org.springframework.core.io.ClassPathResource +import org.springframework.core.io.Resource +import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity +import org.springframework.security.config.web.server.ServerHttpSecurity +import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder +import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder +import org.springframework.security.web.server.SecurityWebFilterChain +import org.springframework.util.Base64Utils +import org.springframework.util.FileCopyUtils +import org.springframework.web.reactive.function.client.WebClient +import java.security.KeyFactory +import java.security.interfaces.RSAPublicKey +import java.security.spec.X509EncodedKeySpec + +@EnableWebFluxSecurity +class SecurityConfig(private val webClient: WebClient) { + + @Value("\${app.auth.cert-url}") + private lateinit var jwkUrl: String + + @Bean + fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain? { + http.csrf().disable() + .authorizeExchange() + .pathMatchers("/filter/**").hasAuthority("SCOPE_trust") + .pathMatchers("/**").permitAll() + .anyExchange().authenticated() + .and() + .oauth2ResourceServer() + .jwt() + return http.build() + } + + @Bean + @Throws(Exception::class) + fun reactiveJwtDecoder(): ReactiveJwtDecoder? { + return NimbusReactiveJwtDecoder.withJwkSetUri(jwkUrl) + .webClient(webClient) + .build() + } +} diff --git a/BlockchainGateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/config/WebClientConfig.kt b/BlockchainGateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/config/WebClientConfig.kt new file mode 100644 index 000000000..934bbd706 --- /dev/null +++ b/BlockchainGateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/config/WebClientConfig.kt @@ -0,0 +1,25 @@ +package co.nilin.opex.bcgateway.app.config + +import org.springframework.cloud.client.ServiceInstance +import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties +import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer +import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerExchangeFilterFunction +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.web.reactive.function.client.WebClient + +@Configuration +class WebClientConfig { + + @Bean + fun webClient(loadBalancerFactory: ReactiveLoadBalancer.Factory): WebClient { + return WebClient.builder() + .filter( + ReactorLoadBalancerExchangeFilterFunction( + loadBalancerFactory, LoadBalancerProperties(), emptyList() + ) + ) + .build() + } + +} diff --git a/BlockchainGateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/controller/DepositController.kt b/BlockchainGateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/controller/DepositController.kt new file mode 100644 index 000000000..e67bef938 --- /dev/null +++ b/BlockchainGateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/controller/DepositController.kt @@ -0,0 +1,22 @@ +package co.nilin.opex.bcgateway.app.controller + +import co.nilin.opex.bcgateway.core.api.AssignAddressService +import co.nilin.opex.bcgateway.core.model.AssignedAddress +import co.nilin.opex.bcgateway.core.model.Currency +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RestController + +@RestController +class DepositController(val assignAddressService: AssignAddressService) { + data class AssignAddressRequest(val uuid: String, val currency: String) + data class AssignAddressResponse(val addresses: List) + + @PostMapping("deposits/assign") + suspend fun assignAddress(@RequestBody assignAddressRequest: AssignAddressRequest): AssignAddressResponse { + val assignedAddress = assignAddressService + .assignAddress(assignAddressRequest.uuid, + Currency(assignAddressRequest.currency, assignAddressRequest.currency)) + return AssignAddressResponse(assignedAddress) + } +} \ No newline at end of file diff --git a/BlockchainGateway/bc-gateway-app/src/main/resources/application-docker.yml b/BlockchainGateway/bc-gateway-app/src/main/resources/application-docker.yml new file mode 100644 index 000000000..ffbf14176 --- /dev/null +++ b/BlockchainGateway/bc-gateway-app/src/main/resources/application-docker.yml @@ -0,0 +1,22 @@ +server.port: 8095 +spring: + application: + name: opex-bc-gateway + kafka: + bootstrap-servers: ${KAFKA_IP_PORT} + redis: + host: ${REDIS_HOST} + port: 6379 + r2dbc: + url: r2dbc:postgresql://${DB_IP_PORT}/opex_bc_gateway + username: opex + password: hiopex + initialization-mode: always + cloud: + consul: + host: ${CONSUL_HOST} + port: 8500 + +app: + auth: + cert-url: lb://opex-auth/auth/realms/opex/protocol/openid-connect/certs \ No newline at end of file diff --git a/BlockchainGateway/bc-gateway-app/src/main/resources/application.yml b/BlockchainGateway/bc-gateway-app/src/main/resources/application.yml new file mode 100644 index 000000000..c21510415 --- /dev/null +++ b/BlockchainGateway/bc-gateway-app/src/main/resources/application.yml @@ -0,0 +1,31 @@ +server.port: 8095 +spring: + application: + name: opex-bc-gateway + main: + allow-bean-definition-overriding: false + kafka: + bootstrap-servers: localhost:2181 + consumer: + group-id: opex-bc-gateway + redis: + host: 127.0.0.1 + port: 6379 + r2dbc: + url: r2dbc:postgresql://localhost/opex_bc_gateway + username: opex + password: hiopex + initialization-mode: always + cloud: + bootstrap: + enabled: true + consul: + port: 8500 + discovery: + #healthCheckPath: ${management.context-path}/health + instance-id: ${spring.application.name}:${server.port} + healthCheckInterval: 20s + prefer-ip-address: true +logging: + level: + org.apache.kafka: DEBUG diff --git a/BlockchainGateway/bc-gateway-core/.gitignore b/BlockchainGateway/bc-gateway-core/.gitignore new file mode 100644 index 000000000..8e22e6c21 --- /dev/null +++ b/BlockchainGateway/bc-gateway-core/.gitignore @@ -0,0 +1,35 @@ +HELP.md +target/ +.mvn +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +.DS_Store diff --git a/BlockchainGateway/bc-gateway-core/mvnw b/BlockchainGateway/bc-gateway-core/mvnw new file mode 100644 index 000000000..a16b5431b --- /dev/null +++ b/BlockchainGateway/bc-gateway-core/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/BlockchainGateway/bc-gateway-core/mvnw.cmd b/BlockchainGateway/bc-gateway-core/mvnw.cmd new file mode 100644 index 000000000..c8d43372c --- /dev/null +++ b/BlockchainGateway/bc-gateway-core/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/BlockchainGateway/bc-gateway-core/pom.xml b/BlockchainGateway/bc-gateway-core/pom.xml new file mode 100644 index 000000000..bf2a407dd --- /dev/null +++ b/BlockchainGateway/bc-gateway-core/pom.xml @@ -0,0 +1,97 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.4 + + + co.nilin.opex.external + bc-gateway-core + 1.0-SNAPSHOT + bc-gateway-core + Blockchain gateway core of Opex + + + 1.8 + 1.4.31 + 3.2.0 + + + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + + + org.jetbrains.kotlinx + kotlinx-coroutines-core + + + io.projectreactor.kotlin + reactor-kotlin-extensions + + + org.springframework.boot + spring-boot-starter + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + org.springframework + spring-tx + provided + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + org.mockito.kotlin + mockito-kotlin + ${mockito-kotlin.version} + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + -Xjsr305=strict + + + spring + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + + + diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/api/AssignAddressService.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/api/AssignAddressService.kt new file mode 100644 index 000000000..275c8ec48 --- /dev/null +++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/api/AssignAddressService.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.bcgateway.core.api + +import co.nilin.opex.bcgateway.core.model.AssignedAddress +import co.nilin.opex.bcgateway.core.model.Currency + +interface AssignAddressService { + suspend fun assignAddress(user: String, currency: Currency): List +} \ No newline at end of file diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/api/ChainSyncService.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/api/ChainSyncService.kt new file mode 100644 index 000000000..ec62c097f --- /dev/null +++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/api/ChainSyncService.kt @@ -0,0 +1,5 @@ +package co.nilin.opex.bcgateway.core.api + +interface ChainSyncService { + suspend fun startSyncWithChain() +} \ No newline at end of file diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/api/InfoService.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/api/InfoService.kt new file mode 100644 index 000000000..838e2a7d8 --- /dev/null +++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/api/InfoService.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.bcgateway.core.api + +import co.nilin.opex.bcgateway.core.model.CurrencyInfo + +interface InfoService { + suspend fun countReservedAddresses(): Long + suspend fun getCurrencyInfo(): CurrencyInfo +} \ No newline at end of file diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/api/WalletSyncService.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/api/WalletSyncService.kt new file mode 100644 index 000000000..df81a410d --- /dev/null +++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/api/WalletSyncService.kt @@ -0,0 +1,5 @@ +package co.nilin.opex.bcgateway.core.api + +interface WalletSyncService { + suspend fun startSyncWithWallet() +} \ No newline at end of file diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/model/Address.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/model/Address.kt new file mode 100644 index 000000000..ff7a125fe --- /dev/null +++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/model/Address.kt @@ -0,0 +1,27 @@ +package co.nilin.opex.bcgateway.core.model + +data class AddressType(val id: Long, val type: String, val addressRegex: String, val memoRegex: String) +data class ReservedAddress(val address: String, val memo: String?, val type: AddressType) +data class AssignedAddress(val uuid: String, val address: String, val memo: String?, val type: AddressType, val chains: MutableList ){ + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as AssignedAddress + + if (uuid != other.uuid) return false + if (address != other.address) return false + if (memo != other.memo) return false + if (type != other.type) return false + + return true + } + + override fun hashCode(): Int { + var result = uuid.hashCode() + result = 31 * result + address.hashCode() + result = 31 * result + (memo?.hashCode() ?: 0) + result = 31 * result + type.hashCode() + return result + } +} \ No newline at end of file diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/model/Chain.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/model/Chain.kt new file mode 100644 index 000000000..d88430445 --- /dev/null +++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/model/Chain.kt @@ -0,0 +1,16 @@ +package co.nilin.opex.bcgateway.core.model + +import java.time.LocalDateTime + +data class Endpoint(val url: String) +data class Chain(val name: String, val addressTypes: List, val endpoints: List) +data class ChainSyncSchedule(val chainName: String, val retryTime: LocalDateTime, val delay: Long) +data class ChainSyncRecord( + val chainName: String, + val time: LocalDateTime, + val endpoint: Endpoint, + val latestBlock: Long?, + val success: Boolean, + val error: String?, + val records: List +) diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/model/Currency.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/model/Currency.kt new file mode 100644 index 000000000..73a9c0db5 --- /dev/null +++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/model/Currency.kt @@ -0,0 +1,17 @@ +package co.nilin.opex.bcgateway.core.model + +import java.math.BigDecimal + +data class Currency(val symbol: String, val name: String) +data class CurrencyImplementation( + val currency: Currency, + val chain: Chain, + val token: Boolean, + val tokenAddress: String?, + val tokenName: String?, + val withdrawEnabled: Boolean, + val withdrawFee: BigDecimal, + val withdrawMin: BigDecimal +) + +data class CurrencyInfo(val currency: Currency, val implementations: List) diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/model/Deposit.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/model/Deposit.kt new file mode 100644 index 000000000..d355d6df3 --- /dev/null +++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/model/Deposit.kt @@ -0,0 +1,13 @@ +package co.nilin.opex.bcgateway.core.model + +import java.math.BigDecimal + +data class Deposit( + val id: Long?, + val depositor: String, + val depositorMemo: String?, + val amount: BigDecimal, + val chain: String, + val token: Boolean, + val tokenAddress: String? +) diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/model/WalletSync.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/model/WalletSync.kt new file mode 100644 index 000000000..439e5e053 --- /dev/null +++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/model/WalletSync.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.bcgateway.core.model + +import java.time.LocalDateTime + +data class WalletSyncSchedule(val retryTime: LocalDateTime, val delay: Long, val batchSize: Long?) +data class WalletSyncRecord( + val time: LocalDateTime, val success: Boolean, val error: String?, val deposit: Deposit +) diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/service/AssignAddressServiceImpl.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/service/AssignAddressServiceImpl.kt new file mode 100644 index 000000000..ef4cb3a45 --- /dev/null +++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/service/AssignAddressServiceImpl.kt @@ -0,0 +1,66 @@ +package co.nilin.opex.bcgateway.core.service + +import co.nilin.opex.bcgateway.core.api.AssignAddressService +import co.nilin.opex.bcgateway.core.model.AddressType +import co.nilin.opex.bcgateway.core.model.AssignedAddress +import co.nilin.opex.bcgateway.core.model.Chain +import co.nilin.opex.bcgateway.core.model.Currency +import co.nilin.opex.bcgateway.core.spi.AssignedAddressHandler +import co.nilin.opex.bcgateway.core.spi.ReservedAddressHandler +import co.nilin.opex.bcgateway.core.spi.CurrencyLoader +import java.lang.RuntimeException + +class AssignAddressServiceImpl( + val currencyLoader: CurrencyLoader, + val assignedAddressHandler: AssignedAddressHandler, + val reservedAddressHandler: ReservedAddressHandler +) : AssignAddressService { + + override suspend fun assignAddress(user: String, currency: Currency): List { + val currencyInfo = currencyLoader.fetchCurrencyInfo(currency.symbol) + val chains = currencyInfo.implementations + .map { imp -> imp.chain } + val addressTypes = chains + .flatMap { chain -> chain.addressTypes } + .distinct() + val chainAddressTypeMap = HashMap>() + chains.forEach { chain -> + chain.addressTypes.forEach { addressType -> + chainAddressTypeMap.putIfAbsent(addressType, mutableListOf()) + chainAddressTypeMap.get(addressType)!!.add(chain) + } + } + val userAssignedAddresses = (assignedAddressHandler.fetchAssignedAddresses(user, addressTypes)).toMutableList() + val result = mutableSetOf() + addressTypes.forEach { addressType -> + val assigned = userAssignedAddresses.firstOrNull { assignAddress -> assignAddress.type.equals(addressType) } + if (assigned != null) { + chainAddressTypeMap.get(addressType)?.forEach { chain -> + if (!assigned.chains.contains(chain)) { + assigned.chains.add(chain) + } + } + result.add(assigned) + } else { + val reservedAddress = reservedAddressHandler.peekReservedAddress(addressType) + if (reservedAddress != null) { + val newAssigned = AssignedAddress( + user, + reservedAddress.address, + reservedAddress.memo, + addressType, + chainAddressTypeMap.get(addressType)!! + ) + reservedAddressHandler.remove(reservedAddress) + result.add(newAssigned) + } else + throw RuntimeException("No reserved address available for $addressType") + + } + } + result.forEach { address -> + assignedAddressHandler.persist(address) + } + return result.toMutableList() + } +} \ No newline at end of file diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/service/ChainSyncServiceImpl.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/service/ChainSyncServiceImpl.kt new file mode 100644 index 000000000..f8f8f4144 --- /dev/null +++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/service/ChainSyncServiceImpl.kt @@ -0,0 +1,54 @@ +package co.nilin.opex.bcgateway.core.service + +import co.nilin.opex.bcgateway.core.api.ChainSyncService +import co.nilin.opex.bcgateway.core.spi.* +import kotlinx.coroutines.* +import org.springframework.transaction.reactive.TransactionalOperator +import org.springframework.transaction.reactive.executeAndAwait +import java.time.LocalDateTime +import java.time.temporal.ChronoUnit +import kotlin.coroutines.coroutineContext + +open class ChainSyncServiceImpl( + private val chainSyncSchedulerHandler: ChainSyncSchedulerHandler, + private val chainEndpointProxyFinder: ChainEndpointProxyFinder, + private val chainSyncRecordHandler: ChainSyncRecordHandler, + private val walletSyncRecordHandler: WalletSyncRecordHandler, + private val currencyLoader: CurrencyLoader, + private val operator: TransactionalOperator, + private val dispatcher: ExecutorCoroutineDispatcher +) : ChainSyncService { + + override suspend fun startSyncWithChain() { + withContext(coroutineContext) { + val schedules = chainSyncSchedulerHandler.fetchActiveSchedules(currentTime()) + schedules.map { syncSchedule -> + async(dispatcher) { + val syncHandler = chainEndpointProxyFinder.findChainEndpointProxy(syncSchedule.chainName) + val lastSync = chainSyncRecordHandler.loadLastSuccessRecord(syncSchedule.chainName) + val tokens = currencyLoader.findImplementationsWithTokenOnChain(syncSchedule.chainName) + .map { impl -> impl.tokenAddress!! } + .toList() + val syncResult = + syncHandler.syncTransfers( + ChainEndpointProxy.DepositFilter( + lastSync?.latestBlock, null, tokens + ) + ) + operator.executeAndAwait { + walletSyncRecordHandler.saveReadyToSyncTransfers(syncResult.chainName, syncResult.records) + chainSyncRecordHandler.saveSyncRecord(syncResult) + if (syncResult.success) { + chainSyncSchedulerHandler.prepareScheduleForNextTry( + syncSchedule, + currentTime().plus(syncSchedule.delay, ChronoUnit.SECONDS) + ) + } + } + } + } + } + } + + protected open fun currentTime() = LocalDateTime.now() +} diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/service/InfoServiceImpl.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/service/InfoServiceImpl.kt new file mode 100644 index 000000000..e600f2369 --- /dev/null +++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/service/InfoServiceImpl.kt @@ -0,0 +1,14 @@ +package co.nilin.opex.bcgateway.core.service + +import co.nilin.opex.bcgateway.core.api.InfoService +import co.nilin.opex.bcgateway.core.model.CurrencyInfo + +class InfoServiceImpl: InfoService { + override suspend fun countReservedAddresses(): Long { + TODO() + } + + override suspend fun getCurrencyInfo(): CurrencyInfo { + TODO() + } +} \ No newline at end of file diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/service/WalletSyncServiceImpl.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/service/WalletSyncServiceImpl.kt new file mode 100644 index 000000000..977202000 --- /dev/null +++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/service/WalletSyncServiceImpl.kt @@ -0,0 +1,51 @@ +package co.nilin.opex.bcgateway.core.service + +import co.nilin.opex.bcgateway.core.api.WalletSyncService +import co.nilin.opex.bcgateway.core.model.WalletSyncRecord +import co.nilin.opex.bcgateway.core.spi.* +import kotlinx.coroutines.ExecutorCoroutineDispatcher +import kotlinx.coroutines.async +import kotlinx.coroutines.withContext +import java.time.LocalDateTime +import java.time.temporal.ChronoUnit +import kotlin.coroutines.coroutineContext + +class WalletSyncServiceImpl( + private val syncSchedulerHandler: WalletSyncSchedulerHandler, + private val walletProxy: WalletProxy, + private val walletSyncRecordHandler: WalletSyncRecordHandler, + private val assignedAddressHandler: AssignedAddressHandler, + private val currencyLoader: CurrencyLoader, + private val dispatcher: ExecutorCoroutineDispatcher +) : WalletSyncService { + + override suspend fun startSyncWithWallet() { + withContext(coroutineContext) { + val schedule = syncSchedulerHandler.fetchActiveSchedule(LocalDateTime.now()) + if (schedule != null) { + val deposits = walletSyncRecordHandler.findReadyToSyncTransfers(schedule.batchSize) + deposits.map { deposit -> + async(dispatcher) { + val uuid = assignedAddressHandler.findUuid(deposit.depositor, deposit.depositorMemo) + if (uuid != null) { + val symbol = currencyLoader.findSymbol(deposit.chain, deposit.tokenAddress) + if (symbol != null) walletProxy.transfer(uuid, symbol, deposit.amount) + walletSyncRecordHandler.saveWalletSyncRecord( + WalletSyncRecord( + LocalDateTime.now(), + true, + null, + deposit + ) + ) + } + } + } + syncSchedulerHandler.prepareScheduleForNextTry( + schedule, LocalDateTime.now() + .plus(schedule.delay, ChronoUnit.SECONDS) + ) + } + } + } +} diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/AssignedAddressHandler.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/AssignedAddressHandler.kt new file mode 100644 index 000000000..1ac2cc2f8 --- /dev/null +++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/AssignedAddressHandler.kt @@ -0,0 +1,10 @@ +package co.nilin.opex.bcgateway.core.spi + +import co.nilin.opex.bcgateway.core.model.AddressType +import co.nilin.opex.bcgateway.core.model.AssignedAddress + +interface AssignedAddressHandler { + suspend fun fetchAssignedAddresses(user: String, addressTypes: List): List + suspend fun persist(assignedAddress: AssignedAddress) + suspend fun findUuid(address: String, memo: String?): String? +} \ No newline at end of file diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/ChainEndpointProxy.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/ChainEndpointProxy.kt new file mode 100644 index 000000000..b73d94eec --- /dev/null +++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/ChainEndpointProxy.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.bcgateway.core.spi + +import co.nilin.opex.bcgateway.core.model.ChainSyncRecord + +interface ChainEndpointProxy { + data class DepositFilter(val startBlock: Long?, val endBlock: Long?, val tokenAddresses: List?) + suspend fun syncTransfers(filter: DepositFilter): ChainSyncRecord +} \ No newline at end of file diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/ChainEndpointProxyFinder.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/ChainEndpointProxyFinder.kt new file mode 100644 index 000000000..7db1ba870 --- /dev/null +++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/ChainEndpointProxyFinder.kt @@ -0,0 +1,5 @@ +package co.nilin.opex.bcgateway.core.spi + +interface ChainEndpointProxyFinder { + suspend fun findChainEndpointProxy(chainName: String):ChainEndpointProxy +} \ No newline at end of file diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/ChainLoader.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/ChainLoader.kt new file mode 100644 index 000000000..6834b58fa --- /dev/null +++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/ChainLoader.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.bcgateway.core.spi + +import co.nilin.opex.bcgateway.core.model.Chain +import co.nilin.opex.bcgateway.core.model.CurrencyInfo + +interface ChainLoader { + suspend fun fetchChainInfo(chain: String): Chain +} \ No newline at end of file diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/ChainSyncRecordHandler.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/ChainSyncRecordHandler.kt new file mode 100644 index 000000000..e8cc7106a --- /dev/null +++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/ChainSyncRecordHandler.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.bcgateway.core.spi + +import co.nilin.opex.bcgateway.core.model.ChainSyncRecord + +interface ChainSyncRecordHandler { + suspend fun loadLastSuccessRecord(chainName: String): ChainSyncRecord? + suspend fun saveSyncRecord(syncRecord: ChainSyncRecord) +} diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/ChainSyncSchedulerHandler.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/ChainSyncSchedulerHandler.kt new file mode 100644 index 000000000..a04e498af --- /dev/null +++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/ChainSyncSchedulerHandler.kt @@ -0,0 +1,9 @@ +package co.nilin.opex.bcgateway.core.spi + +import co.nilin.opex.bcgateway.core.model.ChainSyncSchedule +import java.time.LocalDateTime + +interface ChainSyncSchedulerHandler { + suspend fun fetchActiveSchedules(time: LocalDateTime): List + suspend fun prepareScheduleForNextTry(syncSchedule: ChainSyncSchedule, time: LocalDateTime) +} diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/CurrencyLoader.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/CurrencyLoader.kt new file mode 100644 index 000000000..32aa395f1 --- /dev/null +++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/CurrencyLoader.kt @@ -0,0 +1,10 @@ +package co.nilin.opex.bcgateway.core.spi + +import co.nilin.opex.bcgateway.core.model.CurrencyImplementation +import co.nilin.opex.bcgateway.core.model.CurrencyInfo + +interface CurrencyLoader { + suspend fun fetchCurrencyInfo(symbol: String): CurrencyInfo + suspend fun findSymbol(chain: String, address: String?): String? + suspend fun findImplementationsWithTokenOnChain(chain: String): List +} \ No newline at end of file diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/ReservedAddressHandler.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/ReservedAddressHandler.kt new file mode 100644 index 000000000..67a3c837b --- /dev/null +++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/ReservedAddressHandler.kt @@ -0,0 +1,9 @@ +package co.nilin.opex.bcgateway.core.spi + +import co.nilin.opex.bcgateway.core.model.AddressType +import co.nilin.opex.bcgateway.core.model.ReservedAddress + +interface ReservedAddressHandler { + suspend fun peekReservedAddress(addressType: AddressType): ReservedAddress? + suspend fun remove(reservedAddress: ReservedAddress) +} \ No newline at end of file diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/WalletProxy.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/WalletProxy.kt new file mode 100644 index 000000000..0437da445 --- /dev/null +++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/WalletProxy.kt @@ -0,0 +1,7 @@ +package co.nilin.opex.bcgateway.core.spi + +import java.math.BigDecimal + +interface WalletProxy { + fun transfer(uuid: String, symbol: String, amount: BigDecimal) +} \ No newline at end of file diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/WalletSyncRecordHandler.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/WalletSyncRecordHandler.kt new file mode 100644 index 000000000..90979ab28 --- /dev/null +++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/WalletSyncRecordHandler.kt @@ -0,0 +1,10 @@ +package co.nilin.opex.bcgateway.core.spi + +import co.nilin.opex.bcgateway.core.model.Deposit +import co.nilin.opex.bcgateway.core.model.WalletSyncRecord + +interface WalletSyncRecordHandler { + suspend fun saveReadyToSyncTransfers(chainName: String, deposits: List) + suspend fun saveWalletSyncRecord(syncRecord: WalletSyncRecord) + suspend fun findReadyToSyncTransfers(count: Long?): List +} diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/WalletSyncSchedulerHandler.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/WalletSyncSchedulerHandler.kt new file mode 100644 index 000000000..637b430fe --- /dev/null +++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/WalletSyncSchedulerHandler.kt @@ -0,0 +1,9 @@ +package co.nilin.opex.bcgateway.core.spi + +import co.nilin.opex.bcgateway.core.model.WalletSyncSchedule +import java.time.LocalDateTime + +interface WalletSyncSchedulerHandler { + suspend fun fetchActiveSchedule(time: LocalDateTime): WalletSyncSchedule? + suspend fun prepareScheduleForNextTry(syncSchedule: WalletSyncSchedule, time: LocalDateTime) +} \ No newline at end of file diff --git a/BlockchainGateway/bc-gateway-core/src/main/resources/application.properties b/BlockchainGateway/bc-gateway-core/src/main/resources/application.properties new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/BlockchainGateway/bc-gateway-core/src/main/resources/application.properties @@ -0,0 +1 @@ + diff --git a/BlockchainGateway/bc-gateway-core/src/test/kotlin/co/nilin/opex/bcgateway/core/service/AssignAddressServiceImplUnitTest.kt b/BlockchainGateway/bc-gateway-core/src/test/kotlin/co/nilin/opex/bcgateway/core/service/AssignAddressServiceImplUnitTest.kt new file mode 100644 index 000000000..be773908d --- /dev/null +++ b/BlockchainGateway/bc-gateway-core/src/test/kotlin/co/nilin/opex/bcgateway/core/service/AssignAddressServiceImplUnitTest.kt @@ -0,0 +1,162 @@ +package co.nilin.opex.bcgateway.core.service + +import co.nilin.opex.bcgateway.core.model.AddressType +import co.nilin.opex.bcgateway.core.model.AssignedAddress +import co.nilin.opex.bcgateway.core.model.ReservedAddress +import co.nilin.opex.bcgateway.core.model.Chain +import co.nilin.opex.bcgateway.core.model.Currency +import co.nilin.opex.bcgateway.core.model.CurrencyImplementation +import co.nilin.opex.bcgateway.core.model.CurrencyInfo +import co.nilin.opex.bcgateway.core.spi.AssignedAddressHandler +import co.nilin.opex.bcgateway.core.spi.ReservedAddressHandler +import co.nilin.opex.bcgateway.core.spi.CurrencyLoader +import java.lang.RuntimeException +import java.math.BigDecimal +import java.util.UUID +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.MockitoAnnotations + +class AssignAddressServiceImplUnitTest { + @Mock + lateinit var currencyLoader: CurrencyLoader + + @Mock + lateinit var assignedAddressHandler: AssignedAddressHandler + + @Mock + lateinit var reservedAddressHandler: ReservedAddressHandler + + val assignAddressServiceImpl: AssignAddressServiceImpl + + val currency = Currency("ETH", "Ethereum") + val ethAddressType = AddressType(1, "ETH", "+*", ".*") + val ethMemoAddressType = AddressType(2, "ETH", "+*", "+*") + val ethChain = Chain("ETH_MAINNET", arrayListOf(ethAddressType), emptyList()) + val bscChain = Chain("BSC_MAINNET", arrayListOf(ethAddressType, ethMemoAddressType), emptyList()) + + + init { + MockitoAnnotations.openMocks(this) + assignAddressServiceImpl = AssignAddressServiceImpl( + currencyLoader, assignedAddressHandler, reservedAddressHandler + ) + runBlocking { + val eth = + CurrencyImplementation(currency, ethChain, false, null, null, true, BigDecimal.ONE, BigDecimal.TEN) + val wrappedEth = CurrencyImplementation( + currency, + bscChain, + false, + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "WETH", + true, + BigDecimal.ONE, + BigDecimal.ONE + ) + + Mockito.`when`(currencyLoader.fetchCurrencyInfo(currency.symbol)) + .thenReturn(CurrencyInfo(currency, listOf(eth, wrappedEth))) + } + + + } + + @Test + fun givenReservedAddressAndUserWithNoAssignedAddress_whenAssignAddress_thenReservedAddressAssigned() { + runBlocking { + val user = UUID.randomUUID().toString() + Mockito.`when`(assignedAddressHandler.fetchAssignedAddresses(user, listOf(ethAddressType, ethMemoAddressType))).thenReturn( + emptyList() + ) + Mockito.`when`(reservedAddressHandler.peekReservedAddress(ethAddressType)).thenReturn( + ReservedAddress("0x1", null, ethAddressType) + ) + Mockito.`when`(reservedAddressHandler.peekReservedAddress(ethMemoAddressType)).thenReturn( + ReservedAddress("0x2", "Memo", ethMemoAddressType) + ) + val assignedAddress = assignAddressServiceImpl.assignAddress(user, currency) + Assertions.assertEquals( + listOf( + AssignedAddress( + user, + "0x1", + null, + ethAddressType, + mutableListOf(ethChain, bscChain) + ), + AssignedAddress( + user, + "0x2", + "Memo", + ethMemoAddressType, + mutableListOf(bscChain) + ) + ), assignedAddress + ) + } + } + + @Test + fun givenNoReservedAddressAndUserWithNoAssignedAddress_whenAssignAddress_thenExcpetion() { + runBlocking { + val user = UUID.randomUUID().toString() + Mockito.`when`(assignedAddressHandler.fetchAssignedAddresses(user, listOf(ethAddressType, ethMemoAddressType))).thenReturn( + emptyList() + ) + Mockito.`when`(reservedAddressHandler.peekReservedAddress(ethAddressType)).thenReturn(null) + + Assertions.assertThrows(RuntimeException::class.java) { + runBlocking { + assignAddressServiceImpl.assignAddress(user, currency) + } + } + } + } + + @Test + fun givenReservedAddressAndUserOneAssignedAddress_whenAssignAddress_thenReservedAddressAssigned() { + runBlocking { + val user = UUID.randomUUID().toString() + Mockito.`when`(assignedAddressHandler.fetchAssignedAddresses(user, listOf(ethAddressType, ethMemoAddressType))).thenReturn( + mutableListOf( + AssignedAddress( user, + "0x1", + null, + ethAddressType, + mutableListOf(ethChain) + ) + ) + ) + Mockito.`when`(reservedAddressHandler.peekReservedAddress(ethAddressType)).thenReturn( + ReservedAddress("0x1", null, ethAddressType) + ) + Mockito.`when`(reservedAddressHandler.peekReservedAddress(ethMemoAddressType)).thenReturn( + ReservedAddress("0x2", "Memo", ethMemoAddressType) + ) + val assignedAddress = assignAddressServiceImpl.assignAddress(user, currency) + Assertions.assertEquals( + listOf( + AssignedAddress( + user, + "0x1", + null, + ethAddressType, + mutableListOf(ethChain, bscChain) + ), + AssignedAddress( + user, + "0x2", + "Memo", + ethMemoAddressType, + mutableListOf(bscChain) + ) + ), assignedAddress + ) + } + } + +} \ No newline at end of file diff --git a/BlockchainGateway/bc-gateway-core/src/test/kotlin/co/nilin/opex/bcgateway/core/service/ChainSyncServiceImplTest.kt b/BlockchainGateway/bc-gateway-core/src/test/kotlin/co/nilin/opex/bcgateway/core/service/ChainSyncServiceImplTest.kt new file mode 100644 index 000000000..f9997357e --- /dev/null +++ b/BlockchainGateway/bc-gateway-core/src/test/kotlin/co/nilin/opex/bcgateway/core/service/ChainSyncServiceImplTest.kt @@ -0,0 +1,141 @@ +package co.nilin.opex.bcgateway.core.service + +import co.nilin.opex.bcgateway.core.model.ChainSyncRecord +import co.nilin.opex.bcgateway.core.model.ChainSyncSchedule +import co.nilin.opex.bcgateway.core.model.Endpoint +import co.nilin.opex.bcgateway.core.spi.ChainEndpointProxy +import co.nilin.opex.bcgateway.core.spi.ChainEndpointProxyFinder +import co.nilin.opex.bcgateway.core.spi.CurrencyLoader +import co.nilin.opex.bcgateway.core.spi.ChainSyncRecordHandler +import co.nilin.opex.bcgateway.core.spi.ChainSyncSchedulerHandler +import co.nilin.opex.bcgateway.core.spi.WalletSyncRecordHandler +import co.nilin.opex.bcgateway.test.OPERATOR +import java.time.LocalDateTime +import java.time.temporal.ChronoUnit +import java.util.concurrent.Executors +import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.Test +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.mock +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyZeroInteractions + +internal class ChainSyncServiceImplTest { + + val ethChain = "ETH_MAINNET" + val bscChain = "BSC_MAINNET" + val time = LocalDateTime.now() + val syncService: ChainSyncServiceImpl + + @Mock + lateinit var chainSyncSchedulerHandler: ChainSyncSchedulerHandler + + @Mock + lateinit var chainEndpointProxyFinder: ChainEndpointProxyFinder + + @Mock + lateinit var chainSyncRecordHandler: ChainSyncRecordHandler + + @Mock + lateinit var walletSyncRecordHandler: WalletSyncRecordHandler + + @Mock + lateinit var currencyLoader: CurrencyLoader + + val endpointProxy: ChainEndpointProxy = mock() + + init { + MockitoAnnotations.openMocks(this) + runBlocking { + Mockito.`when`(chainEndpointProxyFinder.findChainEndpointProxy(ethChain)) + .thenReturn(endpointProxy) + Mockito.`when`(currencyLoader.findImplementationsWithTokenOnChain(ethChain)).thenReturn(emptyList()) + } + + syncService = object : ChainSyncServiceImpl( + chainSyncSchedulerHandler, + chainEndpointProxyFinder, + chainSyncRecordHandler, + walletSyncRecordHandler, + currencyLoader, + OPERATOR, + Executors.newFixedThreadPool(2).asCoroutineDispatcher() + ) { + override fun currentTime() = time + } + } + + @Test + fun givenNoActiveSchedules_whenStartSync_thenNoOp() { + runBlocking { + //given + Mockito.`when`(chainSyncSchedulerHandler.fetchActiveSchedules(any())).thenReturn(emptyList()) + + //when + syncService.startSyncWithChain() + + //then + verifyZeroInteractions( + chainEndpointProxyFinder, + chainSyncRecordHandler, + walletSyncRecordHandler, + currencyLoader + ) + } + } + + @Test + fun givenAnActiveScheduleAndChainEndpointWorking_whenStartSync_thenSyncedSuccessfully() { + runBlocking { + //given + val delay = 100L + val syncSchedule = ChainSyncSchedule(ethChain, time, delay) + Mockito.`when`(chainSyncSchedulerHandler.fetchActiveSchedules(any())) + .thenReturn(listOf(syncSchedule)) + Mockito.`when`(endpointProxy.syncTransfers(any())).thenReturn( + ChainSyncRecord( + ethChain, LocalDateTime.now(), Endpoint(""), 100, true, null, emptyList() + ) + ) + + //when + syncService.startSyncWithChain() + + //then + verify(chainSyncRecordHandler).saveSyncRecord(any()) + verify(walletSyncRecordHandler).saveReadyToSyncTransfers(any(), any()) + verify(chainSyncSchedulerHandler).prepareScheduleForNextTry(syncSchedule, time.plus(delay, ChronoUnit.SECONDS)) + } + } + + @Test + fun givenAnActiveScheduleAndChainEndpointFailed_whenStartSync_thenSyncedFailed() { + runBlocking { + //given + val delay = 100L + val syncSchedule = ChainSyncSchedule(ethChain, time, delay) + Mockito.`when`(chainSyncSchedulerHandler.fetchActiveSchedules(any())) + .thenReturn(listOf(syncSchedule)) + Mockito.`when`(endpointProxy.syncTransfers(any())).thenReturn( + ChainSyncRecord( + ethChain, LocalDateTime.now(), Endpoint(""), 100, false, "error", emptyList() + ) + ) + + //when + syncService.startSyncWithChain() + + //then + verify(chainSyncRecordHandler).saveSyncRecord(any()) + verify(walletSyncRecordHandler).saveReadyToSyncTransfers(any(), any()) + verify(chainSyncSchedulerHandler, times(0)).prepareScheduleForNextTry(any(), any()) + } + } + + +} diff --git a/BlockchainGateway/bc-gateway-core/src/test/kotlin/co/nilin/opex/bcgateway/test/MockTxExtension.kt b/BlockchainGateway/bc-gateway-core/src/test/kotlin/co/nilin/opex/bcgateway/test/MockTxExtension.kt new file mode 100644 index 000000000..0a67d8d55 --- /dev/null +++ b/BlockchainGateway/bc-gateway-core/src/test/kotlin/co/nilin/opex/bcgateway/test/MockTxExtension.kt @@ -0,0 +1,27 @@ +package co.nilin.opex.bcgateway.test + +import org.springframework.transaction.ReactiveTransaction +import org.springframework.transaction.reactive.TransactionCallback +import org.springframework.transaction.reactive.TransactionalOperator +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono + + +val OPERATOR = object : TransactionalOperator { + override fun transactional(mono: Mono): Mono { + return Mono.empty() + } + + override fun execute(action: TransactionCallback): Flux { + return Flux.from(action.doInTransaction(object : ReactiveTransaction { + override fun isNewTransaction() = true + + override fun setRollbackOnly() { + } + + override fun isRollbackOnly() = false + + override fun isCompleted() = true + })) + } +} diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/.gitignore b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/.gitignore new file mode 100644 index 000000000..f4e066ca5 --- /dev/null +++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/.gitignore @@ -0,0 +1,36 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ +!/.mvn/ + +.DS_Store diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/mvnw b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/mvnw new file mode 100644 index 000000000..a16b5431b --- /dev/null +++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/mvnw.cmd b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/mvnw.cmd new file mode 100644 index 000000000..c8d43372c --- /dev/null +++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/pom.xml b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/pom.xml new file mode 100644 index 000000000..321effaa2 --- /dev/null +++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/pom.xml @@ -0,0 +1,105 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.4 + + + co.nilin.opex.external + bc-gateway-persister-postgres + 1.0-SNAPSHOT + bc-gateway-persister-postgres + Persist items of Opex blockchain gateway on Postgres + + + 1.8 + 1.4.31 + ${version} + ${version} + + + + + org.springframework.boot + spring-boot-starter-data-r2dbc + + + io.r2dbc + r2dbc-postgresql + runtime + + + org.postgresql + postgresql + runtime + + + + io.projectreactor.kotlin + reactor-kotlin-extensions + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + + + org.jetbrains.kotlinx + kotlinx-coroutines-core + + + co.nilin.opex.external + bc-gateway-core + ${bc-gateway.version} + + + io.projectreactor + reactor-test + test + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + -Xjsr305=strict + + + spring + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + + diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/config/PostgresConfig.kt b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/config/PostgresConfig.kt new file mode 100644 index 000000000..df4ca430b --- /dev/null +++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/config/PostgresConfig.kt @@ -0,0 +1,110 @@ +package co.nilin.opex.port.bcgateway.postgres.config + +import org.springframework.context.annotation.Configuration +import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories +import org.springframework.r2dbc.core.DatabaseClient + +@Configuration +@EnableR2dbcRepositories(basePackages = ["co.nilin.opex"]) +class PostgresConfig(db: DatabaseClient) { + + init { + val initDb = db.sql { + """ + CREATE TABLE IF NOT EXISTS address_types ( + id SERIAL PRIMARY KEY, + address_type VARCHAR(72) NOT NULL, + address_regex VARCHAR(72) NOT NULL, + memo_regex VARCHAR(72) NOT NULL + ); + CREATE TABLE IF NOT EXISTS assigned_addresses ( + id SERIAL PRIMARY KEY, + uuid VARCHAR(72) NOT NULL, + address VARCHAR(72) NOT NULL, + memo VARCHAR(72), + addr_type_id INTEGER NOT NULL, + UNIQUE (address, memo) + ); + CREATE TABLE IF NOT EXISTS assigned_address_chains ( + id SERIAL PRIMARY KEY, + assigned_address_id INTEGER NOT NULL, + chain VARCHAR(72) NOT NULL + ); + CREATE TABLE IF NOT EXISTS reserved_addresses ( + id SERIAL PRIMARY KEY, + address VARCHAR(72) NOT NULL, + memo VARCHAR(72), + address_type VARCHAR(72) NOT NULL, + UNIQUE (address, memo) + ); + CREATE TABLE IF NOT EXISTS chains ( + name VARCHAR(72) PRIMARY KEY + ); + CREATE TABLE IF NOT EXISTS chain_address_types ( + id SERIAL PRIMARY KEY, + chain_name VARCHAR(72) NOT NULL REFERENCES chains (name), + addr_type_id INTEGER NOT NULL REFERENCES address_types (id) + ); + CREATE TABLE IF NOT EXISTS chain_endpoints ( + id SERIAL PRIMARY KEY, + chain_name VARCHAR(72) NOT NULL, + endpoint_url VARCHAR(72) NOT NULL, + endpoint_user VARCHAR(72), + endpoint_password VARCHAR(72) + ); + CREATE TABLE IF NOT EXISTS chain_sync_schedules ( + chain VARCHAR(72) PRIMARY KEY, + retry_time TIMESTAMP NOT NULL, + delay INTEGER NOT NULL + ); + CREATE TABLE IF NOT EXISTS chain_sync_records ( + chain VARCHAR(72) PRIMARY KEY, + time TIMESTAMP NOT NULL, + endpoint_url VARCHAR(72) NOT NULL, + latest_block INTEGER, + success BOOLEAN NOT NULL, + error VARCHAR(100) + ); + CREATE TABLE IF NOT EXISTS wallet_sync_schedules ( + id INTEGER PRIMARY KEY DEFAULT(1) CHECK(id = 1), + retry_time TIMESTAMP NOT NULL, + delay INTEGER NOT NULL, + batch_size INTEGER + ); + CREATE TABLE IF NOT EXISTS wallet_sync_records ( + id SERIAL PRIMARY KEY, + time TIMESTAMP NOT NULL, + success BOOLEAN NOT NULL, + error VARCHAR(100) + ); + CREATE TABLE IF NOT EXISTS deposits ( + id SERIAL PRIMARY KEY, + wallet_sync_record INTEGER NOT NULL, + chain VARCHAR(72) NOT NULL, + token BOOLEAN NOT NULL, + token_address VARCHAR(72), + amount NUMERIC NOT NULL, + depositor VARCHAR(72) NOT NULL, + depositor_memo VARCHAR(72) + ); + CREATE TABLE IF NOT EXISTS currency ( + symbol VARCHAR(72) PRIMARY KEY, + name VARCHAR(72) NOT NULL + ); + CREATE TABLE IF NOT EXISTS currency_implementations ( + symbol VARCHAR(72) PRIMARY KEY, + chain VARCHAR(72) NOT NULL, + token BOOLEAN NOT NULL, + token_address VARCHAR(72), + token_name VARCHAR(72), + withdraw_enabled BOOLEAN NOT NULL, + withdraw_fee NUMERIC NOT NULL, + withdraw_min NUMERIC NOT NULL + ); + """ + } + initDb // initialize the database + .then() + .subscribe() // execute + } +} diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/AddressTypeRepository.kt b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/AddressTypeRepository.kt new file mode 100644 index 000000000..657a2caf4 --- /dev/null +++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/AddressTypeRepository.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.port.bcgateway.postgres.dao + +import co.nilin.opex.port.bcgateway.postgres.model.AddressTypeModel +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository + +@Repository +interface AddressTypeRepository : ReactiveCrudRepository \ No newline at end of file diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/AssignedAddressChainRepository.kt b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/AssignedAddressChainRepository.kt new file mode 100644 index 000000000..47b7d7524 --- /dev/null +++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/AssignedAddressChainRepository.kt @@ -0,0 +1,15 @@ +package co.nilin.opex.port.bcgateway.postgres.dao + +import co.nilin.opex.port.bcgateway.postgres.model.AssignedAddressChainModel +import co.nilin.opex.port.bcgateway.postgres.model.AssignedAddressModel +import kotlinx.coroutines.flow.Flow +import org.springframework.data.r2dbc.repository.Query +import org.springframework.data.repository.query.Param +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository + +@Repository +interface AssignedAddressChainRepository : ReactiveCrudRepository { + @Query("select * from assigned_address_chains where assigned_address_id = :assignedAddress") + fun findByAssignedAddress(@Param("assignedAddress") type: Long): Flow +} \ No newline at end of file diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/AssignedAddressRepository.kt b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/AssignedAddressRepository.kt new file mode 100644 index 000000000..e6e7e147c --- /dev/null +++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/AssignedAddressRepository.kt @@ -0,0 +1,22 @@ +package co.nilin.opex.port.bcgateway.postgres.dao + +import co.nilin.opex.port.bcgateway.postgres.model.AssignedAddressModel +import kotlinx.coroutines.flow.Flow +import org.springframework.data.r2dbc.repository.Query +import org.springframework.data.repository.query.Param +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository +import reactor.core.publisher.Mono + +@Repository +interface AssignedAddressRepository : ReactiveCrudRepository { + @Query("select * from assigned_addresses where uuid = :uuid and addr_type_id in (:addressTypes)") + fun findByUuidAndAddressType( + @Param("uuid") uuid: String, @Param("addressTypes") types: List + ): Flow + + @Query("select * from assigned_addresses where address = :address and (:memo is null or memo = :memo)") + fun findByAddressAndMemo( + @Param("address") address: String, @Param("memo") memo: String? + ): Mono +} \ No newline at end of file diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/ChainAddressTypeRepository.kt b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/ChainAddressTypeRepository.kt new file mode 100644 index 000000000..2248a9474 --- /dev/null +++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/ChainAddressTypeRepository.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.port.bcgateway.postgres.dao + +import co.nilin.opex.port.bcgateway.postgres.model.ChainAddressTypeModel +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository + +@Repository +interface ChainAddressTypeRepository : ReactiveCrudRepository \ No newline at end of file diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/ChainEndpointRepository.kt b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/ChainEndpointRepository.kt new file mode 100644 index 000000000..88e5fdc26 --- /dev/null +++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/ChainEndpointRepository.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.port.bcgateway.postgres.dao + +import co.nilin.opex.port.bcgateway.postgres.model.ChainEndpointModel +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository + +@Repository +interface ChainEndpointRepository : ReactiveCrudRepository \ No newline at end of file diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/ChainRepository.kt b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/ChainRepository.kt new file mode 100644 index 000000000..9395b112a --- /dev/null +++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/ChainRepository.kt @@ -0,0 +1,28 @@ +package co.nilin.opex.port.bcgateway.postgres.dao + +import co.nilin.opex.port.bcgateway.postgres.model.AddressTypeModel +import co.nilin.opex.port.bcgateway.postgres.model.ChainEndpointModel +import co.nilin.opex.port.bcgateway.postgres.model.ChainModel +import kotlinx.coroutines.flow.Flow +import org.springframework.data.r2dbc.repository.Query +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository + +@Repository +interface ChainRepository : ReactiveCrudRepository { + fun findByName(name: String): Flow + + @Query( + """ + select address_types.id, chain_address_types.chain_name, address_types.address_type, address_types.address_regex, address_types.memo_regex + from chain_address_types + join address_types + on address_types.id = chain_address_types.addr_type_id + where chain_name = :name + """ + ) + fun findAddressTypesByName(name: String): Flow + + @Query("select * from chain_endpoints where chain_name = :name") + fun findEndpointsByName(name: String): Flow +} \ No newline at end of file diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/ChainSyncRecordRepository.kt b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/ChainSyncRecordRepository.kt new file mode 100644 index 000000000..20044463e --- /dev/null +++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/ChainSyncRecordRepository.kt @@ -0,0 +1,11 @@ +package co.nilin.opex.port.bcgateway.postgres.dao + +import co.nilin.opex.port.bcgateway.postgres.model.ChainSyncRecordModel +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository +import reactor.core.publisher.Mono + +@Repository +interface ChainSyncRecordRepository : ReactiveCrudRepository { + fun findByChain(chain: String): Mono +} diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/ChainSyncScheduleRepository.kt b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/ChainSyncScheduleRepository.kt new file mode 100644 index 000000000..c4395e8d5 --- /dev/null +++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/ChainSyncScheduleRepository.kt @@ -0,0 +1,16 @@ +package co.nilin.opex.port.bcgateway.postgres.dao + +import co.nilin.opex.port.bcgateway.postgres.model.ChainSyncScheduleModel +import co.nilin.opex.port.bcgateway.postgres.model.WalletSyncScheduleModel +import kotlinx.coroutines.flow.Flow +import org.springframework.data.r2dbc.repository.Query +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository +import reactor.core.publisher.Mono +import java.time.LocalDateTime + +@Repository +interface ChainSyncScheduleRepository : ReactiveCrudRepository { + @Query("select * from chain_sync_schedules where retry_time <= :time") + fun findActiveSchedule(time: LocalDateTime): Flow +} diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/CurrencyImplementationRepository.kt b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/CurrencyImplementationRepository.kt new file mode 100644 index 000000000..6d00f6a36 --- /dev/null +++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/CurrencyImplementationRepository.kt @@ -0,0 +1,24 @@ +package co.nilin.opex.port.bcgateway.postgres.dao + +import co.nilin.opex.port.bcgateway.postgres.model.CurrencyImplementationModel +import kotlinx.coroutines.flow.Flow +import org.springframework.data.r2dbc.repository.Query +import org.springframework.data.repository.query.Param +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository +import reactor.core.publisher.Mono + +@Repository +interface CurrencyImplementationRepository : ReactiveCrudRepository { + fun findBySymbol( + symbol: String + ): Flow + + fun findByChain( + chain: String + ): Flow + + fun findByChainAndTokenAddress( + chain: String, tokenAddress: String? + ): Mono +} diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/CurrencyRepository.kt b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/CurrencyRepository.kt new file mode 100644 index 000000000..f44e25b6b --- /dev/null +++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/CurrencyRepository.kt @@ -0,0 +1,14 @@ +package co.nilin.opex.port.bcgateway.postgres.dao + +import co.nilin.opex.port.bcgateway.postgres.model.CurrencyModel +import kotlinx.coroutines.flow.Flow +import org.springframework.data.r2dbc.repository.Query +import org.springframework.data.repository.query.Param +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository +import reactor.core.publisher.Mono + +@Repository +interface CurrencyRepository : ReactiveCrudRepository { + fun findBySymbol(symbol: String): Mono +} diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/DepositRepository.kt b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/DepositRepository.kt new file mode 100644 index 000000000..848d4d364 --- /dev/null +++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/DepositRepository.kt @@ -0,0 +1,24 @@ +package co.nilin.opex.port.bcgateway.postgres.dao + +import co.nilin.opex.port.bcgateway.postgres.model.DepositModel +import kotlinx.coroutines.flow.Flow +import org.springframework.data.r2dbc.repository.Modifying +import org.springframework.data.r2dbc.repository.Query +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository +import reactor.core.publisher.Mono + +@Repository +interface DepositRepository : ReactiveCrudRepository { + fun findByChain(chain: String): Flow + + @Query("select * from deposits where chain = :chain and wallet_sync_record is null") + fun findByChainWhereNotSynced(chain: String): Flow + + @Query("select * from deposits where wallet_record_id is null limit :count") + fun findLimited(count: Long?): Flow + + @Modifying + @Query("update deposits set wallet_sync_record = :walletSyncRecord where id = :id") + fun updateWalletSyncRecord(id: Long, walletSyncRecord: Long): Mono +} diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/ReservedAddressRepository.kt b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/ReservedAddressRepository.kt new file mode 100644 index 000000000..a48a80366 --- /dev/null +++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/ReservedAddressRepository.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.port.bcgateway.postgres.dao + +import co.nilin.opex.port.bcgateway.postgres.model.ReservedAddressModel +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository + +@Repository +interface ReservedAddressRepository : ReactiveCrudRepository \ No newline at end of file diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/WalletSyncRecordRepository.kt b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/WalletSyncRecordRepository.kt new file mode 100644 index 000000000..cab986087 --- /dev/null +++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/WalletSyncRecordRepository.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.port.bcgateway.postgres.dao + +import co.nilin.opex.port.bcgateway.postgres.model.WalletSyncRecordModel +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository + +@Repository +interface WalletSyncRecordRepository : ReactiveCrudRepository diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/WalletSyncScheduleRepository.kt b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/WalletSyncScheduleRepository.kt new file mode 100644 index 000000000..176ff6a23 --- /dev/null +++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/WalletSyncScheduleRepository.kt @@ -0,0 +1,14 @@ +package co.nilin.opex.port.bcgateway.postgres.dao + +import co.nilin.opex.port.bcgateway.postgres.model.WalletSyncScheduleModel +import org.springframework.data.r2dbc.repository.Query +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository +import reactor.core.publisher.Mono +import java.time.LocalDateTime + +@Repository +interface WalletSyncScheduleRepository : ReactiveCrudRepository { + @Query("select * from wallet_sync_schedules where retry_time <= :time") + fun findActiveSchedule(time: LocalDateTime): Mono +} diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/impl/AssignedAddressHandlerImpl.kt b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/impl/AssignedAddressHandlerImpl.kt new file mode 100644 index 000000000..1e7695999 --- /dev/null +++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/impl/AssignedAddressHandlerImpl.kt @@ -0,0 +1,61 @@ +package co.nilin.opex.port.bcgateway.postgres.impl + +import co.nilin.opex.bcgateway.core.model.AddressType +import co.nilin.opex.bcgateway.core.model.AssignedAddress +import co.nilin.opex.bcgateway.core.spi.AssignedAddressHandler +import co.nilin.opex.bcgateway.core.spi.ChainLoader +import co.nilin.opex.port.bcgateway.postgres.dao.AddressTypeRepository +import co.nilin.opex.port.bcgateway.postgres.dao.AssignedAddressChainRepository +import co.nilin.opex.port.bcgateway.postgres.dao.AssignedAddressRepository +import co.nilin.opex.port.bcgateway.postgres.model.AssignedAddressModel +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.reactive.awaitFirst +import kotlinx.coroutines.reactive.awaitFirstOrNull +import org.springframework.stereotype.Service + +@Service +class AssignedAddressHandlerImpl( + val assignedAddressRepository: AssignedAddressRepository, + val addressTypeRepository: AddressTypeRepository, + val assignedAddressChainRepository: AssignedAddressChainRepository, + val chainLoader: ChainLoader +) : AssignedAddressHandler { + override suspend fun fetchAssignedAddresses(user: String, addressTypes: List): List { + return assignedAddressRepository.findByUuidAndAddressType( + user, addressTypes.map(AddressType::id) + ) + .map { model -> + AssignedAddress( + model.uuid, model.address, model.memo, + addressTypeRepository + .findById(model.addressTypeId) + .map { aam -> + AddressType(aam.id!!, aam.type, aam.addressRegex, aam.memoRegex) + } + .awaitFirst(), + assignedAddressChainRepository.findByAssignedAddress(model.id!!) + .map { cm -> + chainLoader.fetchChainInfo(cm.chain) + } + .toList().toMutableList() + ) + }.toList() + } + + override suspend fun persist(assignedAddress: AssignedAddress) { + assignedAddressRepository.save( + AssignedAddressModel( + null, + assignedAddress.uuid, + assignedAddress.address, + assignedAddress.memo, + assignedAddress.type.id + ) + ).awaitFirst() + } + + override suspend fun findUuid(address: String, memo: String?): String? { + return assignedAddressRepository.findByAddressAndMemo(address, memo).awaitFirstOrNull()?.uuid + } +} \ No newline at end of file diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/impl/ChainHandler.kt b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/impl/ChainHandler.kt new file mode 100644 index 000000000..52a88cd16 --- /dev/null +++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/impl/ChainHandler.kt @@ -0,0 +1,13 @@ +package co.nilin.opex.port.bcgateway.postgres.impl + +import co.nilin.opex.bcgateway.core.model.Chain +import co.nilin.opex.bcgateway.core.spi.ChainLoader +import co.nilin.opex.port.bcgateway.postgres.dao.ChainRepository +import org.springframework.stereotype.Component + +@Component +class ChainHandler(val chainRepository: ChainRepository): ChainLoader { + override suspend fun fetchChainInfo(chain: String): Chain { + TODO("Not implemented!") + } +} \ No newline at end of file diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/impl/ChainSyncRecordHandlerImpl.kt b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/impl/ChainSyncRecordHandlerImpl.kt new file mode 100644 index 000000000..2a2cf7799 --- /dev/null +++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/impl/ChainSyncRecordHandlerImpl.kt @@ -0,0 +1,55 @@ +package co.nilin.opex.port.bcgateway.postgres.impl + +import co.nilin.opex.bcgateway.core.model.ChainSyncRecord +import co.nilin.opex.bcgateway.core.model.Deposit +import co.nilin.opex.bcgateway.core.model.Endpoint +import co.nilin.opex.bcgateway.core.spi.ChainSyncRecordHandler +import co.nilin.opex.port.bcgateway.postgres.dao.ChainSyncRecordRepository +import co.nilin.opex.port.bcgateway.postgres.dao.DepositRepository +import co.nilin.opex.port.bcgateway.postgres.model.ChainSyncRecordModel +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.reactive.awaitFirst +import kotlinx.coroutines.reactive.awaitSingleOrNull +import org.springframework.stereotype.Component +import org.springframework.transaction.annotation.Transactional + +@Component +class ChainSyncRecordHandlerImpl( + private val chainSyncRecordRepository: ChainSyncRecordRepository, + private val depositRepository: DepositRepository +) : ChainSyncRecordHandler { + override suspend fun loadLastSuccessRecord(chainName: String): ChainSyncRecord? { + val chainSyncRecordDao = chainSyncRecordRepository.findByChain(chainName).awaitSingleOrNull() + return if (chainSyncRecordDao !== null) { + val deposits = depositRepository.findByChainWhereNotSynced(chainName).map { + Deposit(it.id, it.depositor, it.depositorMemo, it.amount, it.chain, it.token, it.tokenAddress) + } + ChainSyncRecord( + chainSyncRecordDao.chain, + chainSyncRecordDao.time, + Endpoint(chainSyncRecordDao.endpointUrl), + chainSyncRecordDao.latestBlock, + chainSyncRecordDao.success, + chainSyncRecordDao.error, + deposits.toList() + ) + } else { + null + } + } + + @Transactional + override suspend fun saveSyncRecord(syncRecord: ChainSyncRecord) { + val chainSyncRecordDao = + ChainSyncRecordModel( + syncRecord.chainName, + syncRecord.time, + syncRecord.endpoint.url, + syncRecord.latestBlock, + syncRecord.success, + syncRecord.error + ) + chainSyncRecordRepository.save(chainSyncRecordDao).awaitFirst() + } +} diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/impl/ChainSyncSchedulerHandlerImpl.kt b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/impl/ChainSyncSchedulerHandlerImpl.kt new file mode 100644 index 000000000..f33961d16 --- /dev/null +++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/impl/ChainSyncSchedulerHandlerImpl.kt @@ -0,0 +1,29 @@ +package co.nilin.opex.port.bcgateway.postgres.impl + +import co.nilin.opex.bcgateway.core.model.ChainSyncSchedule +import co.nilin.opex.bcgateway.core.model.WalletSyncSchedule +import co.nilin.opex.bcgateway.core.spi.ChainSyncSchedulerHandler +import co.nilin.opex.port.bcgateway.postgres.dao.ChainSyncScheduleRepository +import co.nilin.opex.port.bcgateway.postgres.model.ChainSyncScheduleModel +import co.nilin.opex.port.bcgateway.postgres.model.WalletSyncScheduleModel +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.reactive.awaitFirst +import kotlinx.coroutines.reactive.awaitSingleOrNull +import org.springframework.stereotype.Component +import java.time.LocalDateTime + +@Component +class ChainSyncSchedulerHandlerImpl(private val chainSyncScheduleRepository: ChainSyncScheduleRepository) : + ChainSyncSchedulerHandler { + override suspend fun fetchActiveSchedules(time: LocalDateTime): List { + return chainSyncScheduleRepository.findActiveSchedule(time).map { + ChainSyncSchedule(it.chain, it.retryTime, it.delay) + }.toList() + } + + override suspend fun prepareScheduleForNextTry(syncSchedule: ChainSyncSchedule, time: LocalDateTime) { + val dao = ChainSyncScheduleModel(syncSchedule.chainName, time, syncSchedule.delay) + chainSyncScheduleRepository.save(dao).awaitFirst() + } +} diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/impl/CurrencyLoaderImpl.kt b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/impl/CurrencyLoaderImpl.kt new file mode 100644 index 000000000..118329469 --- /dev/null +++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/impl/CurrencyLoaderImpl.kt @@ -0,0 +1,62 @@ +package co.nilin.opex.port.bcgateway.postgres.impl + +import co.nilin.opex.bcgateway.core.model.* +import co.nilin.opex.bcgateway.core.spi.CurrencyLoader +import co.nilin.opex.port.bcgateway.postgres.dao.ChainRepository +import co.nilin.opex.port.bcgateway.postgres.dao.CurrencyImplementationRepository +import co.nilin.opex.port.bcgateway.postgres.dao.CurrencyRepository +import co.nilin.opex.port.bcgateway.postgres.model.CurrencyImplementationModel +import co.nilin.opex.port.bcgateway.postgres.model.CurrencyModel +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.reactive.awaitFirstOrNull +import kotlinx.coroutines.reactive.awaitSingle +import kotlinx.coroutines.reactive.awaitSingleOrNull +import org.springframework.stereotype.Component + +@Component +class CurrencyLoaderImpl( + private val chainRepository: ChainRepository, + private val currencyRepository: CurrencyRepository, + private val currencyImplementationRepository: CurrencyImplementationRepository +) : CurrencyLoader { + override suspend fun fetchCurrencyInfo(symbol: String): CurrencyInfo { + val currencyDao = currencyRepository.findBySymbol(symbol).awaitSingleOrNull() + if (currencyDao === null) return CurrencyInfo(Currency("", symbol), emptyList()) + val currencyImplDao = currencyImplementationRepository.findBySymbol(symbol) + val currency = Currency(currencyDao.symbol, currencyDao.name) + val implementations = currencyImplDao.map { projectCurrencyImplementation(it, currencyDao) } + return CurrencyInfo(currency, implementations.toList()) + } + + override suspend fun findSymbol(chain: String, address: String?): String? { + return currencyImplementationRepository.findByChainAndTokenAddress(chain, address) + .awaitFirstOrNull()?.symbol + } + + override suspend fun findImplementationsWithTokenOnChain(chain: String): List { + return currencyImplementationRepository.findByChain(chain).map { projectCurrencyImplementation(it) }.toList() + } + + private suspend fun projectCurrencyImplementation( + implDao: CurrencyImplementationModel, + currencyDao: CurrencyModel? = null + ): CurrencyImplementation { + val addressTypesDao = chainRepository.findAddressTypesByName(implDao.chain) + val addressTypes = addressTypesDao.map { AddressType(it.id!!, it.type, it.addressRegex, it.memoRegex) } + val endpointsDao = chainRepository.findEndpointsByName(implDao.chain) + val endpoints = endpointsDao.map { Endpoint(it.url) } + val currencyDaoVal = currencyDao ?: currencyRepository.findBySymbol(implDao.symbol).awaitSingle() + val currency = Currency(currencyDaoVal.symbol, currencyDaoVal.name) + return CurrencyImplementation( + currency, + Chain(implDao.chain, addressTypes.toList(), endpoints.toList()), + implDao.token, + implDao.tokenAddress, + implDao.tokenName, + implDao.withdrawEnabled, + implDao.withdrawFee, + implDao.withdrawMin + ) + } +} diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/impl/ReservedAddressHandlerImpl.kt b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/impl/ReservedAddressHandlerImpl.kt new file mode 100644 index 000000000..ed2e0e547 --- /dev/null +++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/impl/ReservedAddressHandlerImpl.kt @@ -0,0 +1,17 @@ +package co.nilin.opex.port.bcgateway.postgres.impl + +import co.nilin.opex.bcgateway.core.model.AddressType +import co.nilin.opex.bcgateway.core.model.ReservedAddress +import co.nilin.opex.bcgateway.core.spi.ReservedAddressHandler +import org.springframework.stereotype.Component + +@Component +class ReservedAddressHandlerImpl: ReservedAddressHandler { + override suspend fun peekReservedAddress(addressType: AddressType): ReservedAddress? { + TODO("Not yet implemented") + } + + override suspend fun remove(reservedAddress: ReservedAddress) { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/impl/WalletSyncRecordHandlerImpl.kt b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/impl/WalletSyncRecordHandlerImpl.kt new file mode 100644 index 000000000..c5bd769b8 --- /dev/null +++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/impl/WalletSyncRecordHandlerImpl.kt @@ -0,0 +1,56 @@ +package co.nilin.opex.port.bcgateway.postgres.impl + +import co.nilin.opex.bcgateway.core.model.Deposit +import co.nilin.opex.bcgateway.core.model.WalletSyncRecord +import co.nilin.opex.bcgateway.core.spi.WalletSyncRecordHandler +import co.nilin.opex.port.bcgateway.postgres.dao.DepositRepository +import co.nilin.opex.port.bcgateway.postgres.dao.WalletSyncRecordRepository +import co.nilin.opex.port.bcgateway.postgres.model.DepositModel +import co.nilin.opex.port.bcgateway.postgres.model.WalletSyncRecordModel +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.reactive.awaitFirst +import org.springframework.stereotype.Component +import org.springframework.transaction.annotation.Transactional + +@Component +class WalletSyncRecordHandlerImpl( + private val walletSyncRecordRepository: WalletSyncRecordRepository, + private val depositRepository: DepositRepository +) : WalletSyncRecordHandler { + @Transactional + override suspend fun saveReadyToSyncTransfers(chainName: String, deposits: List) { + val depositsDao = deposits.map { + DepositModel( + null, + null, + it.depositor, + it.depositorMemo, + it.amount, + it.chain, + it.token, + it.tokenAddress + ) + } + depositRepository.saveAll(depositsDao).awaitFirst() + } + + @Transactional + override suspend fun saveWalletSyncRecord(syncRecord: WalletSyncRecord) { + val dao = walletSyncRecordRepository.save( + WalletSyncRecordModel( + null, + syncRecord.time, + syncRecord.success, + syncRecord.error + ) + ).awaitFirst() + depositRepository.updateWalletSyncRecord(syncRecord.deposit.id!!, dao.id!!).awaitFirst() + } + + override suspend fun findReadyToSyncTransfers(count: Long?): List { + return depositRepository.findLimited(count).map { + Deposit(it.id, it.depositor, it.depositorMemo, it.amount, it.chain, it.token, it.tokenAddress) + }.toList() + } +} diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/impl/WalletSyncSchedulerHandlerImpl.kt b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/impl/WalletSyncSchedulerHandlerImpl.kt new file mode 100644 index 000000000..ec3fa6206 --- /dev/null +++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/impl/WalletSyncSchedulerHandlerImpl.kt @@ -0,0 +1,24 @@ +package co.nilin.opex.port.bcgateway.postgres.impl + +import co.nilin.opex.bcgateway.core.model.WalletSyncSchedule +import co.nilin.opex.bcgateway.core.spi.WalletSyncSchedulerHandler +import co.nilin.opex.port.bcgateway.postgres.dao.WalletSyncScheduleRepository +import co.nilin.opex.port.bcgateway.postgres.model.WalletSyncScheduleModel +import kotlinx.coroutines.reactive.awaitFirst +import kotlinx.coroutines.reactive.awaitSingleOrNull +import org.springframework.stereotype.Component +import java.time.LocalDateTime + +@Component +class WalletSyncSchedulerHandlerImpl(private val walletSyncScheduleRepository: WalletSyncScheduleRepository) : + WalletSyncSchedulerHandler { + override suspend fun fetchActiveSchedule(time: LocalDateTime): WalletSyncSchedule? { + val dao = walletSyncScheduleRepository.findActiveSchedule(time).awaitSingleOrNull() + return if (dao !== null) WalletSyncSchedule(dao.retryTime, dao.delay, dao.batchSize) else null + } + + override suspend fun prepareScheduleForNextTry(syncSchedule: WalletSyncSchedule, time: LocalDateTime) { + val dao = WalletSyncScheduleModel(1, time, syncSchedule.delay, syncSchedule.batchSize) + walletSyncScheduleRepository.save(dao).awaitFirst() + } +} diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/model/AddressTypeModel.kt b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/model/AddressTypeModel.kt new file mode 100644 index 000000000..fc1995fe5 --- /dev/null +++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/model/AddressTypeModel.kt @@ -0,0 +1,12 @@ +package co.nilin.opex.port.bcgateway.postgres.model + +import org.springframework.data.relational.core.mapping.Column +import org.springframework.data.relational.core.mapping.Table + +@Table("address_types") +data class AddressTypeModel( + val id: Long?, + @Column("address_type") val type: String, + @Column("address_regex") val addressRegex: String, + @Column("memo_regex") val memoRegex: String +) \ No newline at end of file diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/model/AssignedAddressModel.kt b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/model/AssignedAddressModel.kt new file mode 100644 index 000000000..9e3d3fd95 --- /dev/null +++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/model/AssignedAddressModel.kt @@ -0,0 +1,21 @@ +package co.nilin.opex.port.bcgateway.postgres.model + +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Column +import org.springframework.data.relational.core.mapping.Table + +@Table("assigned_addresses") +data class AssignedAddressModel( + val id: Long?, + val uuid: String, + val address: String, + val memo: String?, + @Column("addr_type_id") val addressTypeId: Long +) + +@Table("assigned_address_chains") +data class AssignedAddressChainModel( + @Id val id: Long?, + @Column("assigned_address_id") val addressTypeId: Long, + @Column("chain") val chain: String +) diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/model/ChainModel.kt b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/model/ChainModel.kt new file mode 100644 index 000000000..fb91ee878 --- /dev/null +++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/model/ChainModel.kt @@ -0,0 +1,22 @@ +package co.nilin.opex.port.bcgateway.postgres.model + +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Column +import org.springframework.data.relational.core.mapping.Table + +@Table("chains") +data class ChainModel(@Id val name: String) + +@Table("chain_address_types") +data class ChainAddressTypeModel( + @Id val id: Long?, @Column("chain_name") val chainName: String, @Column("addr_type_id") val addressTypeId: Long +) + +@Table("chain_endpoints") +data class ChainEndpointModel( + @Id val id: Long?, + @Column("chain_name") val chainName: String, + @Column("endpoint_url") val url: String, + @Column("endpoint_user") val user: String, + @Column("endpoint_password") val password: String +) \ No newline at end of file diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/model/ChainSyncModel.kt b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/model/ChainSyncModel.kt new file mode 100644 index 000000000..c77b3da05 --- /dev/null +++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/model/ChainSyncModel.kt @@ -0,0 +1,21 @@ +package co.nilin.opex.port.bcgateway.postgres.model + +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Column +import org.springframework.data.relational.core.mapping.Table +import java.time.LocalDateTime + +@Table("chain_sync_schedules") +data class ChainSyncScheduleModel( + @Id @Column("chain") val chain: String, @Column("retry_time") val retryTime: LocalDateTime, val delay: Long +) + +@Table("chain_sync_records") +data class ChainSyncRecordModel( + @Id @Column("chain") val chain: String, + val time: LocalDateTime, + @Column("endpoint_url") val endpointUrl: String, + @Column("latest_block") val latestBlock: Long?, + val success: Boolean, + val error: String? +) diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/model/CurrencyImplementationModel.kt b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/model/CurrencyImplementationModel.kt new file mode 100644 index 000000000..eeaaa6f59 --- /dev/null +++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/model/CurrencyImplementationModel.kt @@ -0,0 +1,20 @@ +package co.nilin.opex.port.bcgateway.postgres.model + + +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Column +import org.springframework.data.relational.core.mapping.Table +import java.math.BigDecimal + +@Table("currency_implementations") +class CurrencyImplementationModel( + @Id val id: Long?, + @Column("symbol") val symbol: String, + @Column("chain") val chain: String, + @Column("token") val token: Boolean, + @Column("token_address") val tokenAddress: String?, + @Column("token_name") val tokenName: String?, + @Column("withdraw_enabled") val withdrawEnabled: Boolean, + @Column("withdraw_fee") val withdrawFee: BigDecimal, + @Column("withdraw_min") val withdrawMin: BigDecimal, +) diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/model/CurrencyModel.kt b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/model/CurrencyModel.kt new file mode 100644 index 000000000..354398e25 --- /dev/null +++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/model/CurrencyModel.kt @@ -0,0 +1,12 @@ +package co.nilin.opex.port.bcgateway.postgres.model + + +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Column +import org.springframework.data.relational.core.mapping.Table + +@Table("currency") +class CurrencyModel( + @Id @Column("symbol") val symbol: String, + @Column("name") val name: String +) diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/model/DepositModel.kt b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/model/DepositModel.kt new file mode 100644 index 000000000..617eec478 --- /dev/null +++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/model/DepositModel.kt @@ -0,0 +1,17 @@ +package co.nilin.opex.port.bcgateway.postgres.model + +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Table +import java.math.BigDecimal + +@Table("deposits") +data class DepositModel( + @Id val id: Long?, + val walletSyncRecord: Long?, + val depositor: String, + val depositorMemo: String?, + val amount: BigDecimal, + val chain: String, + val token: Boolean, + val tokenAddress: String? +) diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/model/ReservedAddressModel.kt b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/model/ReservedAddressModel.kt new file mode 100644 index 000000000..fb2d37358 --- /dev/null +++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/model/ReservedAddressModel.kt @@ -0,0 +1,9 @@ +package co.nilin.opex.port.bcgateway.postgres.model + +import org.springframework.data.relational.core.mapping.Column +import org.springframework.data.relational.core.mapping.Table + +@Table("reserved_addresses") +data class ReservedAddressModel( + val id: Long?, val address: String, val memo: String?, @Column("address_type") val type: Long +) \ No newline at end of file diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/model/WalletSyncModel.kt b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/model/WalletSyncModel.kt new file mode 100644 index 000000000..3947af2b7 --- /dev/null +++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/model/WalletSyncModel.kt @@ -0,0 +1,19 @@ +package co.nilin.opex.port.bcgateway.postgres.model + +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Column +import org.springframework.data.relational.core.mapping.Table +import java.time.LocalDateTime + +@Table("wallet_sync_schedules") +data class WalletSyncScheduleModel( + @Id val id: Long?, val retryTime: LocalDateTime, val delay: Long, val batchSize: Long? +) + +@Table("wallet_sync_records") +data class WalletSyncRecordModel( + @Id val id: Long?, + val time: LocalDateTime, + val success: Boolean, + val error: String? +) diff --git a/BlockchainGateway/pom.xml b/BlockchainGateway/pom.xml new file mode 100644 index 000000000..d2090befe --- /dev/null +++ b/BlockchainGateway/pom.xml @@ -0,0 +1,17 @@ + + + 4.0.0 + co.nilin.opex.external + bc-gateway + 1.0-SNAPSHOT + bc-gateway + pom + Blockchain gateway root of opex + + + bc-gateway-core + bc-gateway-app + bc-gateway-ports/bc-persister-postgres + + diff --git a/Deployment/.gitignore b/Deployment/.gitignore new file mode 100644 index 000000000..eaddd784f --- /dev/null +++ b/Deployment/.gitignore @@ -0,0 +1,3 @@ +runtime/ +*.iml + diff --git a/Deployment/docker-compose.yml b/Deployment/docker-compose.yml new file mode 100644 index 000000000..e10c538b7 --- /dev/null +++ b/Deployment/docker-compose.yml @@ -0,0 +1,352 @@ +version: '3.8' +services: + zookeeper: + image: 'docker.io/bitnami/zookeeper:3-debian-10' + ports: + - '127.0.0.1:2181:2181' + volumes: + - $PWD/runtime/zookeeper_data:/bitnami + environment: + - ALLOW_ANONYMOUS_LOGIN=yes + networks: + - opex + deploy: + restart_policy: + condition: on-failure + kafka: + image: 'docker.io/bitnami/kafka:2-debian-10' + ports: + - '127.0.0.1:9092:9092' + volumes: + - $PWD/runtime/kafka-data:/bitnami + environment: + - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181 + - ALLOW_PLAINTEXT_LISTENER=yes + - KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092 + depends_on: + - zookeeper + networks: + - opex + deploy: + restart_policy: + condition: on-failure + consul: + image: 'consul' + ports: + - '127.0.0.1:8500:8500' + - '127.0.0.1:8300:8300' + - '127.0.0.1:8600:8600' + environment: + - CONSUL_BIND_INTERFACE=eth0 + networks: + - opex + deploy: + restart_policy: + condition: on-failure + redis: + image: "redis:alpine" + command: redis-server + ports: + - "127.0.0.1:6379:6379" + volumes: + - $PWD/runtime/redis-data:/var/lib/redis + - $PWD/runtime/redis.conf:/usr/local/etc/redis/redis.conf + environment: + - REDIS_REPLICATION_MODE=master + networks: + - opex + deploy: + restart_policy: + condition: on-failure + postgres-accountant: + image: "postgres" + ports: + - 127.0.0.1:5433:5432 + environment: + - POSTGRES_USER=opex + - POSTGRES_PASSWORD=hiopex + - POSTGRES_DB=opex_accountant + volumes: + - $PWD/runtime/accountant-data:/var/lib/postgresql/data/ + networks: + - opex + postgres-eventlog: + image: "postgres" + ports: + - 127.0.0.1:5434:5432 + environment: + - POSTGRES_USER=opex + - POSTGRES_PASSWORD=hiopex + - POSTGRES_DB=opex_eventlog + volumes: + - $PWD/runtime/eventlog-data:/var/lib/postgresql/data/ + networks: + - opex + postgres-auth: + image: "postgres" + ports: + - 127.0.0.1:5435:5432 + environment: + - POSTGRES_USER=opex + - POSTGRES_PASSWORD=hiopex + - POSTGRES_DB=opex_auth + volumes: + - $PWD/runtime/auth-data:/var/lib/postgresql/data/ + networks: + - opex + deploy: + restart_policy: + condition: on-failure + postgres-wallet: + image: "postgres" + ports: + - 127.0.0.1:5436:5432 + environment: + - POSTGRES_USER=opex + - POSTGRES_PASSWORD=hiopex + - POSTGRES_DB=opex_wallet + volumes: + - $PWD/runtime/wallet-data:/var/lib/postgresql/data/ + networks: + - opex + deploy: + restart_policy: + condition: on-failure + postgres-api: + image: "postgres" + ports: + - 127.0.0.1:5437:5432 + environment: + - POSTGRES_USER=opex + - POSTGRES_PASSWORD=hiopex + - POSTGRES_DB=opex_api + volumes: + - $PWD/runtime/api-data:/var/lib/postgresql/data/ + networks: + - opex + deploy: + restart_policy: + condition: on-failure + postgres-bc-gateway: + image: "postgres" + ports: + - 127.0.0.1:5438:5432 + environment: + - POSTGRES_USER=opex + - POSTGRES_PASSWORD=hiopex + - POSTGRES_DB=opex_bc_gateway + volumes: + - $PWD/runtime/bc-gateway-data:/var/lib/postgresql/data/ + networks: + - opex + deploy: + restart_policy: + condition: on-failure + accountant: + container_name: accountant + build: + context: ../Accountant/accountant-app + dockerfile: Dockerfile + ports: + - 127.0.0.1:8089:8089 + - 127.0.0.1:1051:1044 + environment: + - JAVA_OPTS=-Xmx256m -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=1044 + - SPRING_PROFILES_ACTIVE=docker + - KAFKA_IP_PORT=kafka:9092 + - REDIS_HOST=redis + - CONSUL_HOST=consul + - DB_IP_PORT=postgres-accountant + networks: + - opex + depends_on: + - zookeeper + - kafka + - redis + - consul + - postgres-accountant + eventlog: + container_name: eventlog + build: + context: ../EventLog/eventlog-app + dockerfile: Dockerfile + ports: + - 127.0.0.1:8090:8090 + environment: + - JAVA_OPTS=-Xmx256m + - SPRING_PROFILES_ACTIVE=docker + - KAFKA_IP_PORT=kafka:9092 + - REDIS_HOST=redis + - CONSUL_HOST=consul + - DB_IP_PORT=postgres-eventlog + networks: + - opex + depends_on: + - zookeeper + - kafka + - redis + - consul + - postgres-eventlog + matching-engine: + container_name: matching-engine + build: + context: ../MatchingEngine/matching-app + dockerfile: Dockerfile + ports: + - 127.0.0.1:8092:8092 + - 127.0.0.1:1046:1044 + environment: + - JAVA_OPTS=-Xmx256m -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=1044 + - SPRING_PROFILES_ACTIVE=docker + - KAFKA_IP_PORT=kafka:9092 + - REDIS_HOST=redis + networks: + - opex + depends_on: + - zookeeper + - kafka + - redis + matching-gateway: + container_name: matching-gateway + build: + context: ../MatchingGateway/gateway-app + dockerfile: Dockerfile + ports: + - 127.0.0.1:8093:8093 + - 127.0.0.1:1047:1044 + environment: + - JAVA_OPTS=-Xmx256m -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=1044 + - SPRING_PROFILES_ACTIVE=docker + - KAFKA_IP_PORT=kafka:9092 + - REDIS_HOST=redis + - CONSUL_HOST=consul + networks: + - opex + depends_on: + - zookeeper + - kafka + - consul + auth: + container_name: auth + build: + context: ../UserManagement/keycloak-gateway + dockerfile: Dockerfile + ports: + - 127.0.0.1:8083:8083 + - 127.0.0.1:1048:1044 + environment: + - JAVA_OPTS=-Xmx256m -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=1044 + - SPRING_PROFILES_ACTIVE=docker + - KAFKA_IP_PORT=kafka:9092 + - REDIS_HOST=redis + - CONSUL_HOST=consul + - DB_IP_PORT=postgres-auth + - PROXY_ADDRESS_FORWARDING=true + - WORKING_DIR=$PWD + networks: + - opex + depends_on: + - zookeeper + - kafka + - redis + - consul + - postgres-auth + deploy: + restart_policy: + condition: on-failure + wallet: + container_name: wallet + build: + context: ../Wallet/wallet-app + dockerfile: Dockerfile + ports: + - 127.0.0.1:8091:8091 + - 127.0.0.1:1049:1044 + environment: + - JAVA_OPTS=-Xmx256m -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=1044 + - SPRING_PROFILES_ACTIVE=docker + - KAFKA_IP_PORT=kafka:9092 + - REDIS_HOST=redis + - CONSUL_HOST=consul + - DB_IP_PORT=postgres-wallet + networks: + - opex + depends_on: + - zookeeper + - kafka + - redis + - consul + - postgres-wallet + deploy: + restart_policy: + condition: on-failure + api: + container_name: API + build: + context: ../Api/api-app + dockerfile: Dockerfile + ports: + - 127.0.0.1:8094:8094 + - 127.0.0.1:1050:1044 + environment: + - JAVA_OPTS=-Xmx256m -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=1044 + - SPRING_PROFILES_ACTIVE=docker + - KAFKA_IP_PORT=kafka:9092 + - REDIS_HOST=redis + - CONSUL_HOST=consul + - DB_IP_PORT=postgres-api + networks: + - opex + depends_on: + - zookeeper + - kafka + - redis + - consul + - postgres-api + deploy: + restart_policy: + condition: on-failure + nginx: + image: nginx:latest + container_name: opex_nginx + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf + - $PWD/runtime/www:/data/www + ports: + - 80:80 + depends_on: + - wallet + - auth + - matching-gateway + - api + networks: + - opex + bc-gateway: + container_name: bc-gateway + build: + context: ../BlockchainGateway/bc-gateway-app + dockerfile: Dockerfile + ports: + - 127.0.0.1:8095:8095 + - 127.0.0.1:1051:1044 + environment: + - JAVA_OPTS=-Xmx256m -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=1044 + - SPRING_PROFILES_ACTIVE=docker + - KAFKA_IP_PORT=kafka:9092 + - REDIS_HOST=redis + - CONSUL_HOST=consul + - DB_IP_PORT=postgres-bc-gateway + networks: + - opex + depends_on: + - zookeeper + - kafka + - redis + - consul + - postgres-bc-gateway + deploy: + restart_policy: + condition: on-failure +networks: + opex: + driver: bridge \ No newline at end of file diff --git a/Deployment/nginx.conf b/Deployment/nginx.conf new file mode 100644 index 000000000..687bd16a8 --- /dev/null +++ b/Deployment/nginx.conf @@ -0,0 +1,45 @@ +worker_processes 1; +events { worker_connections 1024; } + http { + sendfile on; + upstream docker-wallet { + server wallet:8091; +} + upstream docker-auth { + server auth:8083; +} + upstream docker-matching-gateway { + server matching-gateway:8093; +} + upstream docker-api { + server api:8094; +} + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $server_name; + + server { + server_name api.opex.dev; + + location /auth { + proxy_pass http://docker-auth; +} + + location /wallet { + proxy_pass http://docker-wallet; + rewrite ^/wallet(.*)$ $1 break; +} + + location /gateway { + proxy_pass http://docker-matching-gateway; + rewrite ^/gateway(.*)$ $1 break; +} + + location /api { + proxy_pass http://docker-api; + rewrite ^/api(.*)$ $1 break; +} +} +} \ No newline at end of file diff --git a/EventLog/.gitignore b/EventLog/.gitignore new file mode 100644 index 000000000..f4e066ca5 --- /dev/null +++ b/EventLog/.gitignore @@ -0,0 +1,36 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ +!/.mvn/ + +.DS_Store diff --git a/EventLog/eventlog-app/.gitignore b/EventLog/eventlog-app/.gitignore new file mode 100644 index 000000000..549e00a2a --- /dev/null +++ b/EventLog/eventlog-app/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/EventLog/eventlog-app/Dockerfile b/EventLog/eventlog-app/Dockerfile new file mode 100644 index 000000000..f2cbd4c26 --- /dev/null +++ b/EventLog/eventlog-app/Dockerfile @@ -0,0 +1,5 @@ +FROM openjdk:8-jdk-alpine +VOLUME /tmp +ARG JAR_FILE=target/*.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"] \ No newline at end of file diff --git a/EventLog/eventlog-app/pom.xml b/EventLog/eventlog-app/pom.xml new file mode 100644 index 000000000..fe3074461 --- /dev/null +++ b/EventLog/eventlog-app/pom.xml @@ -0,0 +1,189 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.4 + + + co.nilin.opex + eventlog-app + 1.0-SNAPSHOT + eventlog-app + Event log running app Opex + + + 1.8 + 1.4.31 + ${version} + ${version} + + + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + com.fasterxml.jackson.module + jackson-module-kotlin + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-webflux + + + co.nilin.opex + matching-core + ${matching.version} + + + co.nilin.opex + eventlog-core + ${eventlog.version} + + + co.nilin.opex + eventlog-eventlistener-kafka + ${eventlog.version} + + + co.nilin.opex + eventlog-persister-postgres + ${eventlog.version} + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + 2.18 + + + ${skip.unit.tests} + + + **/*IntegrationTest.java + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-test-source + generate-test-sources + + add-test-source + + + + src/test/java + + + + + compile + + add-source + + + + src/main/java + + + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + compile + + + + test-compile + test-compile + + test-compile + + + + + + -Xjsr305=strict + + + spring + + 1.8 + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + compile + compile + + compile + + + + testCompile + test-compile + + testCompile + + + + + + opex-eventlog + + + diff --git a/EventLog/eventlog-app/src/main/kotlin/co/nilin/opex/eventlog/app/EventLogApp.kt b/EventLog/eventlog-app/src/main/kotlin/co/nilin/opex/eventlog/app/EventLogApp.kt new file mode 100644 index 000000000..a42bd4d1f --- /dev/null +++ b/EventLog/eventlog-app/src/main/kotlin/co/nilin/opex/eventlog/app/EventLogApp.kt @@ -0,0 +1,12 @@ +package co.nilin.opex.app + +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication +import org.springframework.context.annotation.ComponentScan + +@SpringBootApplication +@ComponentScan("co.nilin.opex") +class EventLogApp +fun main(args: Array) { + runApplication(*args) +} \ No newline at end of file diff --git a/EventLog/eventlog-app/src/main/kotlin/co/nilin/opex/eventlog/app/config/AppConfig.kt b/EventLog/eventlog-app/src/main/kotlin/co/nilin/opex/eventlog/app/config/AppConfig.kt new file mode 100644 index 000000000..82cb52f44 --- /dev/null +++ b/EventLog/eventlog-app/src/main/kotlin/co/nilin/opex/eventlog/app/config/AppConfig.kt @@ -0,0 +1,127 @@ +package co.nilin.opex.eventlog.app.config + +import co.nilin.opex.eventlog.spi.EventPersister +import co.nilin.opex.eventlog.spi.OrderPersister +import co.nilin.opex.eventlog.spi.TradePersister +import co.nilin.opex.matching.core.eventh.events.* +import co.nilin.opex.port.eventlog.kafka.consumer.OrderKafkaListener +import co.nilin.opex.port.eventlog.kafka.spi.OrderSubmitRequestListener +import co.nilin.opex.port.order.kafka.inout.OrderSubmitRequest +import co.nilin.opex.port.eventlog.kafka.consumer.EventKafkaListener +import co.nilin.opex.port.eventlog.kafka.consumer.TradeKafkaListener +import co.nilin.opex.port.eventlog.kafka.spi.EventListener +import co.nilin.opex.port.eventlog.kafka.spi.TradeListener +import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.runBlocking +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import java.util.concurrent.Executors +import org.slf4j.LoggerFactory + +@Configuration +class AppConfig { + + @Bean + fun orderListener(orderPersister: OrderPersister): OrderListener { + return OrderListener(orderPersister) + } + + @Bean + fun eventlogTradeListener(tradePersister: TradePersister): EventlogTradeListener { + return EventlogTradeListener(tradePersister) + } + + @Bean + fun eventlogEventListener( + orderPersister: OrderPersister, eventPersister: EventPersister + ): EventlogEventListener { + return EventlogEventListener(orderPersister, eventPersister) + } + + @Bean + fun orderKafkaListener():OrderKafkaListener { + return OrderKafkaListener(Executors.newFixedThreadPool(10).asCoroutineDispatcher()) + } + + @Autowired + fun configureOrderListener(orderKafkaListener: OrderKafkaListener, orderListener: OrderListener) { + orderKafkaListener.addOrderListener(orderListener) + } + + @Autowired + fun configureTradeListener(tradeKafkaListener: TradeKafkaListener, eventlogTradeListener: EventlogTradeListener) { + tradeKafkaListener.addTradeListener(eventlogTradeListener) + } + + @Autowired + fun configureEventListener(eventKafkaListener: EventKafkaListener, eventlogEventListener: EventlogEventListener) { + eventKafkaListener.addEventListener(eventlogEventListener) + } + + class OrderListener(val orderPersister: OrderPersister) : OrderSubmitRequestListener { + + override fun id(): String { + return "OrderListener" + } + + override suspend fun onOrder(order: OrderSubmitRequest, partition: Int, offset: Long, timestamp: Long) { + orderPersister.submitOrder( + SubmitOrderEvent( + order.ouid, + order.uuid, + order.orderId, + order.pair, + order.price, + order.quantity, + 0, + order.direction, + order.matchConstraint, + order.orderType + ) + ) + } + } + + class EventlogTradeListener(val tradePersister: TradePersister) : TradeListener { + + private val log = LoggerFactory.getLogger(EventlogTradeListener::class.java) + + override fun id(): String { + return "TradeListener" + } + + override fun onTrade(tradeEvent: TradeEvent, partition: Int, offset: Long, timestamp: Long) { + log.debug("Receive TradeEvent {}", tradeEvent) + runBlocking { + tradePersister.saveTrade(tradeEvent) + } + } + } + + class EventlogEventListener( + val orderPersister: OrderPersister, val eventPersister: EventPersister + ) : EventListener { + + private val log = LoggerFactory.getLogger(EventlogEventListener::class.java) + + override fun id(): String { + return "EventListener" + } + + override fun onEvent(coreEvent: CoreEvent, partition: Int, offset: Long, timestamp: Long) { + log.debug("Receive CoreEvent {}", coreEvent) + runBlocking { + if (coreEvent is CreateOrderEvent) + orderPersister.saveOrder(coreEvent) + else if (coreEvent is RejectOrderEvent) + orderPersister.rejectOrder(coreEvent) + else if (coreEvent is UpdatedOrderEvent) + orderPersister.updateOrder(coreEvent) + else if (coreEvent is CancelOrderEvent) + orderPersister.cancelOrder(coreEvent) + eventPersister.saveEvent(coreEvent) + } + } + } +} \ No newline at end of file diff --git a/EventLog/eventlog-app/src/main/resources/application-docker.yml b/EventLog/eventlog-app/src/main/resources/application-docker.yml new file mode 100644 index 000000000..5055b1c51 --- /dev/null +++ b/EventLog/eventlog-app/src/main/resources/application-docker.yml @@ -0,0 +1,11 @@ +server.port: 8090 +spring: + kafka: + bootstrap-servers: ${KAFKA_IP_PORT} + consumer: + group-id: eventlog + r2dbc: + url: r2dbc:postgresql://${DB_IP_PORT}/opex_eventlog + username: opex + password: hiopex + initialization-mode: always \ No newline at end of file diff --git a/EventLog/eventlog-app/src/main/resources/application.yml b/EventLog/eventlog-app/src/main/resources/application.yml new file mode 100644 index 000000000..2b7bd821f --- /dev/null +++ b/EventLog/eventlog-app/src/main/resources/application.yml @@ -0,0 +1,11 @@ +server.port: 8090 +spring: + kafka: + bootstrap-servers: localhost:9092 + consumer: + group-id: eventlog + r2dbc: + url: r2dbc:postgresql://localhost/opex_eventlog + username: opex + password: hiopex + initialization-mode: always \ No newline at end of file diff --git a/EventLog/eventlog-core/.gitignore b/EventLog/eventlog-core/.gitignore new file mode 100644 index 000000000..549e00a2a --- /dev/null +++ b/EventLog/eventlog-core/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/EventLog/eventlog-core/pom.xml b/EventLog/eventlog-core/pom.xml new file mode 100644 index 000000000..f916ddc41 --- /dev/null +++ b/EventLog/eventlog-core/pom.xml @@ -0,0 +1,100 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.4 + + + co.nilin.opex + eventlog-core + 1.0-SNAPSHOT + eventlog-core + Event log of Opex + + + 1.8 + 1.4.31 + ${version} + + + + + co.nilin.opex + matching-core + ${matching.version} + provided + + + org.springframework.boot + spring-boot-starter + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + exchange.core2 + collections + 0.5.1 + + + + io.projectreactor.kotlin + reactor-kotlin-extensions + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + + + org.jetbrains.kotlinx + kotlinx-coroutines-core + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + -Xjsr305=strict + + + spring + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + + + diff --git a/EventLog/eventlog-core/src/main/kotlin/co/nilin/opex/eventlog/spi/Event.kt b/EventLog/eventlog-core/src/main/kotlin/co/nilin/opex/eventlog/spi/Event.kt new file mode 100644 index 000000000..eecd48b7f --- /dev/null +++ b/EventLog/eventlog-core/src/main/kotlin/co/nilin/opex/eventlog/spi/Event.kt @@ -0,0 +1,4 @@ +package co.nilin.opex.eventlog.spi + +interface Event { +} \ No newline at end of file diff --git a/EventLog/eventlog-core/src/main/kotlin/co/nilin/opex/eventlog/spi/EventPersister.kt b/EventLog/eventlog-core/src/main/kotlin/co/nilin/opex/eventlog/spi/EventPersister.kt new file mode 100644 index 000000000..f2e0fb35a --- /dev/null +++ b/EventLog/eventlog-core/src/main/kotlin/co/nilin/opex/eventlog/spi/EventPersister.kt @@ -0,0 +1,7 @@ +package co.nilin.opex.eventlog.spi + +import co.nilin.opex.matching.core.eventh.events.CoreEvent + +interface EventPersister { + suspend fun saveEvent(event: CoreEvent): List +} \ No newline at end of file diff --git a/EventLog/eventlog-core/src/main/kotlin/co/nilin/opex/eventlog/spi/Order.kt b/EventLog/eventlog-core/src/main/kotlin/co/nilin/opex/eventlog/spi/Order.kt new file mode 100644 index 000000000..6816360ca --- /dev/null +++ b/EventLog/eventlog-core/src/main/kotlin/co/nilin/opex/eventlog/spi/Order.kt @@ -0,0 +1,4 @@ +package co.nilin.opex.eventlog.spi + +interface Order { +} \ No newline at end of file diff --git a/EventLog/eventlog-core/src/main/kotlin/co/nilin/opex/eventlog/spi/OrderPersister.kt b/EventLog/eventlog-core/src/main/kotlin/co/nilin/opex/eventlog/spi/OrderPersister.kt new file mode 100644 index 000000000..2eefab154 --- /dev/null +++ b/EventLog/eventlog-core/src/main/kotlin/co/nilin/opex/eventlog/spi/OrderPersister.kt @@ -0,0 +1,11 @@ +package co.nilin.opex.eventlog.spi + +import co.nilin.opex.matching.core.eventh.events.* + +interface OrderPersister { + suspend fun submitOrder(orderEvent: SubmitOrderEvent) + suspend fun rejectOrder(orderEvent: RejectOrderEvent) + suspend fun saveOrder(orderEvent: CreateOrderEvent) + suspend fun updateOrder(orderEvent: UpdatedOrderEvent) + suspend fun cancelOrder(orderEvent: CancelOrderEvent) +} \ No newline at end of file diff --git a/EventLog/eventlog-core/src/main/kotlin/co/nilin/opex/eventlog/spi/Trade.kt b/EventLog/eventlog-core/src/main/kotlin/co/nilin/opex/eventlog/spi/Trade.kt new file mode 100644 index 000000000..127144c2a --- /dev/null +++ b/EventLog/eventlog-core/src/main/kotlin/co/nilin/opex/eventlog/spi/Trade.kt @@ -0,0 +1,4 @@ +package co.nilin.opex.eventlog.spi + +interface Trade { +} \ No newline at end of file diff --git a/EventLog/eventlog-core/src/main/kotlin/co/nilin/opex/eventlog/spi/TradePersister.kt b/EventLog/eventlog-core/src/main/kotlin/co/nilin/opex/eventlog/spi/TradePersister.kt new file mode 100644 index 000000000..cf39ae969 --- /dev/null +++ b/EventLog/eventlog-core/src/main/kotlin/co/nilin/opex/eventlog/spi/TradePersister.kt @@ -0,0 +1,7 @@ +package co.nilin.opex.eventlog.spi + +import co.nilin.opex.matching.core.eventh.events.TradeEvent + +interface TradePersister { + suspend fun saveTrade(tradeEvent: TradeEvent): Trade +} \ No newline at end of file diff --git a/EventLog/eventlog-ports/eventlog-eventlistener-kafka/.gitignore b/EventLog/eventlog-ports/eventlog-eventlistener-kafka/.gitignore new file mode 100644 index 000000000..549e00a2a --- /dev/null +++ b/EventLog/eventlog-ports/eventlog-eventlistener-kafka/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/EventLog/eventlog-ports/eventlog-eventlistener-kafka/mvnw b/EventLog/eventlog-ports/eventlog-eventlistener-kafka/mvnw new file mode 100644 index 000000000..a16b5431b --- /dev/null +++ b/EventLog/eventlog-ports/eventlog-eventlistener-kafka/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/EventLog/eventlog-ports/eventlog-eventlistener-kafka/mvnw.cmd b/EventLog/eventlog-ports/eventlog-eventlistener-kafka/mvnw.cmd new file mode 100644 index 000000000..c8d43372c --- /dev/null +++ b/EventLog/eventlog-ports/eventlog-eventlistener-kafka/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/EventLog/eventlog-ports/eventlog-eventlistener-kafka/pom.xml b/EventLog/eventlog-ports/eventlog-eventlistener-kafka/pom.xml new file mode 100644 index 000000000..38abd4d43 --- /dev/null +++ b/EventLog/eventlog-ports/eventlog-eventlistener-kafka/pom.xml @@ -0,0 +1,106 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.4 + + + co.nilin.opex + eventlog-eventlistener-kafka + 1.0-SNAPSHOT + eventlog-eventlistener-kafka + Matching engine kafka trade handler of Opex + + + 1.8 + 1.4.31 + ${version} + + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-webflux + + + co.nilin.opex + matching-core + ${matching.version} + provided + + + org.springframework.kafka + spring-kafka + + + io.projectreactor.kotlin + reactor-kotlin-extensions + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + + + org.jetbrains.kotlinx + kotlinx-coroutines-core + + + org.springframework.kafka + spring-kafka-test + test + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + -Xjsr305=strict + + + spring + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + + + diff --git a/EventLog/eventlog-ports/eventlog-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/eventlog/kafka/config/EventlogKafkaConfig.kt b/EventLog/eventlog-ports/eventlog-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/eventlog/kafka/config/EventlogKafkaConfig.kt new file mode 100644 index 000000000..f8c441526 --- /dev/null +++ b/EventLog/eventlog-ports/eventlog-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/eventlog/kafka/config/EventlogKafkaConfig.kt @@ -0,0 +1,109 @@ +package co.nilin.opex.port.eventlog.kafka.config + + +import co.nilin.opex.matching.core.eventh.events.CoreEvent +import co.nilin.opex.port.eventlog.kafka.consumer.OrderKafkaListener +import co.nilin.opex.port.eventlog.kafka.consumer.EventKafkaListener +import co.nilin.opex.port.eventlog.kafka.consumer.TradeKafkaListener +import org.apache.kafka.clients.consumer.ConsumerConfig +import org.apache.kafka.clients.producer.ProducerConfig +import org.apache.kafka.common.serialization.StringDeserializer +import org.apache.kafka.common.serialization.StringSerializer +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.beans.factory.annotation.Value +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.kafka.core.ConsumerFactory +import org.springframework.kafka.core.DefaultKafkaConsumerFactory +import org.springframework.kafka.core.DefaultKafkaProducerFactory +import org.springframework.kafka.core.KafkaTemplate +import org.springframework.kafka.core.ProducerFactory +import org.springframework.kafka.listener.ConcurrentMessageListenerContainer +import org.springframework.kafka.listener.ContainerProperties +import org.springframework.kafka.support.serializer.JsonDeserializer +import org.springframework.kafka.support.serializer.JsonSerializer +import java.util.* +import java.util.regex.Pattern + + +@Configuration +class EventlogKafkaConfig { + @Value("\${spring.kafka.bootstrap-servers}") + private val bootstrapServers: String? = null + + @Value("\${spring.kafka.consumer.group-id}") + private val groupId: String? = null + + @Bean("eventlogConsumerConfig") + fun consumerConfigs(): Map? { + val props: MutableMap = HashMap() + props[ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG] = bootstrapServers + props[ConsumerConfig.GROUP_ID_CONFIG] = groupId + props[ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG] = StringDeserializer::class.java + props[ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG] = JsonDeserializer::class.java + props[JsonDeserializer.TRUSTED_PACKAGES] = "co.nilin.opex.*" + return props + } + + @Bean("eventlogConsumerFactory") + fun consumerFactory(@Qualifier("eventlogConsumerConfig")consumerConfigs: Map): ConsumerFactory { + return DefaultKafkaConsumerFactory(consumerConfigs) + } + + @Bean("eventlogProducerConfig") + fun producerConfigs(): Map { + val props: MutableMap = HashMap() + props[ProducerConfig.BOOTSTRAP_SERVERS_CONFIG] = bootstrapServers + props[ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG] = StringSerializer::class.java + props[ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG] = JsonSerializer::class.java + return props + } + + @Bean("eventlogProducerFactory") + fun producerFactory(@Qualifier("eventlogProducerConfig") producerConfigs: Map): ProducerFactory { + return DefaultKafkaProducerFactory(producerConfigs) + } + + @Bean("eventlogKafkaTemplate") + fun kafkaTemplate(@Qualifier("eventlogProducerFactory") producerFactory: ProducerFactory): KafkaTemplate { + return KafkaTemplate(producerFactory) + } + + + @Autowired + @ConditionalOnBean(TradeKafkaListener::class) + fun configureTradeListener(tradeListener: TradeKafkaListener + , @Qualifier("eventlogConsumerFactory") consumerFactory: ConsumerFactory) { + val containerProps = ContainerProperties(Pattern.compile("trades_.*")) + containerProps.messageListener = tradeListener + val container = ConcurrentMessageListenerContainer(consumerFactory, containerProps) + container.setBeanName("TradeKafkaListenerContainer") + container.start() + } + + @Autowired + @ConditionalOnBean(EventKafkaListener::class) + fun configureEventListener(eventListener: EventKafkaListener + , @Qualifier("eventlogConsumerFactory") consumerFactory: ConsumerFactory) { + val containerProps = ContainerProperties(Pattern.compile("events_.*")) + containerProps.messageListener = eventListener + val container = ConcurrentMessageListenerContainer(consumerFactory, containerProps) + container.setBeanName("EventKafkaListenerContainer") + container.start() + } + + @Autowired + @ConditionalOnBean(OrderKafkaListener::class) + fun configureOrderListener(orderListener: OrderKafkaListener + , @Qualifier("eventlogConsumerFactory") consumerFactory: ConsumerFactory) { + val containerProps = ContainerProperties(Pattern.compile("orders_.*")) + containerProps.messageListener = orderListener + val container = ConcurrentMessageListenerContainer(consumerFactory, containerProps) + container.setBeanName("OrderKafkaListenerContainer") + container.start() + } + + +} \ No newline at end of file diff --git a/EventLog/eventlog-ports/eventlog-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/eventlog/kafka/consumer/EventKafkaListener.kt b/EventLog/eventlog-ports/eventlog-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/eventlog/kafka/consumer/EventKafkaListener.kt new file mode 100644 index 000000000..4e02b2665 --- /dev/null +++ b/EventLog/eventlog-ports/eventlog-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/eventlog/kafka/consumer/EventKafkaListener.kt @@ -0,0 +1,28 @@ +package co.nilin.opex.port.eventlog.kafka.consumer + + +import co.nilin.opex.matching.core.eventh.events.CoreEvent +import co.nilin.opex.port.eventlog.kafka.spi.EventListener +import org.apache.kafka.clients.consumer.ConsumerRecord +import org.springframework.kafka.listener.MessageListener +import org.springframework.stereotype.Component + +@Component +class EventKafkaListener: MessageListener { + val eventListeners = arrayListOf() + override fun onMessage(data: ConsumerRecord) { + eventListeners.forEach{ + tl -> tl.onEvent(data.value(), data.partition(), data.offset(), data.timestamp()) + } + } + + fun addEventListener(tl: EventListener){ + eventListeners.add(tl) + } + + fun removeEventListener(tl: EventListener){ + eventListeners.removeIf { + item -> item.id() == tl.id() + } + } +} \ No newline at end of file diff --git a/EventLog/eventlog-ports/eventlog-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/eventlog/kafka/consumer/OrderKafkaListener.kt b/EventLog/eventlog-ports/eventlog-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/eventlog/kafka/consumer/OrderKafkaListener.kt new file mode 100644 index 000000000..efe994344 --- /dev/null +++ b/EventLog/eventlog-ports/eventlog-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/eventlog/kafka/consumer/OrderKafkaListener.kt @@ -0,0 +1,34 @@ +package co.nilin.opex.port.eventlog.kafka.consumer + +import co.nilin.opex.port.eventlog.kafka.spi.OrderSubmitRequestListener +import co.nilin.opex.port.order.kafka.inout.OrderSubmitRequest +import kotlinx.coroutines.ExecutorCoroutineDispatcher +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import org.apache.kafka.clients.consumer.ConsumerRecord +import org.springframework.kafka.listener.MessageListener +import org.springframework.stereotype.Component + + +class OrderKafkaListener(private val executorCoroutineDispatcher: ExecutorCoroutineDispatcher) : MessageListener { + val orderListeners = arrayListOf() + override fun onMessage(data: ConsumerRecord) { + runBlocking { + orderListeners.forEach { tl -> + withContext(executorCoroutineDispatcher) { + tl.onOrder(data.value(), data.partition(), data.offset(), data.timestamp()) + } + } + } + } + + fun addOrderListener(tl: OrderSubmitRequestListener) { + orderListeners.add(tl) + } + + fun removeOrderListener(tl: OrderSubmitRequestListener) { + orderListeners.removeIf { item -> + item.id() == tl.id() + } + } +} \ No newline at end of file diff --git a/EventLog/eventlog-ports/eventlog-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/eventlog/kafka/consumer/TradeKafkaListener.kt b/EventLog/eventlog-ports/eventlog-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/eventlog/kafka/consumer/TradeKafkaListener.kt new file mode 100644 index 000000000..9672f2ab8 --- /dev/null +++ b/EventLog/eventlog-ports/eventlog-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/eventlog/kafka/consumer/TradeKafkaListener.kt @@ -0,0 +1,28 @@ +package co.nilin.opex.port.eventlog.kafka.consumer + + +import co.nilin.opex.matching.core.eventh.events.TradeEvent +import co.nilin.opex.port.eventlog.kafka.spi.TradeListener +import org.apache.kafka.clients.consumer.ConsumerRecord +import org.springframework.kafka.listener.MessageListener +import org.springframework.stereotype.Component + +@Component +class TradeKafkaListener: MessageListener { + val tradeListeners = arrayListOf() + override fun onMessage(data: ConsumerRecord) { + tradeListeners.forEach{ + tl -> tl.onTrade(data.value(), data.partition(), data.offset(), data.timestamp()) + } + } + + fun addTradeListener(tl: TradeListener){ + tradeListeners.add(tl) + } + + fun removeTradeListener(tl: TradeListener){ + tradeListeners.removeIf { + item -> item.id() == tl.id() + } + } +} \ No newline at end of file diff --git a/EventLog/eventlog-ports/eventlog-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/eventlog/kafka/spi/EventListener.kt b/EventLog/eventlog-ports/eventlog-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/eventlog/kafka/spi/EventListener.kt new file mode 100644 index 000000000..6cbf6e603 --- /dev/null +++ b/EventLog/eventlog-ports/eventlog-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/eventlog/kafka/spi/EventListener.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.port.eventlog.kafka.spi + +import co.nilin.opex.matching.core.eventh.events.CoreEvent + +interface EventListener { + fun id(): String + fun onEvent(coreEvent: CoreEvent, partition: Int, offset: Long, timestamp: Long) +} \ No newline at end of file diff --git a/EventLog/eventlog-ports/eventlog-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/eventlog/kafka/spi/OrderSubmitRequestListener.kt b/EventLog/eventlog-ports/eventlog-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/eventlog/kafka/spi/OrderSubmitRequestListener.kt new file mode 100644 index 000000000..4c90e5b03 --- /dev/null +++ b/EventLog/eventlog-ports/eventlog-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/eventlog/kafka/spi/OrderSubmitRequestListener.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.port.eventlog.kafka.spi + +import co.nilin.opex.port.order.kafka.inout.OrderSubmitRequest + +interface OrderSubmitRequestListener { + fun id(): String + suspend fun onOrder(order: OrderSubmitRequest, partition: Int, offset: Long, timestamp: Long) +} \ No newline at end of file diff --git a/EventLog/eventlog-ports/eventlog-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/eventlog/kafka/spi/TradeListener.kt b/EventLog/eventlog-ports/eventlog-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/eventlog/kafka/spi/TradeListener.kt new file mode 100644 index 000000000..8d8891173 --- /dev/null +++ b/EventLog/eventlog-ports/eventlog-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/eventlog/kafka/spi/TradeListener.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.port.eventlog.kafka.spi + +import co.nilin.opex.matching.core.eventh.events.TradeEvent + +interface TradeListener { + fun id(): String + fun onTrade(tradeEvent: TradeEvent, partition: Int, offset: Long, timestamp: Long) +} \ No newline at end of file diff --git a/EventLog/eventlog-ports/eventlog-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/inout/OrderSubmitRequest.kt b/EventLog/eventlog-ports/eventlog-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/inout/OrderSubmitRequest.kt new file mode 100644 index 000000000..b15b98eed --- /dev/null +++ b/EventLog/eventlog-ports/eventlog-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/inout/OrderSubmitRequest.kt @@ -0,0 +1,39 @@ +package co.nilin.opex.port.order.kafka.inout + +import co.nilin.opex.matching.core.model.MatchConstraint +import co.nilin.opex.matching.core.model.OrderDirection +import co.nilin.opex.matching.core.model.OrderType + +public class OrderSubmitRequest() { + lateinit var ouid: String + lateinit var uuid: String + var orderId: Long? = null + lateinit var pair: co.nilin.opex.matching.core.model.Pair + var price: Long = 0 + var quantity: Long = 0 + var direction: OrderDirection = OrderDirection.BID + var matchConstraint: MatchConstraint = MatchConstraint.GTC + var orderType: OrderType = OrderType.LIMIT_ORDER + + constructor(ouid: String, + uuid: String, + orderId: Long?, + pair: co.nilin.opex.matching.core.model.Pair, + price: Long, + quantity: Long, + direction: OrderDirection, + matchConstraint: MatchConstraint, + orderType: OrderType):this(){ + this.ouid = ouid + this.uuid = uuid + this.orderId = orderId + this.pair = pair + this.price = price + this.quantity = quantity + this.direction = direction + this.matchConstraint = matchConstraint + this.orderType = orderType + } + + +} \ No newline at end of file diff --git a/EventLog/eventlog-ports/eventlog-persister-postgres/.gitignore b/EventLog/eventlog-ports/eventlog-persister-postgres/.gitignore new file mode 100644 index 000000000..549e00a2a --- /dev/null +++ b/EventLog/eventlog-ports/eventlog-persister-postgres/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/EventLog/eventlog-ports/eventlog-persister-postgres/mvnw b/EventLog/eventlog-ports/eventlog-persister-postgres/mvnw new file mode 100644 index 000000000..a16b5431b --- /dev/null +++ b/EventLog/eventlog-ports/eventlog-persister-postgres/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/EventLog/eventlog-ports/eventlog-persister-postgres/mvnw.cmd b/EventLog/eventlog-ports/eventlog-persister-postgres/mvnw.cmd new file mode 100644 index 000000000..c8d43372c --- /dev/null +++ b/EventLog/eventlog-ports/eventlog-persister-postgres/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/EventLog/eventlog-ports/eventlog-persister-postgres/pom.xml b/EventLog/eventlog-ports/eventlog-persister-postgres/pom.xml new file mode 100644 index 000000000..3c2fcc5e3 --- /dev/null +++ b/EventLog/eventlog-ports/eventlog-persister-postgres/pom.xml @@ -0,0 +1,112 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.4 + + + co.nilin.opex + eventlog-persister-postgres + 1.0-SNAPSHOT + eventlog-persister-postgres + Persist items of Opex on Postgres + + + 1.8 + 1.4.31 + ${version} + ${version} + + + + + co.nilin.opex + matching-core + ${matching.version} + provided + + + co.nilin.opex + eventlog-core + ${eventlog.version} + provided + + + org.springframework.boot + spring-boot-starter-data-r2dbc + + + io.r2dbc + r2dbc-postgresql + runtime + + + org.postgresql + postgresql + runtime + + + + io.projectreactor.kotlin + reactor-kotlin-extensions + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + + + org.jetbrains.kotlinx + kotlinx-coroutines-core + + + io.projectreactor + reactor-test + test + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + -Xjsr305=strict + + + spring + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + + diff --git a/EventLog/eventlog-ports/eventlog-persister-postgres/src/main/kotlin/co/nilin/opex/port/eventlog/postgres/config/PostgresConfig.kt b/EventLog/eventlog-ports/eventlog-persister-postgres/src/main/kotlin/co/nilin/opex/port/eventlog/postgres/config/PostgresConfig.kt new file mode 100644 index 000000000..8b6803afc --- /dev/null +++ b/EventLog/eventlog-ports/eventlog-persister-postgres/src/main/kotlin/co/nilin/opex/port/eventlog/postgres/config/PostgresConfig.kt @@ -0,0 +1,80 @@ +package co.nilin.opex.port.eventlog.postgres.config + +import org.springframework.context.annotation.Configuration +import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories +import org.springframework.r2dbc.core.DatabaseClient + +@Configuration +@EnableR2dbcRepositories(basePackages = ["co.nilin.opex"]) +class PostgresConfig(db: DatabaseClient) { + + init { + val initDb = db.sql { + """ CREATE TABLE IF NOT EXISTS opex_orders ( + id SERIAL PRIMARY KEY, + ouid VARCHAR(72) NOT NULL UNIQUE, + symbol VARCHAR(20), + direction VARCHAR(20), + match_constraint VARCHAR(20), + order_type VARCHAR(20), + uuid VARCHAR(72) NOT NULL, + agent VARCHAR(20), + ip VARCHAR(11), + order_date TIMESTAMP, + create_date TIMESTAMP + ); + CREATE TABLE IF NOT EXISTS opex_order_events ( + id SERIAL PRIMARY KEY, + ouid VARCHAR(72) NOT NULL, + matching_orderid bigint, + price bigint, + quantity bigint, + filled_quantity bigint, + uuid VARCHAR(72) NOT NULL, + event VARCHAR(30) NOT NULL, + agent VARCHAR(20), + ip VARCHAR(11), + event_date TIMESTAMP, + create_date TIMESTAMP + ); + CREATE TABLE IF NOT EXISTS opex_events ( + id SERIAL PRIMARY KEY, + correlation_id VARCHAR(72), + ouid VARCHAR(72), + uuid VARCHAR(72), + symbol VARCHAR(20), + event VARCHAR(30) NOT NULL, + event_json TEXT NOT NULL, + agent VARCHAR(20), + ip VARCHAR(11), + event_date TIMESTAMP, + create_date TIMESTAMP + ); + CREATE TABLE IF NOT EXISTS opex_trades ( + id SERIAL PRIMARY KEY, + symbol VARCHAR(20), + taker_ouid VARCHAR(72) NOT NULL, + taker_uuid VARCHAR(72) NOT NULL, + taker_matching_orderid bigint, + taker_direction VARCHAR(20), + taker_price bigint, + taker_remained_quantity bigint, + maker_ouid VARCHAR(72) NOT NULL, + maker_uuid VARCHAR(72) NOT NULL, + maker_matching_orderid bigint, + maker_direction VARCHAR(20), + maker_price bigint, + maker_remained_quantity bigint, + matched_quantity bigint, + trade_date TIMESTAMP, + create_date TIMESTAMP + ); + """ + } + + initDb // initialize the database + .then() + .subscribe() // execute + } + +} diff --git a/EventLog/eventlog-ports/eventlog-persister-postgres/src/main/kotlin/co/nilin/opex/port/eventlog/postgres/dao/EventRepository.kt b/EventLog/eventlog-ports/eventlog-persister-postgres/src/main/kotlin/co/nilin/opex/port/eventlog/postgres/dao/EventRepository.kt new file mode 100644 index 000000000..b8f5bd05c --- /dev/null +++ b/EventLog/eventlog-ports/eventlog-persister-postgres/src/main/kotlin/co/nilin/opex/port/eventlog/postgres/dao/EventRepository.kt @@ -0,0 +1,9 @@ +package co.nilin.opex.port.eventlog.postgres.dao + +import co.nilin.opex.port.eventlog.postgres.model.EventModel +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository + +@Repository +interface EventRepository: ReactiveCrudRepository { +} \ No newline at end of file diff --git a/EventLog/eventlog-ports/eventlog-persister-postgres/src/main/kotlin/co/nilin/opex/port/eventlog/postgres/dao/OrderEventRepository.kt b/EventLog/eventlog-ports/eventlog-persister-postgres/src/main/kotlin/co/nilin/opex/port/eventlog/postgres/dao/OrderEventRepository.kt new file mode 100644 index 000000000..329fc4bc8 --- /dev/null +++ b/EventLog/eventlog-ports/eventlog-persister-postgres/src/main/kotlin/co/nilin/opex/port/eventlog/postgres/dao/OrderEventRepository.kt @@ -0,0 +1,10 @@ +package co.nilin.opex.port.eventlog.postgres.dao + +import co.nilin.opex.port.eventlog.postgres.model.OrderEventsModel +import co.nilin.opex.port.eventlog.postgres.model.OrderModel +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository + +@Repository +interface OrderEventRepository: ReactiveCrudRepository { +} \ No newline at end of file diff --git a/EventLog/eventlog-ports/eventlog-persister-postgres/src/main/kotlin/co/nilin/opex/port/eventlog/postgres/dao/OrderRepository.kt b/EventLog/eventlog-ports/eventlog-persister-postgres/src/main/kotlin/co/nilin/opex/port/eventlog/postgres/dao/OrderRepository.kt new file mode 100644 index 000000000..698335999 --- /dev/null +++ b/EventLog/eventlog-ports/eventlog-persister-postgres/src/main/kotlin/co/nilin/opex/port/eventlog/postgres/dao/OrderRepository.kt @@ -0,0 +1,9 @@ +package co.nilin.opex.port.eventlog.postgres.dao + +import co.nilin.opex.port.eventlog.postgres.model.OrderModel +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository + +@Repository +interface OrderRepository: ReactiveCrudRepository { +} \ No newline at end of file diff --git a/EventLog/eventlog-ports/eventlog-persister-postgres/src/main/kotlin/co/nilin/opex/port/eventlog/postgres/dao/TradeRepository.kt b/EventLog/eventlog-ports/eventlog-persister-postgres/src/main/kotlin/co/nilin/opex/port/eventlog/postgres/dao/TradeRepository.kt new file mode 100644 index 000000000..7fa737a35 --- /dev/null +++ b/EventLog/eventlog-ports/eventlog-persister-postgres/src/main/kotlin/co/nilin/opex/port/eventlog/postgres/dao/TradeRepository.kt @@ -0,0 +1,9 @@ +package co.nilin.opex.port.eventlog.postgres.dao + +import co.nilin.opex.port.eventlog.postgres.model.TradeModel +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository + +@Repository +interface TradeRepository: ReactiveCrudRepository{ +} \ No newline at end of file diff --git a/EventLog/eventlog-ports/eventlog-persister-postgres/src/main/kotlin/co/nilin/opex/port/eventlog/postgres/impl/EventPersisterImpl.kt b/EventLog/eventlog-ports/eventlog-persister-postgres/src/main/kotlin/co/nilin/opex/port/eventlog/postgres/impl/EventPersisterImpl.kt new file mode 100644 index 000000000..a21565cf7 --- /dev/null +++ b/EventLog/eventlog-ports/eventlog-persister-postgres/src/main/kotlin/co/nilin/opex/port/eventlog/postgres/impl/EventPersisterImpl.kt @@ -0,0 +1,72 @@ +package co.nilin.opex.port.eventlog.postgres.impl + +import co.nilin.opex.eventlog.spi.Event +import co.nilin.opex.eventlog.spi.EventPersister +import co.nilin.opex.matching.core.eventh.events.* +import co.nilin.opex.port.eventlog.postgres.dao.EventRepository +import co.nilin.opex.port.eventlog.postgres.model.EventModel +import kotlinx.coroutines.reactive.awaitFirst +import kotlinx.coroutines.reactive.awaitFirstOrNull +import org.springframework.stereotype.Component +import java.time.LocalDateTime +import java.util.* + +@Component +class EventPersisterImpl(val eventRepository: EventRepository) : EventPersister { + override suspend fun saveEvent(event: CoreEvent): List { + if (event is OneOrderEvent) { + return listOf(eventRepository.save( + EventModel( + null, + UUID.randomUUID().toString(), + event.ouid(), + event.uuid(), + event.pair.toString(), + event::class.simpleName!!, + "", + "agent", + "127.0.0.1", + event.eventDate, + LocalDateTime.now() + ) + ).awaitFirst()) + } else if (event is TradeEvent) { + val correlation = UUID.randomUUID().toString() + val tuple = eventRepository.save( + EventModel( + null, + correlation, + event.takerOuid, + event.takerUuid, + event.pair.toString(), + event::class.simpleName!!, + "", + "agent", + "127.0.0.1", + event.eventDate, + LocalDateTime.now() + ) + ).zipWhen { + eventRepository.save( + EventModel( + null, + correlation, + event.makerOuid, + event.makerUuid, + event.pair.toString(), + event::class.simpleName!!, + "", + "agent", + "127.0.0.1", + event.eventDate, + LocalDateTime.now() + ) + ) + }.awaitFirst() + return listOf(tuple.t1, tuple.t2) + } + TODO() + } + + +} \ No newline at end of file diff --git a/EventLog/eventlog-ports/eventlog-persister-postgres/src/main/kotlin/co/nilin/opex/port/eventlog/postgres/impl/OrderPersisterImpl.kt b/EventLog/eventlog-ports/eventlog-persister-postgres/src/main/kotlin/co/nilin/opex/port/eventlog/postgres/impl/OrderPersisterImpl.kt new file mode 100644 index 000000000..caf1dcf08 --- /dev/null +++ b/EventLog/eventlog-ports/eventlog-persister-postgres/src/main/kotlin/co/nilin/opex/port/eventlog/postgres/impl/OrderPersisterImpl.kt @@ -0,0 +1,66 @@ +package co.nilin.opex.port.eventlog.postgres.impl + +import co.nilin.opex.eventlog.spi.OrderPersister +import co.nilin.opex.matching.core.eventh.events.* +import co.nilin.opex.port.eventlog.postgres.dao.OrderEventRepository +import co.nilin.opex.port.eventlog.postgres.dao.OrderRepository +import co.nilin.opex.port.eventlog.postgres.model.OrderEventsModel +import co.nilin.opex.port.eventlog.postgres.model.OrderModel +import kotlinx.coroutines.reactive.awaitFirst +import kotlinx.coroutines.reactive.awaitFirstOrNull +import kotlinx.coroutines.reactive.awaitSingle +import org.springframework.stereotype.Component +import java.time.LocalDateTime + +@Component +class OrderPersisterImpl(val orderRepository: OrderRepository +, val orderEventRepository: OrderEventRepository): OrderPersister { + override suspend fun submitOrder(orderEvent: SubmitOrderEvent) { + orderRepository.save(OrderModel(null, orderEvent.ouid + , orderEvent.pair.toString() + , orderEvent.direction.toString() + , orderEvent.matchConstraint.toString() + , orderEvent.orderType.toString() + , orderEvent.uuid, "agent", "127.0.0.1", orderEvent.eventDate, LocalDateTime.now())) + .block() + orderEventRepository.save(OrderEventsModel(null + , orderEvent.ouid, orderEvent.uuid, orderEvent.orderId, orderEvent.price + , orderEvent.quantity, orderEvent.quantity - orderEvent.remainedQuantity + , orderEvent.javaClass.simpleName + , "agent", "127.0.0.1", orderEvent.eventDate, LocalDateTime.now())) + .awaitFirst() + + } + + override suspend fun rejectOrder(orderEvent: RejectOrderEvent) { + orderEventRepository.save(OrderEventsModel(null + , orderEvent.ouid, orderEvent.uuid, orderEvent.orderId, orderEvent.price + , orderEvent.quantity, 0 + , orderEvent.javaClass.simpleName + , "agent", "127.0.0.1", orderEvent.eventDate, LocalDateTime.now())).awaitFirst() + } + + override suspend fun saveOrder(orderEvent: CreateOrderEvent) { + orderEventRepository.save(OrderEventsModel(null + , orderEvent.ouid, orderEvent.uuid, orderEvent.orderId, orderEvent.price + , orderEvent.quantity, orderEvent.quantity - orderEvent.remainedQuantity + , orderEvent.javaClass.simpleName + , "agent", "127.0.0.1", orderEvent.eventDate, LocalDateTime.now())).awaitFirstOrNull() + } + + override suspend fun updateOrder(orderEvent: UpdatedOrderEvent) { + orderEventRepository.save(OrderEventsModel(null + , orderEvent.ouid, orderEvent.uuid, orderEvent.orderId, orderEvent.price + , orderEvent.quantity, orderEvent.quantity - orderEvent.remainedQuantity + , orderEvent.javaClass.simpleName + , "agent", "127.0.0.1", orderEvent.eventDate, LocalDateTime.now())).awaitFirstOrNull() + } + + override suspend fun cancelOrder(orderEvent: CancelOrderEvent) { + orderEventRepository.save(OrderEventsModel(null + , orderEvent.ouid, orderEvent.uuid, orderEvent.orderId, orderEvent.price + , orderEvent.quantity, orderEvent.quantity - orderEvent.remainedQuantity + , orderEvent.javaClass.simpleName + , "agent", "127.0.0.1", orderEvent.eventDate, LocalDateTime.now())).awaitFirst() + } +} \ No newline at end of file diff --git a/EventLog/eventlog-ports/eventlog-persister-postgres/src/main/kotlin/co/nilin/opex/port/eventlog/postgres/impl/TradePersisterImpl.kt b/EventLog/eventlog-ports/eventlog-persister-postgres/src/main/kotlin/co/nilin/opex/port/eventlog/postgres/impl/TradePersisterImpl.kt new file mode 100644 index 000000000..9740a008a --- /dev/null +++ b/EventLog/eventlog-ports/eventlog-persister-postgres/src/main/kotlin/co/nilin/opex/port/eventlog/postgres/impl/TradePersisterImpl.kt @@ -0,0 +1,37 @@ +package co.nilin.opex.port.eventlog.postgres.impl + +import co.nilin.opex.eventlog.spi.Trade +import co.nilin.opex.eventlog.spi.TradePersister +import co.nilin.opex.matching.core.eventh.events.TradeEvent +import co.nilin.opex.port.eventlog.postgres.dao.TradeRepository +import co.nilin.opex.port.eventlog.postgres.model.TradeModel +import kotlinx.coroutines.reactive.awaitFirst +import org.springframework.stereotype.Component +import java.time.LocalDateTime + +@Component +class TradePersisterImpl(val tradeRepository: TradeRepository) : TradePersister { + override suspend fun saveTrade(tradeEvent: TradeEvent): Trade { + return tradeRepository.save( + TradeModel( + null, + tradeEvent.pair.toString(), + tradeEvent.takerOuid, + tradeEvent.takerUuid, + tradeEvent.takerOrderId, + tradeEvent.takerDirection.toString(), + tradeEvent.takerPrice, + tradeEvent.takerRemainedQuantity, + tradeEvent.makerOuid, + tradeEvent.makerUuid, + tradeEvent.makerOrderId, + tradeEvent.makerDirection.toString(), + tradeEvent.makerPrice, + tradeEvent.makerRemainedQuantity, + tradeEvent.matchedQuantity, + tradeEvent.eventDate, + LocalDateTime.now() + ) + ).awaitFirst() + } +} \ No newline at end of file diff --git a/EventLog/eventlog-ports/eventlog-persister-postgres/src/main/kotlin/co/nilin/opex/port/eventlog/postgres/model/EventModel.kt b/EventLog/eventlog-ports/eventlog-persister-postgres/src/main/kotlin/co/nilin/opex/port/eventlog/postgres/model/EventModel.kt new file mode 100644 index 000000000..54ced7554 --- /dev/null +++ b/EventLog/eventlog-ports/eventlog-persister-postgres/src/main/kotlin/co/nilin/opex/port/eventlog/postgres/model/EventModel.kt @@ -0,0 +1,23 @@ +package co.nilin.opex.port.eventlog.postgres.model + +import co.nilin.opex.eventlog.spi.Event +import co.nilin.opex.eventlog.spi.Trade +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Column +import org.springframework.data.relational.core.mapping.Table +import java.time.LocalDateTime + +@Table("opex_events") +class EventModel( + @Id var id: Long?, + val correlationId: String, + val ouid: String, + val uuid: String, + val symbol: String, + val event: String, + @Column("event_json") val eventJson: String, + val agent: String, + val ip: String, + @Column("event_date") val eventDate: LocalDateTime, + @Column("create_date") val createDate: LocalDateTime +) : Event \ No newline at end of file diff --git a/EventLog/eventlog-ports/eventlog-persister-postgres/src/main/kotlin/co/nilin/opex/port/eventlog/postgres/model/OrderEventsModel.kt b/EventLog/eventlog-ports/eventlog-persister-postgres/src/main/kotlin/co/nilin/opex/port/eventlog/postgres/model/OrderEventsModel.kt new file mode 100644 index 000000000..65180d4c8 --- /dev/null +++ b/EventLog/eventlog-ports/eventlog-persister-postgres/src/main/kotlin/co/nilin/opex/port/eventlog/postgres/model/OrderEventsModel.kt @@ -0,0 +1,22 @@ +package co.nilin.opex.port.eventlog.postgres.model + +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Column +import org.springframework.data.relational.core.mapping.Table +import java.time.LocalDateTime + +@Table("opex_order_events") +class OrderEventsModel( + @Id var id: Long?, + var ouid: String, + val uuid: String, + @Column("matching_orderid") val matchingOrderId: Long?, + val price: Long?, + val quantity: Long?, + @Column("filled_quantity") val filledQuantity: Long?, + val event: String, + val agent: String, + val ip: String, + @Column("event_date") val eventDate: LocalDateTime, + @Column("create_date") val createDate: LocalDateTime +) \ No newline at end of file diff --git a/EventLog/eventlog-ports/eventlog-persister-postgres/src/main/kotlin/co/nilin/opex/port/eventlog/postgres/model/OrderModel.kt b/EventLog/eventlog-ports/eventlog-persister-postgres/src/main/kotlin/co/nilin/opex/port/eventlog/postgres/model/OrderModel.kt new file mode 100644 index 000000000..3fd65a0ed --- /dev/null +++ b/EventLog/eventlog-ports/eventlog-persister-postgres/src/main/kotlin/co/nilin/opex/port/eventlog/postgres/model/OrderModel.kt @@ -0,0 +1,23 @@ +package co.nilin.opex.port.eventlog.postgres.model + +import co.nilin.opex.eventlog.spi.Order +import co.nilin.opex.eventlog.spi.Trade +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Column +import org.springframework.data.relational.core.mapping.Table +import java.time.LocalDateTime + +@Table("opex_orders") +class OrderModel( + @Id var id: Long?, + val ouid: String, + val symbol: String, + val direction: String, + @Column("match_constraint") val matchConstraint: String, + @Column("order_type") val orderType: String, + val uuid: String, + val agent: String, + val ip: String, + @Column("order_date") val orderDate: LocalDateTime, + @Column("create_date") val createDate: LocalDateTime +) : Order \ No newline at end of file diff --git a/EventLog/eventlog-ports/eventlog-persister-postgres/src/main/kotlin/co/nilin/opex/port/eventlog/postgres/model/TradeModel.kt b/EventLog/eventlog-ports/eventlog-persister-postgres/src/main/kotlin/co/nilin/opex/port/eventlog/postgres/model/TradeModel.kt new file mode 100644 index 000000000..8a0f74b8b --- /dev/null +++ b/EventLog/eventlog-ports/eventlog-persister-postgres/src/main/kotlin/co/nilin/opex/port/eventlog/postgres/model/TradeModel.kt @@ -0,0 +1,45 @@ +package co.nilin.opex.port.eventlog.postgres.model + +import co.nilin.opex.eventlog.spi.Trade +import co.nilin.opex.matching.core.model.OrderDirection +import co.nilin.opex.matching.core.model.Pair +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Column +import org.springframework.data.relational.core.mapping.Table +import java.time.LocalDateTime + +@Table("opex_trades") +class TradeModel( + @Id var id: Long?, + val symbol: String, + @Column("taker_ouid") + val takerOuid: String, + @Column("taker_uuid") + val takerUuid: String, + @Column("taker_matching_orderid") + val takerOrderId: Long, + @Column("taker_direction") + val takerDirection: String, + @Column("taker_price") + val takerPrice: Long, + @Column("taker_remained_quantity") + val takerRemainedQuantity: Long, + @Column("maker_ouid") + val makerOuid: String, + @Column("maker_uuid") + val makerUuid: String, + @Column("maker_matching_orderid") + val makerOrderId: Long, + @Column("maker_direction") + val makerDirection: String, + @Column("maker_price") + val makerPrice: Long, + @Column("maker_remained_quantity") + val makerRemainedQuantity: Long, + @Column("matched_quantity") + val matchedQuantity: Long, + @Column("trade_date") + val eventDate: LocalDateTime, + @Column("create_date") + val createDate: LocalDateTime +) : Trade \ No newline at end of file diff --git a/EventLog/pom.xml b/EventLog/pom.xml new file mode 100644 index 000000000..25d17a0d6 --- /dev/null +++ b/EventLog/pom.xml @@ -0,0 +1,18 @@ + + + 4.0.0 + co.nilin.opex + eventlog + 1.0-SNAPSHOT + eventlog + pom + Event log root of Opex + + + eventlog-core + eventlog-ports/eventlog-persister-postgres + eventlog-ports/eventlog-eventlistener-kafka + eventlog-app + + diff --git a/Jenkins/Jenkinsfile.deploy.groovy b/Jenkins/Jenkinsfile.deploy.groovy new file mode 100644 index 000000000..628c665cd --- /dev/null +++ b/Jenkins/Jenkinsfile.deploy.groovy @@ -0,0 +1,57 @@ +pipeline { + agent any + + stages { + stage('Build') { + steps { + withMaven( + maven: 'maven-latest' + ) { + dir("Utility") { + sh 'mvn -B clean install' + } + + dir("MatchingEngine") { + sh 'mvn -B clean install' + } + + dir("MatchingGateway") { + sh 'mvn -B clean install' + } + + dir("Accountant") { + sh 'mvn -B clean install' + } + + dir("EventLog") { + sh 'mvn -B clean install' + } + + dir("UserManagement") { + sh 'mvn -B clean install' + } + + dir("Wallet") { + sh 'mvn -B clean install' + } + + dir("Api") { + sh 'mvn -B clean install' + } + } + + } + } + stage('Deliver') { + steps { + dir("Deployment") { + sh 'docker-compose build' + } + + dir("Deployment") { + sh 'docker-compose up -d' + } + } + } + } +} diff --git a/LICENSE-THIRD-PARTY b/LICENSE-THIRD-PARTY new file mode 100644 index 000000000..593602476 --- /dev/null +++ b/LICENSE-THIRD-PARTY @@ -0,0 +1,6 @@ +The Opex source code itself does not bundle any third party libraries, but it +depends on a number of libraries which carry their own copyright notices and +license terms. These libraries are normally all linked static into the binary +distributions of Opex: +exchange-core/collection - https://github.com/exchange-core/collections/blob/master/LICENSE + diff --git a/MatchingEngine/.gitignore b/MatchingEngine/.gitignore new file mode 100644 index 000000000..d0fc406b4 --- /dev/null +++ b/MatchingEngine/.gitignore @@ -0,0 +1,86 @@ +# Created by .ignore support plugin (hsz.mobi) +### Kotlin template +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 +.idea/ +*.iml +*.ipr + +# CMake +cmake-build-*/ + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + + +### macOS template +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + diff --git a/MatchingEngine/matching-app/.gitignore b/MatchingEngine/matching-app/.gitignore new file mode 100644 index 000000000..7f15adddb --- /dev/null +++ b/MatchingEngine/matching-app/.gitignore @@ -0,0 +1,77 @@ +# Created by .ignore support plugin (hsz.mobi) +### Kotlin template +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +.idea/artifacts +.idea/compiler.xml +.idea/jarRepositories.xml +.idea/modules.xml +.idea/*.iml +.idea/modules +*.iml +*.ipr + +# File-based project format +*.iws + +# IntelliJ +out/ + +target/ + + + + diff --git a/MatchingEngine/matching-app/Dockerfile b/MatchingEngine/matching-app/Dockerfile new file mode 100644 index 000000000..f2cbd4c26 --- /dev/null +++ b/MatchingEngine/matching-app/Dockerfile @@ -0,0 +1,5 @@ +FROM openjdk:8-jdk-alpine +VOLUME /tmp +ARG JAR_FILE=target/*.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"] \ No newline at end of file diff --git a/MatchingEngine/matching-app/pom.xml b/MatchingEngine/matching-app/pom.xml new file mode 100644 index 000000000..1f93bef15 --- /dev/null +++ b/MatchingEngine/matching-app/pom.xml @@ -0,0 +1,188 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.4 + + + co.nilin.opex + matching-app + 1.0-SNAPSHOT + matching-app + Matching engine running app Opex + + + 1.8 + 1.4.31 + ${version} + + + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + com.fasterxml.jackson.module + jackson-module-kotlin + + + org.springframework.boot + spring-boot-starter + + + co.nilin.opex + matching-core + ${matching.version} + + + co.nilin.opex + matching-submitter-kafka + ${matching.version} + + + co.nilin.opex + matching-eventlistener-kafka + ${matching.version} + + + co.nilin.opex + matching-snapshots-redis + ${matching.version} + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + 2.18 + + + ${skip.unit.tests} + + + **/*IntegrationTest.java + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-test-source + generate-test-sources + + add-test-source + + + + src/test/java + + + + + compile + + add-source + + + + src/main/java + + + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + compile + + + + test-compile + test-compile + + test-compile + + + + + + -Xjsr305=strict + + + spring + + 1.8 + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + compile + compile + + compile + + + + testCompile + test-compile + + testCompile + + + + + + opex-matching-engine + + + diff --git a/MatchingEngine/matching-app/src/main/kotlin/co/nilin/opex/app/MatchingEngineApp.kt b/MatchingEngine/matching-app/src/main/kotlin/co/nilin/opex/app/MatchingEngineApp.kt new file mode 100644 index 000000000..b98a4585c --- /dev/null +++ b/MatchingEngine/matching-app/src/main/kotlin/co/nilin/opex/app/MatchingEngineApp.kt @@ -0,0 +1,13 @@ +package co.nilin.opex.app + +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication +import org.springframework.context.annotation.ComponentScan + +@SpringBootApplication +@ComponentScan("co.nilin.opex") +class MatchingEngineApp + +fun main(args: Array) { + runApplication(*args) +} \ No newline at end of file diff --git a/MatchingEngine/matching-app/src/main/kotlin/co/nilin/opex/app/bl/ExchangeEventHandler.kt b/MatchingEngine/matching-app/src/main/kotlin/co/nilin/opex/app/bl/ExchangeEventHandler.kt new file mode 100644 index 000000000..22bac3fbd --- /dev/null +++ b/MatchingEngine/matching-app/src/main/kotlin/co/nilin/opex/app/bl/ExchangeEventHandler.kt @@ -0,0 +1,29 @@ +package co.nilin.opex.app.bl + +import co.nilin.opex.matching.core.eventh.EventDispatcher +import co.nilin.opex.matching.core.eventh.events.* +import co.nilin.opex.port.order.kafka.service.EventsSubmitter +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import org.springframework.stereotype.Component + +@Component +class ExchangeEventHandler(eventsSubmitter: EventsSubmitter) +{ + fun register(){ + EventDispatcher.register(CreateOrderEvent::class.java, handler) + EventDispatcher.register(CancelOrderEvent::class.java, handler) + EventDispatcher.register(UpdatedOrderEvent::class.java, handler) + EventDispatcher.register(RejectOrderEvent::class.java, handler) + EventDispatcher.register(SubmitOrderEvent::class.java, handler) + EventDispatcher.register(TradeEvent::class.java, handler) + } + + val handler: (CoreEvent) -> Unit = { + CoroutineScope(Dispatchers.Default).launch { + eventsSubmitter.submit(it) + } + } + +} \ No newline at end of file diff --git a/MatchingEngine/matching-app/src/main/kotlin/co/nilin/opex/app/bl/OrderBooks.kt b/MatchingEngine/matching-app/src/main/kotlin/co/nilin/opex/app/bl/OrderBooks.kt new file mode 100644 index 000000000..53725d72b --- /dev/null +++ b/MatchingEngine/matching-app/src/main/kotlin/co/nilin/opex/app/bl/OrderBooks.kt @@ -0,0 +1,26 @@ +package co.nilin.opex.app.bl + +import co.nilin.opex.matching.core.factory.OrderBookFactory +import co.nilin.opex.matching.core.model.OrderBook +import co.nilin.opex.matching.core.model.PersistentOrderBook + +object OrderBooks { + private val orderBooks = mutableMapOf() + + fun createOrderBook(pair: String) { + println("Going to add order book:" + pair + ", current order books#" + orderBooks.size) + if ( orderBooks.containsKey(pair)) + throw IllegalArgumentException("${pair} has an order book right now!") + val pairs = pair.split("_") + orderBooks[pair] = OrderBookFactory.createOrderBook(co.nilin.opex.matching.core.model.Pair(pairs[0], pairs[1])) + println("order book:" + pair + " added, current order books#" + orderBooks.size) + } + + fun reloadOrderBook(orderBook: PersistentOrderBook){ + orderBooks["${orderBook.pair.leftSideName}_${orderBook.pair.rightSideName}"] = OrderBookFactory.createOrderBook(orderBook) + } + + fun lookupOrderBook(pair: String): OrderBook { + return orderBooks[pair]?:throw IllegalArgumentException("No orderbook for $pair") + } +} \ No newline at end of file diff --git a/MatchingEngine/matching-app/src/main/kotlin/co/nilin/opex/app/config/AppConfig.kt b/MatchingEngine/matching-app/src/main/kotlin/co/nilin/opex/app/config/AppConfig.kt new file mode 100644 index 000000000..482f1c036 --- /dev/null +++ b/MatchingEngine/matching-app/src/main/kotlin/co/nilin/opex/app/config/AppConfig.kt @@ -0,0 +1,161 @@ +package co.nilin.opex.app.config + +import co.nilin.opex.app.bl.ExchangeEventHandler +import co.nilin.opex.app.bl.OrderBooks +import co.nilin.opex.matching.core.eventh.events.* +import co.nilin.opex.matching.core.inout.OrderCancelCommand +import co.nilin.opex.matching.core.inout.OrderCreateCommand +import co.nilin.opex.matching.core.inout.OrderEditCommand +import co.nilin.opex.matching.core.model.PersistentOrderBook +import co.nilin.opex.matching.core.spi.OrderBookPersister +import co.nilin.opex.port.order.kafka.consumer.EventKafkaListener +import co.nilin.opex.port.order.kafka.consumer.OrderKafkaListener +import co.nilin.opex.port.order.kafka.inout.OrderSubmitRequest +import co.nilin.opex.port.order.kafka.spi.EventListener +import co.nilin.opex.port.order.kafka.spi.OrderSubmitRequestListener +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Value +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +class AppConfig { + + @Value("\${spring.app.symbols}") + private val symbols: String? = null + + @Bean + @ConditionalOnMissingBean(value = [OrderBookPersister::class]) + fun orderBookPersister(): OrderBookPersister { + return object : OrderBookPersister { + override suspend fun storeLastState(orderBook: PersistentOrderBook) { + } + + override suspend fun loadLastState(symbol: String): PersistentOrderBook? { + return null + } + } + } + + + @Autowired + fun configureOrderBooks(orderBookPersister: OrderBookPersister) { + symbols!!.split(",") + .forEach { symbol -> + CoroutineScope(AppSchedulers.generalExecutor).launch { + val lastOrderBook = orderBookPersister.loadLastState(symbol) + //todo: load db orders from last order in order book and put in order book + //todo: add missing orders to lastOrderBook or create one + if (lastOrderBook != null) { + withContext(coroutineContext) { + OrderBooks.reloadOrderBook(lastOrderBook) + } + } else { + OrderBooks.createOrderBook(symbol) + } + + } + } + } + + + @Bean + fun orderListener(orderBookPersister: OrderBookPersister): OrderListener { + return OrderListener(orderBookPersister) + } + + @Autowired + fun configureOrderListener(orderKafkaListener: OrderKafkaListener, orderListener: OrderListener) { + orderKafkaListener.addOrderListener(orderListener) + } + + @Bean + fun eventListener(orderBookPersister: OrderBookPersister): MatchingEngineEventListener { + return MatchingEngineEventListener(orderBookPersister) + } + + @Autowired + fun configureEventListener(eventKafkaListener: EventKafkaListener, eventListener: MatchingEngineEventListener) { + eventKafkaListener.addEventListener(eventListener) + } + + @Autowired + fun configureMatchingEngineListener(exchangeEventHandler: ExchangeEventHandler) { + exchangeEventHandler.register() + } + + class OrderListener(private val orderBookPersister: OrderBookPersister) : OrderSubmitRequestListener { + + override fun id(): String { + return "OrderListener" + } + + override suspend fun onOrder(order: OrderSubmitRequest, partition: Int, offset: Long, timestamp: Long) { + val orderBook = OrderBooks.lookupOrderBook( + order.pair.leftSideName + "_" + + order.pair.rightSideName + ) + orderBook.handleNewOrderCommand( + OrderCreateCommand( + order.ouid, + order.uuid, + order.pair, + order.price, + order.quantity, + order.direction, + order.matchConstraint, + order.orderType + ) + ) + orderBookPersister.storeLastState(orderBook.persistent()) + } + } + + class MatchingEngineEventListener(private val orderBookPersister: OrderBookPersister) : EventListener { + + private val logger = LoggerFactory.getLogger(MatchingEngineEventListener::class.java) + + override fun id(): String { + return "EventListener" + } + + override fun onEvent(event: CoreEvent, partition: Int, offset: Long, timestamp: Long) { + logger.info("Received CoreEvent: ${event::class.java}") + + runBlocking(AppSchedulers.kafkaExecutor) { + val orderBook = OrderBooks.lookupOrderBook("${event.pair.leftSideName}_${event.pair.rightSideName}") + + when (event) { + is UpdatedOrderEvent -> orderBook.handleEditCommand( + OrderEditCommand( + event.ouid, + event.uuid, + event.orderId, + event.pair, + event.price, + event.quantity + ) + ) + + is CancelOrderEvent -> orderBook.handleCancelCommand( + OrderCancelCommand( + event.ouid, + event.uuid, + event.orderId, + event.pair + ) + ) + } + + orderBookPersister.storeLastState(orderBook.persistent()) + } + } + } + +} \ No newline at end of file diff --git a/MatchingEngine/matching-app/src/main/kotlin/co/nilin/opex/app/config/AppSchedulers.kt b/MatchingEngine/matching-app/src/main/kotlin/co/nilin/opex/app/config/AppSchedulers.kt new file mode 100644 index 000000000..8aa5390f3 --- /dev/null +++ b/MatchingEngine/matching-app/src/main/kotlin/co/nilin/opex/app/config/AppSchedulers.kt @@ -0,0 +1,9 @@ +package co.nilin.opex.app.config + +import kotlinx.coroutines.asCoroutineDispatcher +import java.util.concurrent.Executors + +object AppSchedulers { + val generalExecutor = Executors.newFixedThreadPool(5).asCoroutineDispatcher() + val kafkaExecutor = Executors.newSingleThreadExecutor().asCoroutineDispatcher() +} \ No newline at end of file diff --git a/MatchingEngine/matching-app/src/main/resources/application-docker.yml b/MatchingEngine/matching-app/src/main/resources/application-docker.yml new file mode 100644 index 000000000..d2bddd8e0 --- /dev/null +++ b/MatchingEngine/matching-app/src/main/resources/application-docker.yml @@ -0,0 +1,13 @@ +server.port: 8092 +spring: + main: + allow-bean-definition-overriding: false + kafka: + bootstrap-servers: ${KAFKA_IP_PORT} + consumer: + group-id: engine + redis: + host: ${REDIS_HOST} + port: 6379 + app: + symbols: btc_usdt,eth_usdt,eth_btc \ No newline at end of file diff --git a/MatchingEngine/matching-app/src/main/resources/application.yml b/MatchingEngine/matching-app/src/main/resources/application.yml new file mode 100644 index 000000000..13eeed8ad --- /dev/null +++ b/MatchingEngine/matching-app/src/main/resources/application.yml @@ -0,0 +1,13 @@ +server.port: 8092 +spring: + main: + allow-bean-definition-overriding: false + kafka: + bootstrap-servers: localhost:9092 + consumer: + group-id: engine + redis: + host: 127.0.0.1 + port: 6379 + app: + symbols: btc_usdt,eth_usdt,eth_btc \ No newline at end of file diff --git a/MatchingEngine/matching-core/.gitignore b/MatchingEngine/matching-core/.gitignore new file mode 100644 index 000000000..7f15adddb --- /dev/null +++ b/MatchingEngine/matching-core/.gitignore @@ -0,0 +1,77 @@ +# Created by .ignore support plugin (hsz.mobi) +### Kotlin template +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +.idea/artifacts +.idea/compiler.xml +.idea/jarRepositories.xml +.idea/modules.xml +.idea/*.iml +.idea/modules +*.iml +*.ipr + +# File-based project format +*.iws + +# IntelliJ +out/ + +target/ + + + + diff --git a/MatchingEngine/matching-core/mvnw b/MatchingEngine/matching-core/mvnw new file mode 100644 index 000000000..a16b5431b --- /dev/null +++ b/MatchingEngine/matching-core/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/MatchingEngine/matching-core/mvnw.cmd b/MatchingEngine/matching-core/mvnw.cmd new file mode 100644 index 000000000..c8d43372c --- /dev/null +++ b/MatchingEngine/matching-core/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/MatchingEngine/matching-core/pom.xml b/MatchingEngine/matching-core/pom.xml new file mode 100644 index 000000000..48b09607a --- /dev/null +++ b/MatchingEngine/matching-core/pom.xml @@ -0,0 +1,93 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.4 + + + co.nilin.opex + matching-core + 1.0-SNAPSHOT + matching-core + Matching engine of Opex + + + 1.8 + 1.4.31 + + + + + org.springframework.boot + spring-boot-starter + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + exchange.core2 + collections + 0.5.1 + + + + io.projectreactor.kotlin + reactor-kotlin-extensions + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + + + org.jetbrains.kotlinx + kotlinx-coroutines-core + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + -Xjsr305=strict + + + spring + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + + + diff --git a/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/engine/SimpleOrderBook.kt b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/engine/SimpleOrderBook.kt new file mode 100644 index 000000000..e615a60ec --- /dev/null +++ b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/engine/SimpleOrderBook.kt @@ -0,0 +1,465 @@ +package co.nilin.opex.matching.core.engine + +import co.nilin.opex.matching.core.eventh.EventDispatcher +import co.nilin.opex.matching.core.eventh.events.* +import co.nilin.opex.matching.core.inout.* +import co.nilin.opex.matching.core.model.* +import exchange.core2.collections.art.LongAdaptiveRadixTreeMap +import org.slf4j.LoggerFactory +import java.util.* +import java.util.concurrent.atomic.AtomicLong + +class SimpleOrderBook(val pair: Pair, var replayMode: Boolean) : OrderBook { + + private val logger =LoggerFactory.getLogger(SimpleOrderBook::class.java) + + val askOrders = LongAdaptiveRadixTreeMap() + val bidOrders = LongAdaptiveRadixTreeMap() + val orders = TreeMap() + + var bestAskOrder: SimpleOrder? = null + var bestBidOrder: SimpleOrder? = null + + val orderCounter = AtomicLong() + val tradeCounter = AtomicLong() + + var lastOrder: SimpleOrder? = null + + data class SimpleOrder(var id: Long?, val ouid: String, val uuid: String, val price: Long, val quantity: Long, val matchConstraint: MatchConstraint, val orderType: OrderType, val direction: OrderDirection, var filledQuantity: Long, var worse: SimpleOrder?, var better: SimpleOrder?, var bucket: Bucket?) : Order { + fun remainedQuantity() = quantity - filledQuantity + override fun id(): Long? = id + override fun toString(): String { + return "SimpleOrder(id=$id, price=$price, quantity=$quantity, matchConstraint=$matchConstraint, orderType=$orderType, filledQuantity=$filledQuantity, worse=${worse?.id}, better=${better?.id}, bucket=${bucket?.totalQuantity})" + } + + override fun persistent(): PersistentOrder { + return PersistentOrder(id!!, ouid, uuid, price, quantity, matchConstraint, orderType, direction, filledQuantity) + } + } + + data class Bucket(val price: Long, var totalQuantity: Long, var ordersCount: Long, var lastOrder: SimpleOrder) + + override fun handleNewOrderCommand(orderCommand: OrderCreateCommand): Order? { + logger.info("****************** new order received *******************") + logger.info("** order id: ${orderCommand.ouid}") + logger.info("** price: ${orderCommand.price}") + logger.info("** quantity: ${orderCommand.quantity}") + logger.info("** direction: ${orderCommand.direction}") + logger.info("*********************************************************") + println() + + val order = when (orderCommand.matchConstraint) { + MatchConstraint.GTC -> { + if (orderCommand.orderType == OrderType.MARKET_ORDER) { + if (!replayMode) { + EventDispatcher.emit(RejectOrderEvent(orderCommand.ouid, orderCommand.uuid, orderCommand.pair, orderCommand.price, orderCommand.quantity, orderCommand.direction, orderCommand.matchConstraint, orderCommand.orderType, RequestedOperation.PLACE_ORDER, RejectReason.ORDER_TYPE_NOT_MATCHED_MATCHC)) + } + return null + } + val order = SimpleOrder( + orderCounter.incrementAndGet(), + orderCommand.ouid, + orderCommand.uuid, + orderCommand.price, + orderCommand.quantity, + orderCommand.matchConstraint, + orderCommand.orderType, + orderCommand.direction, + 0, + null, + null, + null + ) + if (!replayMode) { + EventDispatcher.emit(CreateOrderEvent(orderCommand.ouid, orderCommand.uuid, + order.id!!, orderCommand.pair, orderCommand.price, orderCommand.quantity, order.remainedQuantity(), orderCommand.direction, orderCommand.matchConstraint, orderCommand.orderType + )) + } + // try to match instantly + val queueOrder = matchInstantly(order) + // if remained quantity > 0 add to queue + if (queueOrder.filledQuantity != queueOrder.quantity) { + putGtcInQueue(queueOrder) + } + queueOrder + } + MatchConstraint.IOC -> { + val order = SimpleOrder( + orderCounter.incrementAndGet(), + orderCommand.ouid, + orderCommand.uuid, + orderCommand.price, + orderCommand.quantity, + orderCommand.matchConstraint, + orderCommand.orderType, + orderCommand.direction, + 0, + null, + null, + null + ) + if (!replayMode) { + EventDispatcher.emit( + CreateOrderEvent( + orderCommand.ouid, orderCommand.uuid, + order.id!!, orderCommand.pair, orderCommand.price, + orderCommand.quantity, order.remainedQuantity(), + orderCommand.direction, orderCommand.matchConstraint, orderCommand.orderType + ) + ) + } + // try to match instantly + val queueOrder = matchIocInstantly(order) + if (!replayMode) { + if (queueOrder.filledQuantity != queueOrder.quantity) { + EventDispatcher.emit(CancelOrderEvent(orderCommand.ouid, orderCommand.uuid, + queueOrder.id!!, orderCommand.pair, order.price, order.quantity, order.remainedQuantity(), order.direction, order.matchConstraint, order.orderType + )) + } + } + queueOrder + } + else -> { + if (!replayMode) { + EventDispatcher.emit(RejectOrderEvent(orderCommand.ouid, orderCommand.uuid,orderCommand.pair, orderCommand.price, orderCommand.quantity, orderCommand.direction, orderCommand.matchConstraint, orderCommand.orderType, RequestedOperation.PLACE_ORDER, RejectReason.OPERATION_NOT_MATCHED_MATCHC)) + } + null + } + } + lastOrder = order + logCurrentState() + return order + } + + override fun handleCancelCommand(orderCommand: OrderCancelCommand) { + logger.info("********************* order cancel **********************") + logger.info("** order id: ${orderCommand.ouid}") + logger.info("*********************************************************") + println() + + val simpleOrder = orders.entries.find { it.value.ouid == orderCommand.ouid } + val order = simpleOrder?.value + if (order == null /*check for userid*/) { + if (!replayMode) { + EventDispatcher.emit(RejectOrderEvent(orderCommand.ouid, orderCommand.uuid,orderCommand.orderId, orderCommand.pair, RequestedOperation.CANCEL_ORDER, RejectReason.ORDER_NOT_FOUND)) + } + return + } else { + orders.remove(simpleOrder.key) + } + + if (order.direction == OrderDirection.BID) { + handleCancelOrder(order, bidOrders, bestBidOrder) { newBestOrder: SimpleOrder? -> + bestBidOrder = newBestOrder + } + } else { + handleCancelOrder(order, askOrders, bestAskOrder) { newBestOrder: SimpleOrder? -> + bestAskOrder = newBestOrder + } + } + if (!replayMode) { + EventDispatcher.emit(CancelOrderEvent(orderCommand.ouid, orderCommand.uuid, + orderCommand.orderId, orderCommand.pair, + order.price, order.quantity, + order.remainedQuantity(), order.direction, + order.matchConstraint, order.orderType + )) + } + logCurrentState() + } + + override fun handleEditCommand(orderCommand: OrderEditCommand): Order? { + val order = orders.remove(orderCommand.orderId) + if (order == null /*check for userid*/) { + if (!replayMode) { + EventDispatcher.emit(RejectOrderEvent(orderCommand.ouid, orderCommand.uuid,orderCommand.orderId, orderCommand.pair, RequestedOperation.EDIT_ORDER, RejectReason.ORDER_NOT_FOUND)) + } + return order + } + if (order.direction == OrderDirection.BID) { + handleCancelOrder(order, bidOrders, bestBidOrder) { newBestOrder: SimpleOrder? -> + bestBidOrder = newBestOrder + } + } else { + handleCancelOrder(order, askOrders, bestAskOrder) { newBestOrder: SimpleOrder? -> + bestAskOrder = newBestOrder + } + } + val newOrder = SimpleOrder( + order.id, + orderCommand.ouid, + orderCommand.uuid, + orderCommand.price, + orderCommand.quantity, + order.matchConstraint, + order.orderType, + order.direction, + order.filledQuantity, + null, + null, + null + ) + + return when (order.matchConstraint) { + MatchConstraint.GTC -> { + if (!replayMode) { + EventDispatcher.emit(UpdatedOrderEvent(orderCommand.ouid, orderCommand.uuid, + order.id!!, orderCommand.pair, order.price, order.quantity, + orderCommand.price, orderCommand.quantity, order.remainedQuantity(), + order.direction, order.matchConstraint, order.orderType + )) + } + // try to match instantly + val queueOrder = matchInstantly(newOrder) + //if remained quantity > 0 add to queue + if (queueOrder.filledQuantity != queueOrder.quantity) { + putGtcInQueue(queueOrder) + } + queueOrder + } + MatchConstraint.IOC -> { + if (!replayMode) { + EventDispatcher.emit(UpdatedOrderEvent(orderCommand.ouid, orderCommand.uuid, + order.id!!, orderCommand.pair, order.price, order.quantity, orderCommand.price, orderCommand.quantity, order.remainedQuantity(), order.direction, order.matchConstraint, order.orderType + )) + } + // try to match instantly + val queueOrder = matchIocInstantly(newOrder) + if (!replayMode) { + if (queueOrder.filledQuantity != queueOrder.quantity) { + EventDispatcher.emit(CancelOrderEvent(orderCommand.ouid, orderCommand.uuid, + queueOrder.id!!, orderCommand.pair, order.price, order.quantity, order.remainedQuantity(), order.direction, order.matchConstraint, order.orderType + )) + } + } + queueOrder + } + else -> { + if (!replayMode) { + EventDispatcher.emit(RejectOrderEvent(orderCommand.ouid, orderCommand.uuid,orderCommand.orderId, orderCommand.pair, orderCommand.price, orderCommand.quantity, order.direction, order.matchConstraint, order.orderType, RequestedOperation.EDIT_ORDER, RejectReason.OPERATION_NOT_MATCHED_MATCHC)) + } + null + } + } + + } + + fun handleCancelOrder(order: SimpleOrder, bucketQueue: LongAdaptiveRadixTreeMap, bestOrder: SimpleOrder?, setBestOrder: (SimpleOrder?) -> Unit) { + val bucket = order.bucket!! + bucket.ordersCount-- + bucket.totalQuantity -= order.remainedQuantity() + if (bucket.lastOrder == order) { + if (bucket.lastOrder.better == null || bucket.lastOrder.better!!.bucket != bucket) { + bucketQueue.remove(bucket.price) + } else + bucket.lastOrder = bucket.lastOrder.better!! + } + order.better?.worse = order.worse + order.worse?.better = order.better + if (order == bestOrder) + setBestOrder(bestOrder.worse) + } + + private fun matchInstantly(order: SimpleOrder): SimpleOrder { + if (order.direction == OrderDirection.BID) { + return matchInstantly(order, bestAskOrder, askOrders, { makerPrice: Long -> + makerPrice <= order.price + }) { newMakerOrder: SimpleOrder? -> + bestAskOrder = newMakerOrder + } + } else { + return matchInstantly(order, bestBidOrder, bidOrders, { makerPrice: Long -> + makerPrice >= order.price + }) { newMakerOrder: SimpleOrder? -> + bestBidOrder = newMakerOrder + } + } + } + + private fun matchIocInstantly(order: SimpleOrder): SimpleOrder { + if (order.direction == OrderDirection.BID) { + return matchInstantly(order, bestAskOrder, askOrders, { makerPrice: Long -> + order.orderType == OrderType.MARKET_ORDER || makerPrice <= order.price + + }) { newMakerOrder: SimpleOrder? -> + bestAskOrder = newMakerOrder + } + } else { + return matchInstantly(order, bestBidOrder, bidOrders, { makerPrice: Long -> + order.orderType == OrderType.MARKET_ORDER || makerPrice >= order.price + }) { newMakerOrder: SimpleOrder? -> + bestBidOrder = newMakerOrder + } + } + } + + private fun putGtcInQueue(order: SimpleOrder): SimpleOrder { + if (order.direction == OrderDirection.BID) { + return putGtcInQueue(order, bidOrders, bestBidOrder, { price, queue -> + queue.getHigherValue(price) + }) { newMakerOrder: SimpleOrder? -> + bestBidOrder = newMakerOrder + } + } else { + return putGtcInQueue(order, askOrders, bestAskOrder, { price, queue -> + queue.getLowerValue(price) + }) { newMakerOrder: SimpleOrder? -> + bestAskOrder = newMakerOrder + } + } + } + + private fun matchInstantly(order: SimpleOrder, makerOrder: SimpleOrder?, queue: LongAdaptiveRadixTreeMap, isPriceMatched: (makerPrice: Long) -> Boolean, setNewMarkerOrder: (SimpleOrder?) -> Unit): SimpleOrder { + //the best sell price is higher the requested buy price, so no instant match + if (makerOrder == null || !isPriceMatched(makerOrder.price)) { + return order + } + var currentMaker = makerOrder + var lastOrderOfMakerBucket = makerOrder.bucket!!.lastOrder + do { + val instantMatchQuantity = Math.min(order.remainedQuantity(), currentMaker!!.remainedQuantity()) + order.filledQuantity += instantMatchQuantity + currentMaker.filledQuantity += instantMatchQuantity + currentMaker.bucket!!.totalQuantity -= instantMatchQuantity + if (!replayMode) { + EventDispatcher.emit(TradeEvent(tradeCounter.incrementAndGet(), pair, order.ouid, order.uuid, order.id + ?: 0, order.direction, order.price, order.remainedQuantity(), currentMaker.ouid, currentMaker.uuid, currentMaker.id!!, currentMaker.direction, currentMaker.price, currentMaker.remainedQuantity(), instantMatchQuantity)) + } + if (currentMaker.remainedQuantity() == 0L) { + currentMaker.bucket!!.ordersCount-- + } + //create trade with instantMatchQuantity + if (currentMaker.remainedQuantity() > 0) { + break + } + //remove the makerOrder + orders.remove(currentMaker.id!!) + if (currentMaker == lastOrderOfMakerBucket) { + queue.remove(currentMaker.price) + if (currentMaker.worse != null) + lastOrderOfMakerBucket = currentMaker.worse!!.bucket!!.lastOrder + } + + currentMaker = currentMaker.worse + } while (order.remainedQuantity() > 0 + && currentMaker != null + && isPriceMatched(currentMaker.price) + ) + if (currentMaker != null) { + currentMaker.better = null + } + setNewMarkerOrder(currentMaker) + return order + + } + + + fun putGtcInQueue(order: SimpleOrder, + queue: LongAdaptiveRadixTreeMap, + bestOrder: SimpleOrder?, + betterBucketSelector: (price: Long, queue: LongAdaptiveRadixTreeMap) -> Bucket?, + setNewMarkerOrder: (SimpleOrder?) -> Unit + ): SimpleOrder { + if (order.id == null) + order.id = orderCounter.incrementAndGet() + orders[order.id!!] = order + var bucket = queue[order.price] + if (bucket != null) { + bucket.ordersCount++ + bucket.totalQuantity += order.remainedQuantity() + order.bucket = bucket + val bucketLastOrder = bucket.lastOrder + val worseOfBucketLastOrder = bucketLastOrder.worse + bucket.lastOrder = order + bucketLastOrder.worse = order + if (worseOfBucketLastOrder != null) { + worseOfBucketLastOrder.better = order + } + order.better = bucketLastOrder + order.worse = worseOfBucketLastOrder + } else { + bucket = Bucket( + order.price, + order.remainedQuantity(), + 1, + order + ) + order.bucket = bucket + queue.put(order.price, bucket) + val betterBucket = betterBucketSelector(order.price, queue) + if (betterBucket != null) { + val aboveBucketLastOrder = betterBucket.lastOrder + val worseOrder = aboveBucketLastOrder.worse + aboveBucketLastOrder.worse = order + if (worseOrder != null) { + worseOrder.better = order + } + order.better = aboveBucketLastOrder + order.worse = worseOrder + } else { + if (bestOrder != null) + bestOrder.better = order + order.worse = bestOrder + setNewMarkerOrder(order) + } + } + return order + } + + override fun pair(): Pair { + return pair + } + + override fun startReplayMode() { + replayMode = true + } + + override fun stopReplayMode() { + replayMode = false + } + + override fun lastOrder(): Order? { + return lastOrder + } + + override fun persistent(): PersistentOrderBook { + val persistent = PersistentOrderBook(pair) + persistent.lastOrder = lastOrder?.persistent() + persistent.orders = orders.values + .map { order -> order.persistent() } + return persistent + } + + fun rebuild(persistentOrderBook: PersistentOrderBook) { + persistentOrderBook.orders?.map { order -> + SimpleOrder( + order.id, + order.ouid, + order.uuid, + order.price, + order.quantity, + order.matchConstraint, + order.orderType, + order.direction, + order.filledQuantity, + null, + null, + null + ) + }?.filter { order -> order.matchConstraint == MatchConstraint.GTC + }?.forEach { order -> putGtcInQueue(order) } + orderCounter.set(persistentOrderBook.lastOrder?.id?:0) + } + + private fun logCurrentState(){ + logger.info("******************** ${pair.leftSideName}-${pair.rightSideName} ********************") + logger.info("** askOrders size: ${askOrders.entriesList().size}") + logger.info("** bidOrders size: ${bidOrders.entriesList().size}") + logger.info("** orders size: ${orders.size}") + logger.info("** bestAskOrder: ${bestAskOrder?.ouid}") + logger.info("** bestBidOrder: ${bestBidOrder?.ouid}") + logger.info("** lastOrder: ${lastOrder?.ouid}") + logger.info("*********************************************************") + println() + } +} \ No newline at end of file diff --git a/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/eventh/EventDispatcher.kt b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/eventh/EventDispatcher.kt new file mode 100644 index 000000000..bb620a5fd --- /dev/null +++ b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/eventh/EventDispatcher.kt @@ -0,0 +1,50 @@ +package co.nilin.opex.matching.core.eventh + +import co.nilin.opex.matching.core.eventh.events.CoreEvent +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.launch +import java.util.* +import java.util.concurrent.Executors +import kotlin.coroutines.suspendCoroutine + +object EventDispatcher { + + private val executorService = Executors.newFixedThreadPool(10).asCoroutineDispatcher() + private val eventsHandler = mutableMapOf, MutableList>>() + + @JvmStatic + inline fun register(noinline lambda: (T) -> Unit) = register(T::class.java, lambda) + + @JvmStatic + fun register(type: Class, lambda: (T) -> Unit) = register(type, EventListener(lambda)) + + @JvmStatic + fun register(type: Class, listener: EventListener) { + eventsHandler.getOrPut(type, { LinkedList() }).add(listener) + } + + + fun emit(event: CoreEvent) = CoroutineScope(executorService).launch { + var type: Class<*>? = event::class.java + while (type != null) { + eventsHandler[type]?.forEach { eventsHandler -> + suspendCoroutine { + kotlin.runCatching { + eventsHandler(event) + } + } + } + type = type.superclass + } + } + + + open class EventListener( + val lambda: (T) -> Unit + ) { + operator fun invoke(event: Any) { + lambda(event as T) + } + } +} \ No newline at end of file diff --git a/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/eventh/events/CancelOrderEvent.kt b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/eventh/events/CancelOrderEvent.kt new file mode 100644 index 000000000..300960035 --- /dev/null +++ b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/eventh/events/CancelOrderEvent.kt @@ -0,0 +1,48 @@ +package co.nilin.opex.matching.core.eventh.events + +import co.nilin.opex.matching.core.model.MatchConstraint +import co.nilin.opex.matching.core.model.OrderDirection +import co.nilin.opex.matching.core.model.OrderType + +class CancelOrderEvent() : CoreEvent(), OneOrderEvent{ + var ouid: String = "" + var uuid: String = "" + var orderId: Long = 0 + var price: Long = 0 + var quantity: Long = 0 + var remainedQuantity: Long = 0 + var direction: OrderDirection = OrderDirection.ASK + var matchConstraint: MatchConstraint = MatchConstraint.GTC + var orderType: OrderType = OrderType.LIMIT_ORDER + + constructor(ouid: String, + uuid: String, + orderId: Long, + pair: co.nilin.opex.matching.core.model.Pair, + price: Long, + quantity: Long, + remainedQuantity: Long, + direction: OrderDirection, + matchConstraint: MatchConstraint, + orderType: OrderType) + : this() { + this.ouid = ouid + this.uuid = uuid + this.orderId = orderId + this.pair = pair + this.price = price + this.quantity = quantity + this.remainedQuantity = remainedQuantity + this.direction = direction + this.matchConstraint = matchConstraint + this.orderType = orderType + } + + override fun ouid(): String { + return ouid + } + + override fun uuid(): String { + return uuid + } +} \ No newline at end of file diff --git a/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/eventh/events/CoreEvent.kt b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/eventh/events/CoreEvent.kt new file mode 100644 index 000000000..44dff675f --- /dev/null +++ b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/eventh/events/CoreEvent.kt @@ -0,0 +1,9 @@ +package co.nilin.opex.matching.core.eventh.events + +import co.nilin.opex.matching.core.model.Pair +import java.time.LocalDateTime + +open class CoreEvent { + lateinit var pair: Pair + var eventDate: LocalDateTime = LocalDateTime.now() +} \ No newline at end of file diff --git a/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/eventh/events/CreateOrderEvent.kt b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/eventh/events/CreateOrderEvent.kt new file mode 100644 index 000000000..25a0896fe --- /dev/null +++ b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/eventh/events/CreateOrderEvent.kt @@ -0,0 +1,48 @@ +package co.nilin.opex.matching.core.eventh.events + +import co.nilin.opex.matching.core.model.MatchConstraint +import co.nilin.opex.matching.core.model.OrderDirection +import co.nilin.opex.matching.core.model.OrderType + +class CreateOrderEvent() : CoreEvent() , OneOrderEvent{ + var ouid: String = "" + var uuid: String = "" + var orderId: Long = 0 + var price: Long = 0 + var quantity: Long = 0 + var remainedQuantity: Long = 0 + var direction: OrderDirection = OrderDirection.ASK + var matchConstraint: MatchConstraint = MatchConstraint.GTC + var orderType: OrderType = OrderType.LIMIT_ORDER + + constructor(ouid: String, + uuid: String, + orderId: Long, + pair: co.nilin.opex.matching.core.model.Pair, + price: Long, + quantity: Long, + remainedQuantity: Long, + direction: OrderDirection, + matchConstraint: MatchConstraint, + orderType: OrderType) + : this() { + this.ouid = ouid + this.uuid = uuid + this.orderId = orderId + this.pair = pair + this.price = price + this.quantity = quantity + this.remainedQuantity = remainedQuantity + this.direction = direction + this.matchConstraint = matchConstraint + this.orderType = orderType + } + + override fun ouid(): String { + return ouid + } + + override fun uuid(): String { + return uuid + } +} \ No newline at end of file diff --git a/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/eventh/events/OneOrderEvent.kt b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/eventh/events/OneOrderEvent.kt new file mode 100644 index 000000000..21ab23dbd --- /dev/null +++ b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/eventh/events/OneOrderEvent.kt @@ -0,0 +1,6 @@ +package co.nilin.opex.matching.core.eventh.events + +interface OneOrderEvent { + fun ouid(): String + fun uuid(): String +} \ No newline at end of file diff --git a/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/eventh/events/RejectOrderEvent.kt b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/eventh/events/RejectOrderEvent.kt new file mode 100644 index 000000000..39cd75e5c --- /dev/null +++ b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/eventh/events/RejectOrderEvent.kt @@ -0,0 +1,70 @@ +package co.nilin.opex.matching.core.eventh.events + +import co.nilin.opex.matching.core.inout.RejectReason +import co.nilin.opex.matching.core.inout.RequestedOperation +import co.nilin.opex.matching.core.model.MatchConstraint +import co.nilin.opex.matching.core.model.OrderDirection +import co.nilin.opex.matching.core.model.OrderType + +class RejectOrderEvent(): CoreEvent(), OneOrderEvent { + var ouid: String = "" + var uuid: String = "" + var orderId: Long? = null + var price: Long? = null + var quantity: Long? = null + var direction: OrderDirection? = null + var matchConstraint: MatchConstraint? = null + var orderType: OrderType? = null + var requestedOperation: RequestedOperation = RequestedOperation.PLACE_ORDER + var reason: RejectReason? = null + + constructor(ouid: String, + uuid: String, + pair: co.nilin.opex.matching.core.model.Pair, + price: Long, + quantity: Long, + direction: OrderDirection, + matchConstraint: MatchConstraint, + orderType: OrderType, + requestedOperation: RequestedOperation, + reason: RejectReason?) + : this(ouid, uuid, null, pair, price, quantity, direction, matchConstraint, orderType, requestedOperation, reason) + constructor(ouid: String, + uuid: String, + orderId: Long, + pair: co.nilin.opex.matching.core.model.Pair, + requestedOperation: RequestedOperation, + reason: RejectReason?) + : this(ouid, uuid, orderId, pair, null, null, null, null, null, requestedOperation, reason) + constructor(ouid: String, + uuid: String, + orderId: Long?, + pair: co.nilin.opex.matching.core.model.Pair, + price: Long?, + quantity: Long?, + direction: OrderDirection?, + matchConstraint: MatchConstraint?, + orderType: OrderType?, + requestedOperation: RequestedOperation, + reason: RejectReason?) + : this(){ + this.ouid = ouid + this.uuid = uuid + this.orderId = orderId + this.pair = pair + this.price = price + this.quantity = quantity + this.direction = direction + this.matchConstraint = matchConstraint + this.orderType = orderType + this.requestedOperation = requestedOperation + this.reason = reason + } + override fun ouid(): String { + return ouid + } + + override fun uuid(): String { + return uuid + } +} \ No newline at end of file diff --git a/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/eventh/events/SubmitOrderEvent.kt b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/eventh/events/SubmitOrderEvent.kt new file mode 100644 index 000000000..8c6b7518e --- /dev/null +++ b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/eventh/events/SubmitOrderEvent.kt @@ -0,0 +1,48 @@ +package co.nilin.opex.matching.core.eventh.events + +import co.nilin.opex.matching.core.model.MatchConstraint +import co.nilin.opex.matching.core.model.OrderDirection +import co.nilin.opex.matching.core.model.OrderType + +class SubmitOrderEvent() : CoreEvent() , OneOrderEvent{ + var ouid: String = "" + var uuid: String = "" + var orderId: Long? = null + var price: Long = 0 + var quantity: Long = 0 + var remainedQuantity: Long = 0 + var direction: OrderDirection = OrderDirection.ASK + var matchConstraint: MatchConstraint = MatchConstraint.GTC + var orderType: OrderType = OrderType.LIMIT_ORDER + + constructor(ouid: String, + uuid: String, + orderId: Long?, + pair: co.nilin.opex.matching.core.model.Pair, + price: Long, + quantity: Long, + remainedQuantity: Long, + direction: OrderDirection, + matchConstraint: MatchConstraint, + orderType: OrderType) + : this() { + this.ouid = ouid + this.uuid = uuid + this.orderId = orderId + this.pair = pair + this.price = price + this.quantity = quantity + this.remainedQuantity = remainedQuantity + this.direction = direction + this.matchConstraint = matchConstraint + this.orderType = orderType + } + + override fun ouid(): String { + return ouid + } + + override fun uuid(): String { + return uuid + } +} \ No newline at end of file diff --git a/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/eventh/events/TradeEvent.kt b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/eventh/events/TradeEvent.kt new file mode 100644 index 000000000..7408f7ab0 --- /dev/null +++ b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/eventh/events/TradeEvent.kt @@ -0,0 +1,57 @@ +package co.nilin.opex.matching.core.eventh.events + +import co.nilin.opex.matching.core.model.OrderDirection + +class TradeEvent() : CoreEvent() { + var tradeId: Long = 0 + var takerOuid: String = "" + var takerUuid: String = "" + var takerOrderId: Long = 0 + var takerDirection: OrderDirection = OrderDirection.ASK + var takerPrice: Long = 0 + var takerRemainedQuantity: Long = 0 + var makerOuid: String = "" + var makerUuid: String = "" + var makerOrderId: Long = 0 + var makerDirection: OrderDirection = OrderDirection.BID + var makerPrice: Long = 0 + var makerRemainedQuantity: Long = 0 + var matchedQuantity: Long = 0 + + + constructor(tradeId: Long, + pair: co.nilin.opex.matching.core.model.Pair, + takerOuid: String, + takerUuid: String, + takerOrderId: Long, + takerDirection: OrderDirection, + takerPrice: Long, + takerRemainedQuantity: Long, + makerOuid: String, + makerUuid: String, + makerOrderId: Long, + makerDirection: OrderDirection, + makerPrice: Long, + makerRemainedQuantity: Long, + matchedQuantity: Long + ) + : this() { + this.tradeId = tradeId + this.takerOuid = takerOuid + this.takerUuid = takerUuid + this.pair = pair + this.takerOrderId = takerOrderId + this.takerPrice = takerPrice + this.takerDirection = takerDirection + this.takerRemainedQuantity = takerRemainedQuantity + + this.makerOuid = makerOuid + this.makerUuid = makerUuid + this.makerOrderId = makerOrderId + this.makerPrice = makerPrice + this.makerDirection = makerDirection + this.makerRemainedQuantity = makerRemainedQuantity + + this.matchedQuantity = matchedQuantity + } +} \ No newline at end of file diff --git a/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/eventh/events/UpdatedOrderEvent.kt b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/eventh/events/UpdatedOrderEvent.kt new file mode 100644 index 000000000..744186d0a --- /dev/null +++ b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/eventh/events/UpdatedOrderEvent.kt @@ -0,0 +1,54 @@ +package co.nilin.opex.matching.core.eventh.events + +import co.nilin.opex.matching.core.model.MatchConstraint +import co.nilin.opex.matching.core.model.OrderDirection +import co.nilin.opex.matching.core.model.OrderType + +class UpdatedOrderEvent(): CoreEvent() , OneOrderEvent{ + var ouid: String = "" + var uuid: String = "" + var orderId: Long = 0 + var oldPrice: Long = 0 + var oldQuantity: Long = 0 + var price: Long = 0 + var quantity: Long = 0 + var remainedQuantity: Long = 0 + var direction: OrderDirection = OrderDirection.ASK + var matchConstraint: MatchConstraint = MatchConstraint.GTC + var orderType: OrderType = OrderType.LIMIT_ORDER + + constructor(ouid: String, + uuid: String, + orderId: Long, + pair: co.nilin.opex.matching.core.model.Pair, + oldPrice: Long, + oldQuantity: Long, + price: Long, + quantity: Long, + remainedQuantity: Long, + direction: OrderDirection, + matchConstraint: MatchConstraint, + orderType: OrderType) + : this(){ + this.ouid = ouid + this.uuid = uuid + this.orderId = orderId + this.pair = pair + this.oldPrice = oldPrice + this.oldQuantity = oldQuantity + this.price = price + this.quantity = quantity + this.remainedQuantity = remainedQuantity + this.direction = direction + this.matchConstraint = matchConstraint + this.orderType = orderType + } + + override fun ouid(): String { + return ouid + } + + override fun uuid(): String { + return uuid + } +} \ No newline at end of file diff --git a/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/factory/OrderBookFactory.kt b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/factory/OrderBookFactory.kt new file mode 100644 index 000000000..ec504ecf5 --- /dev/null +++ b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/factory/OrderBookFactory.kt @@ -0,0 +1,20 @@ +package co.nilin.opex.matching.core.factory + +import co.nilin.opex.matching.core.engine.SimpleOrderBook +import co.nilin.opex.matching.core.model.OrderBook +import co.nilin.opex.matching.core.model.PersistentOrderBook +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +object OrderBookFactory { + fun createOrderBook(pair: co.nilin.opex.matching.core.model.Pair): OrderBook { + return SimpleOrderBook(pair, false) + } + + fun createOrderBook(persistentOrderBook: PersistentOrderBook): OrderBook { + val orderBook = SimpleOrderBook(persistentOrderBook.pair, true) + orderBook.rebuild(persistentOrderBook) + orderBook.stopReplayMode() + return orderBook + } +} \ No newline at end of file diff --git a/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/inout/OrderCancelCommand.kt b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/inout/OrderCancelCommand.kt new file mode 100644 index 000000000..a62e0d4a7 --- /dev/null +++ b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/inout/OrderCancelCommand.kt @@ -0,0 +1,5 @@ +package co.nilin.opex.matching.core.inout + +import co.nilin.opex.matching.core.model.Pair + +class OrderCancelCommand(val ouid: String, val uuid: String, val orderId: Long, val pair: Pair) \ No newline at end of file diff --git a/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/inout/OrderCreateCommand.kt b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/inout/OrderCreateCommand.kt new file mode 100644 index 000000000..ae6fdc9e7 --- /dev/null +++ b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/inout/OrderCreateCommand.kt @@ -0,0 +1,16 @@ +package co.nilin.opex.matching.core.inout + +import co.nilin.opex.matching.core.model.MatchConstraint +import co.nilin.opex.matching.core.model.OrderDirection +import co.nilin.opex.matching.core.model.OrderType +import co.nilin.opex.matching.core.model.Pair + + +data class OrderCreateCommand(val ouid: String, + val uuid: String, + val pair: Pair, + val price: Long, + val quantity: Long, + val direction: OrderDirection, + val matchConstraint: MatchConstraint, + val orderType: OrderType) \ No newline at end of file diff --git a/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/inout/OrderEditCommand.kt b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/inout/OrderEditCommand.kt new file mode 100644 index 000000000..beb7697eb --- /dev/null +++ b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/inout/OrderEditCommand.kt @@ -0,0 +1,12 @@ +package co.nilin.opex.matching.core.inout + +import co.nilin.opex.matching.core.model.Pair + +data class OrderEditCommand( + val ouid: String, + val uuid: String, + val orderId: Long, + val pair: Pair, + val price: Long, + val quantity: Long +) \ No newline at end of file diff --git a/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/inout/RejectReason.kt b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/inout/RejectReason.kt new file mode 100644 index 000000000..33aa2caa8 --- /dev/null +++ b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/inout/RejectReason.kt @@ -0,0 +1,5 @@ +package co.nilin.opex.matching.core.inout + +enum class RejectReason { + ORDER_TYPE_NOT_MATCHED_MATCHC, ORDER_NOT_FOUND, OPERATION_NOT_MATCHED_MATCHC +} \ No newline at end of file diff --git a/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/inout/RequestedOperation.kt b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/inout/RequestedOperation.kt new file mode 100644 index 000000000..fe3d61268 --- /dev/null +++ b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/inout/RequestedOperation.kt @@ -0,0 +1,5 @@ +package co.nilin.opex.matching.core.inout + +enum class RequestedOperation { + PLACE_ORDER, CANCEL_ORDER, EDIT_ORDER +} \ No newline at end of file diff --git a/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/model/Order.kt b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/model/Order.kt new file mode 100644 index 000000000..cac761570 --- /dev/null +++ b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/model/Order.kt @@ -0,0 +1,6 @@ +package co.nilin.opex.matching.core.model + +interface Order{ + fun id():Long? + fun persistent():PersistentOrder +} diff --git a/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/model/OrderBook.kt b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/model/OrderBook.kt new file mode 100644 index 000000000..1de0a0cf8 --- /dev/null +++ b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/model/OrderBook.kt @@ -0,0 +1,16 @@ +package co.nilin.opex.matching.core.model + +import co.nilin.opex.matching.core.inout.OrderCancelCommand +import co.nilin.opex.matching.core.inout.OrderEditCommand +import co.nilin.opex.matching.core.inout.OrderCreateCommand + +interface OrderBook { + fun pair(): Pair + fun startReplayMode() + fun stopReplayMode() + fun lastOrder(): Order? + fun handleNewOrderCommand(orderCommand: OrderCreateCommand): Order? + fun handleCancelCommand(orderCommand: OrderCancelCommand) + fun handleEditCommand(orderCommand: OrderEditCommand): Order? + fun persistent(): PersistentOrderBook +} \ No newline at end of file diff --git a/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/model/OrderMetaData.kt b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/model/OrderMetaData.kt new file mode 100644 index 000000000..379dcf2fa --- /dev/null +++ b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/model/OrderMetaData.kt @@ -0,0 +1,26 @@ +package co.nilin.opex.matching.core.model + +enum class OrderDirection { + ASK, BID +} + +enum class MatchConstraint { + GTC, + + // Immediate or Cancel - equivalent to strict-risk market order + IOC, // without price cap + + // with price cap + IOC_BUDGET, // with total amount cap + + // with total amount cap + // Fill or Kill - execute immediately completely or not at all + FOK, // without price cap + + // with price cap + FOK_BUDGET +} + +enum class OrderType { + LIMIT_ORDER, MARKET_ORDER +} diff --git a/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/model/Pair.kt b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/model/Pair.kt new file mode 100644 index 000000000..622d8e3fd --- /dev/null +++ b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/model/Pair.kt @@ -0,0 +1,16 @@ +package co.nilin.opex.matching.core.model + +class Pair() { + lateinit var leftSideName: String + lateinit var rightSideName: String + + constructor(leftSideName: String, rightSideName: String) : this() { + this.leftSideName = leftSideName + this.rightSideName = rightSideName + } + + override fun toString(): String { + return "${leftSideName}_$rightSideName" + } + +} \ No newline at end of file diff --git a/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/model/PersistentOrder.kt b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/model/PersistentOrder.kt new file mode 100644 index 000000000..a5b19a5e1 --- /dev/null +++ b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/model/PersistentOrder.kt @@ -0,0 +1,39 @@ +package co.nilin.opex.matching.core.model + +class PersistentOrder { + var id: Long = 0 + var ouid: String = "" + var uuid: String = "" + var price: Long = 0 + var quantity: Long = 0 + lateinit var matchConstraint: MatchConstraint + lateinit var orderType: OrderType + lateinit var direction: OrderDirection + var filledQuantity: Long = 0 + + constructor() { + + } + + constructor( + id: Long, + ouid: String, + uuid: String, + price: Long, + quantity: Long, + matchConstraint: MatchConstraint, + orderType: OrderType, + direction: OrderDirection, + filledQuantity: Long + ) { + this.id = id + this.ouid = ouid + this.uuid = uuid + this.price = price + this.quantity = quantity + this.matchConstraint = matchConstraint + this.orderType = orderType + this.direction = direction + this.filledQuantity = filledQuantity + } +} \ No newline at end of file diff --git a/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/model/PersistentOrderBook.kt b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/model/PersistentOrderBook.kt new file mode 100644 index 000000000..48c6e6c8e --- /dev/null +++ b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/model/PersistentOrderBook.kt @@ -0,0 +1,16 @@ +package co.nilin.opex.matching.core.model + +class PersistentOrderBook { + + lateinit var pair: Pair + var lastOrder: PersistentOrder? = null + var orders : List? = emptyList() + + constructor(){ + } + + constructor(pair: Pair){ + this.pair = pair + } + +} \ No newline at end of file diff --git a/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/spi/OrderBookPersister.kt b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/spi/OrderBookPersister.kt new file mode 100644 index 000000000..f0adff00a --- /dev/null +++ b/MatchingEngine/matching-core/src/main/kotlin/co/nilin/opex/matching/core/spi/OrderBookPersister.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.matching.core.spi + +import co.nilin.opex.matching.core.model.PersistentOrderBook + +interface OrderBookPersister { + suspend fun storeLastState(orderBook: PersistentOrderBook) + suspend fun loadLastState(symbol: String): PersistentOrderBook? +} \ No newline at end of file diff --git a/MatchingEngine/matching-core/src/test/kotlin/co/nilin/opex/matching/core/engine/SimpleOrderBookUnitTest.kt b/MatchingEngine/matching-core/src/test/kotlin/co/nilin/opex/matching/core/engine/SimpleOrderBookUnitTest.kt new file mode 100644 index 000000000..02130a642 --- /dev/null +++ b/MatchingEngine/matching-core/src/test/kotlin/co/nilin/opex/matching/core/engine/SimpleOrderBookUnitTest.kt @@ -0,0 +1,320 @@ +package co.nilin.opex.matching.core.engine + +import co.nilin.opex.matching.core.inout.OrderCancelCommand +import co.nilin.opex.matching.core.inout.OrderCreateCommand +import co.nilin.opex.matching.core.inout.OrderEditCommand +import co.nilin.opex.matching.core.model.MatchConstraint +import co.nilin.opex.matching.core.model.OrderDirection +import co.nilin.opex.matching.core.model.OrderType +import kotlinx.coroutines.Dispatchers +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import java.util.* + +class SimpleOrderBookUnitTest { + val pair = co.nilin.opex.matching.core.model.Pair("BTC", "USDT") + val ETH_BTC_PAIR = co.nilin.opex.matching.core.model.Pair("ETH", "BTC") + val uuid = UUID.randomUUID().toString() + + @Test + fun givenEmptyOrderBook_whenGtcBidLimitOrderCreated_then1BucketWithSize1() { + //given + val orderBook = SimpleOrderBook(pair, false) + //when + val order = orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, pair, 1, 1, OrderDirection.BID, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) + //then + Assertions.assertEquals(orderBook.bidOrders.entriesList().size , 1) + Assertions.assertEquals(orderBook.bestBidOrder, order) + Dispatchers.Default + } + + @Test + fun givenOrderBookWithBidOrders_whenGtcBidLimitOrderWithSamePriceCreated_then() { + //given + val orderBook = SimpleOrderBook(pair, false) + orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, pair, 1, 1, OrderDirection.BID, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) + val bestBidOrder = orderBook.bestBidOrder + //when + val order: SimpleOrderBook.SimpleOrder = orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, pair, 1, 1, OrderDirection.BID, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) as SimpleOrderBook.SimpleOrder + //then + Assertions.assertEquals(orderBook.bidOrders.entriesList().size , 1) + Assertions.assertEquals(orderBook.bestBidOrder, bestBidOrder) + Assertions.assertEquals(bestBidOrder!!.worse, order) + Assertions.assertEquals(order.better, bestBidOrder) + Assertions.assertEquals(orderBook.bidOrders.get(order.price).lastOrder, order) + Assertions.assertEquals(orderBook.bidOrders.get(order.price).totalQuantity, 2) + Assertions.assertEquals(orderBook.bidOrders.get(order.price).ordersCount, 2) + } + + @Test + fun givenOrderBookWithBidOrders_whenGtcBidLimitOrderWithLowerPriceCreated_thenBestOrderNotChange() { + //given + val orderBook = SimpleOrderBook(pair, false) + orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, pair, 2, 1, OrderDirection.BID, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) + val bestBidOrder = orderBook.bestBidOrder + //when + val order: SimpleOrderBook.SimpleOrder = orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, pair, 1, 1, OrderDirection.BID, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) as SimpleOrderBook.SimpleOrder + //then + Assertions.assertEquals(orderBook.bidOrders.entriesList().size , 2) + Assertions.assertEquals(orderBook.bestBidOrder, bestBidOrder) + Assertions.assertEquals(bestBidOrder!!.worse, order) + Assertions.assertEquals(order.better, bestBidOrder) + Assertions.assertEquals(orderBook.bidOrders.get(order.price).lastOrder, order) + Assertions.assertEquals(orderBook.bidOrders.get(order.price).totalQuantity, 1) + Assertions.assertEquals(orderBook.bidOrders.get(order.price).ordersCount, 1) + } + + @Test + fun givenOrderBookWithBidOrders_whenGtcBidLimitOrderWithHigherPriceCreated_thenBestOrderChanged() { + //given + val orderBook = SimpleOrderBook(pair, false) + orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, pair, 1, 1, OrderDirection.BID, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) + val bestBidOrder = orderBook.bestBidOrder + //when + val order: SimpleOrderBook.SimpleOrder = orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, pair, 2, 1, OrderDirection.BID, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) as SimpleOrderBook.SimpleOrder + //then + Assertions.assertEquals(orderBook.bidOrders.entriesList().size , 2) + Assertions.assertEquals(orderBook.bestBidOrder, order) + Assertions.assertEquals(bestBidOrder!!.better, order) + Assertions.assertEquals(order.worse, bestBidOrder) + Assertions.assertEquals(orderBook.bidOrders.get(order.price).lastOrder, order) + Assertions.assertEquals(orderBook.bidOrders.get(order.price).totalQuantity, 1) + Assertions.assertEquals(orderBook.bidOrders.get(order.price).ordersCount, 1) + } + + @Test + fun givenOrderBookWithBidOrders_whenGtcAskLimitOrderWithSamePriceCreated_thenInstantMatch() { + //given + val orderBook = SimpleOrderBook(pair, false) + orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, pair, 1, 1, OrderDirection.BID, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) + //when + orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, pair, 1, 1, OrderDirection.ASK, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) as SimpleOrderBook.SimpleOrder + //then + Assertions.assertEquals(orderBook.bidOrders.entriesList().size , 0) + Assertions.assertEquals(orderBook.askOrders.entriesList().size , 0) + Assertions.assertNull(orderBook.bestBidOrder) + Assertions.assertNull(orderBook.bestAskOrder) + } + + @Test + fun givenOrderBookWithBidOrders_whenGtcAskLimitOrderWithNotMatchPriceCreated_thenAddToQueue() { + //given + val orderBook = SimpleOrderBook(pair, false) + orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, pair, 2, 1, OrderDirection.BID, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) + orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, pair, 1, 1, OrderDirection.BID, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) + //when + val order: SimpleOrderBook.SimpleOrder = orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, pair, 3, 1, OrderDirection.ASK, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) as SimpleOrderBook.SimpleOrder + //then + Assertions.assertEquals(orderBook.bidOrders.entriesList().size , 2) + Assertions.assertEquals(orderBook.askOrders.entriesList().size , 1) + Assertions.assertNotNull(orderBook.bestBidOrder) + Assertions.assertEquals(orderBook.bestAskOrder, order) + } + + @Test + fun givenOrderBookWithBidAndAskOrders_whenGtcAskLimitOrderWithMatchPriceGreaterQuantityCreated_thenAddToQueue() { + //given + val orderBook = SimpleOrderBook(pair, false) + orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, pair, 2, 1, OrderDirection.BID, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) + orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, pair, 1, 1, OrderDirection.BID, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) + orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, pair, 3, 1, OrderDirection.ASK, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) + //when + val order: SimpleOrderBook.SimpleOrder = orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, pair, 1, 3, OrderDirection.ASK, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) as SimpleOrderBook.SimpleOrder + //then + Assertions.assertEquals(orderBook.bidOrders.entriesList().size , 0) + Assertions.assertEquals(orderBook.askOrders.entriesList().size , 2) + Assertions.assertNull(orderBook.bestBidOrder) + Assertions.assertEquals(orderBook.bestAskOrder, order) + } + + @Test + fun givenOrderBook_whenCancelBestBidOrder_thenBestBidOrderChange(){ + //given + val orderBook = SimpleOrderBook(pair, false) + val firstOrderId = UUID.randomUUID().toString() + val secondOrderId = UUID.randomUUID().toString() + + val firstOrder = orderBook.handleNewOrderCommand(OrderCreateCommand(firstOrderId, uuid, pair, 2, 1, OrderDirection.BID, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) + val lastOrder = orderBook.handleNewOrderCommand(OrderCreateCommand(secondOrderId, uuid, pair, 1, 1, OrderDirection.BID, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) + //when + orderBook.handleCancelCommand(OrderCancelCommand(firstOrderId, uuid, firstOrder!!.id()!!, pair)) + //then + Assertions.assertEquals(orderBook.bestBidOrder, lastOrder) + Assertions.assertEquals(orderBook.bidOrders.entriesList().size, 1) + } + + @Test + fun givenOrderBookWithMoreBids_whenCancelBestBidOrder_thenBestBidOrderChange(){ + //given + val orderBook = SimpleOrderBook(pair, false) + val firstOrderId = UUID.randomUUID().toString() + val secondOrderId = UUID.randomUUID().toString() + + val firstOrder = orderBook.handleNewOrderCommand(OrderCreateCommand(firstOrderId, uuid, pair, 2, 1, OrderDirection.BID, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) + val secondOrder = orderBook.handleNewOrderCommand(OrderCreateCommand(secondOrderId, uuid, pair, 2, 3, OrderDirection.BID, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) + orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, pair, 1, 1, OrderDirection.BID, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) + //when + orderBook.handleCancelCommand(OrderCancelCommand(firstOrderId, uuid, firstOrder!!.id()!!, pair)) + //then + Assertions.assertEquals(orderBook.bestBidOrder, secondOrder) + Assertions.assertEquals(orderBook.bidOrders.entriesList().size, 2) + } + + @Test + fun givenOrderBookWithMoreBids_whenCancelABidOrder_thenBestBidOrderNotChange(){ + //given + val orderBook = SimpleOrderBook(pair, false) + val firstOrderId = UUID.randomUUID().toString() + val secondOrderId = UUID.randomUUID().toString() + + val firstOrder = orderBook.handleNewOrderCommand(OrderCreateCommand(firstOrderId, uuid, pair, 2, 1, OrderDirection.BID, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) + val secondOrder = orderBook.handleNewOrderCommand(OrderCreateCommand(secondOrderId, uuid, pair, 2, 3, OrderDirection.BID, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) + orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, pair, 1, 1, OrderDirection.BID, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) + //when + orderBook.handleCancelCommand(OrderCancelCommand(secondOrderId, uuid, secondOrder!!.id()!!, pair)) + //then + Assertions.assertEquals(orderBook.bestBidOrder, firstOrder) + Assertions.assertEquals(orderBook.bidOrders.entriesList().size, 2) + } + + + @Test + fun givenOrderBookWithMoreBids_whenEditABidOrder_thenBestBidOrderChange(){ + //given + val orderBook = SimpleOrderBook(pair, false) + orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, pair, 2, 1, OrderDirection.BID, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) + val secondOrder = orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, pair, 2, 3, OrderDirection.BID, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) + orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, pair, 1, 1, OrderDirection.BID, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) + //when + val order = orderBook.handleEditCommand(OrderEditCommand(UUID.randomUUID().toString(), uuid, secondOrder!!.id()!!, pair, 3, 2)) + //then + Assertions.assertEquals(secondOrder.id(), order?.id()) + Assertions.assertEquals(orderBook.bestBidOrder, order) + Assertions.assertEquals(orderBook.bidOrders.entriesList().size, 3) + } + + @Test + fun givenOrderBookWithBidAndAskOrders_whenEditABidOrder_thenRefill() { + //given + val orderBook = SimpleOrderBook(pair, false) + orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, pair, 2, 1, OrderDirection.BID, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) + val secondBid = orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, pair, 1, 1, OrderDirection.BID, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) + orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, pair, 3, 1, OrderDirection.ASK, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) + //when + val order: SimpleOrderBook.SimpleOrder = orderBook.handleEditCommand(OrderEditCommand(UUID.randomUUID().toString(), uuid, secondBid!!.id()!!, pair, 3, 3)) as SimpleOrderBook.SimpleOrder + //then + Assertions.assertEquals(2 , orderBook.bidOrders.entriesList().size ) + Assertions.assertEquals(0, orderBook.askOrders.entriesList().size ) + Assertions.assertEquals(orderBook.bestBidOrder, order) + Assertions.assertNull(orderBook.bestAskOrder) + } + + @Test + fun givenEmptyOrderBook_whenGtcBidMarketOrderCreated_thenRejected() { + //given + val orderBook = SimpleOrderBook(pair, false) + //when + + val order = orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, pair, 1, 1, OrderDirection.BID, MatchConstraint.GTC, OrderType.MARKET_ORDER)) + //then + Assertions.assertEquals(orderBook.bidOrders.entriesList().size , 0) + Assertions.assertNull(orderBook.bestBidOrder) + Assertions.assertNull(order) + } + + @Test + fun givenEmptyOrderBook_whenIocBidMarketOrderCreated_thenNoOrderCreated() { + //given + val orderBook = SimpleOrderBook(pair, false) + //when + + val order = orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, pair, 1, 1, OrderDirection.BID, MatchConstraint.GTC, OrderType.MARKET_ORDER)) + //then + Assertions.assertEquals(orderBook.bidOrders.entriesList().size , 0) + Assertions.assertNull(orderBook.bestBidOrder) + Assertions.assertNull(order) + } + + @Test + fun givenOrderBookWithBidAndAskOrders_whenIocAskMarketOrderWithGreaterQuantityCreated_thenPartiallyFilled() { + //given + val orderBook = SimpleOrderBook(pair, false) + orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, pair, 2, 1, OrderDirection.BID, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) + orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, pair, 1, 1, OrderDirection.BID, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) + orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, pair, 3, 1, OrderDirection.ASK, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) + val bestAskOrder = orderBook.bestAskOrder + //when + val order: SimpleOrderBook.SimpleOrder = orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, pair, 0, 3, OrderDirection.ASK, MatchConstraint.IOC, OrderType.MARKET_ORDER)) as SimpleOrderBook.SimpleOrder + //then + Assertions.assertEquals(2, order.filledQuantity) + Assertions.assertEquals(orderBook.bidOrders.entriesList().size , 0) + Assertions.assertEquals(orderBook.askOrders.entriesList().size , 1) + Assertions.assertNull(orderBook.bestBidOrder) + Assertions.assertEquals(orderBook.bestAskOrder, bestAskOrder) + } + + @Test + fun givenOrderBookWithBidAndAskOrders_whenIocAskLimitOrderWithHigherPriceAndGreaterQuantityCreated_thenNotFilled() { + //given + val orderBook = SimpleOrderBook(pair, false) + orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, pair, 2, 1, OrderDirection.BID, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) + orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, pair, 1, 1, OrderDirection.BID, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) + orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, pair, 3, 1, OrderDirection.ASK, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) + val bestAskOrder = orderBook.bestAskOrder + val bestBidOrder = orderBook.bestBidOrder + //when + val order: SimpleOrderBook.SimpleOrder = orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, pair, 3, 3, OrderDirection.ASK, MatchConstraint.IOC, OrderType.LIMIT_ORDER)) as SimpleOrderBook.SimpleOrder + //then + Assertions.assertEquals(0, order.filledQuantity) + Assertions.assertEquals(orderBook.bidOrders.entriesList().size , 2) + Assertions.assertEquals(orderBook.askOrders.entriesList().size , 1) + Assertions.assertEquals(bestBidOrder, orderBook.bestBidOrder) + Assertions.assertEquals(bestAskOrder, orderBook.bestAskOrder) + } + + @Test + fun whenSample1SequenceOfOrdersOccurs_thenAllSuccess() { + + val orderBook = SimpleOrderBook(ETH_BTC_PAIR, false) + orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, ETH_BTC_PAIR, 5000000, 10000, OrderDirection.BID, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) as SimpleOrderBook.SimpleOrder + Assertions.assertNotNull(orderBook.bestBidOrder) + Assertions.assertEquals(1, orderBook.bidOrders.entriesList().size) + Assertions.assertEquals(1, orderBook.orders.size) + + orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, ETH_BTC_PAIR, 4900000, 20000, OrderDirection.ASK, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) as SimpleOrderBook.SimpleOrder + Assertions.assertNull(orderBook.bestBidOrder) + Assertions.assertNotNull(orderBook.bestAskOrder) + Assertions.assertEquals(0, orderBook.bidOrders.entriesList().size) + Assertions.assertEquals(1, orderBook.askOrders.entriesList().size) + Assertions.assertEquals(1, orderBook.orders.size) + + orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, ETH_BTC_PAIR, 4800000, 10000, OrderDirection.ASK, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) as SimpleOrderBook.SimpleOrder + Assertions.assertNull(orderBook.bestBidOrder) + Assertions.assertNotNull(orderBook.bestAskOrder) + Assertions.assertEquals(0, orderBook.bidOrders.entriesList().size) + Assertions.assertEquals(2, orderBook.askOrders.entriesList().size) + Assertions.assertEquals(2, orderBook.orders.size) + + orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, ETH_BTC_PAIR, 4850000, 20000, OrderDirection.BID, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) as SimpleOrderBook.SimpleOrder + Assertions.assertEquals(1, orderBook.bidOrders.entriesList().size) + Assertions.assertEquals(1, orderBook.askOrders.entriesList().size) + Assertions.assertEquals(2, orderBook.orders.size) + Assertions.assertNotNull(orderBook.bestBidOrder) + Assertions.assertNotNull(orderBook.bestAskOrder) + + orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, ETH_BTC_PAIR, 4850100, 10000, OrderDirection.ASK, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) as SimpleOrderBook.SimpleOrder + Assertions.assertEquals(1, orderBook.bidOrders.entriesList().size) + Assertions.assertEquals(2, orderBook.askOrders.entriesList().size) + Assertions.assertEquals(3, orderBook.orders.size) + Assertions.assertNotNull(orderBook.bestBidOrder) + Assertions.assertNotNull(orderBook.bestAskOrder) + + orderBook.handleNewOrderCommand(OrderCreateCommand(UUID.randomUUID().toString(), uuid, ETH_BTC_PAIR, 4849900, 10000, OrderDirection.BID, MatchConstraint.GTC, OrderType.LIMIT_ORDER)) as SimpleOrderBook.SimpleOrder + Assertions.assertEquals(2, orderBook.bidOrders.entriesList().size) + Assertions.assertEquals(2, orderBook.askOrders.entriesList().size) + Assertions.assertEquals(4, orderBook.orders.size) + Assertions.assertNotNull(orderBook.bestBidOrder) + Assertions.assertNotNull(orderBook.bestAskOrder) + } + +} \ No newline at end of file diff --git a/MatchingEngine/matching-ports/matching-eventlistener-kafka/.gitignore b/MatchingEngine/matching-ports/matching-eventlistener-kafka/.gitignore new file mode 100644 index 000000000..8851f0e4a --- /dev/null +++ b/MatchingEngine/matching-ports/matching-eventlistener-kafka/.gitignore @@ -0,0 +1,79 @@ +# Created by .ignore support plugin (hsz.mobi) +### Kotlin template +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +.idea/artifacts +.idea/compiler.xml +.idea/jarRepositories.xml +.idea/modules.xml +.idea/*.iml +.idea/modules +*.iml +*.ipr + +# File-based project format +*.iws + +# IntelliJ +out/ + +target/ + +.DS_Store + + + + diff --git a/MatchingEngine/matching-ports/matching-eventlistener-kafka/mvnw b/MatchingEngine/matching-ports/matching-eventlistener-kafka/mvnw new file mode 100644 index 000000000..a16b5431b --- /dev/null +++ b/MatchingEngine/matching-ports/matching-eventlistener-kafka/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/MatchingEngine/matching-ports/matching-eventlistener-kafka/mvnw.cmd b/MatchingEngine/matching-ports/matching-eventlistener-kafka/mvnw.cmd new file mode 100644 index 000000000..c8d43372c --- /dev/null +++ b/MatchingEngine/matching-ports/matching-eventlistener-kafka/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/MatchingEngine/matching-ports/matching-eventlistener-kafka/pom.xml b/MatchingEngine/matching-ports/matching-eventlistener-kafka/pom.xml new file mode 100644 index 000000000..4f0975361 --- /dev/null +++ b/MatchingEngine/matching-ports/matching-eventlistener-kafka/pom.xml @@ -0,0 +1,113 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.4 + + + co.nilin.opex + matching-eventlistener-kafka + 1.0-SNAPSHOT + matching-eventlistener-kafka + Matching engine kafka order submitter of Opex + + + 1.8 + 1.4.31 + ${version} + + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-webflux + + + co.nilin.opex + matching-core + ${matching.version} + provided + + + org.springframework.kafka + spring-kafka + + + io.projectreactor.kotlin + reactor-kotlin-extensions + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + + + org.jetbrains.kotlinx + kotlinx-coroutines-core + + + org.springframework.kafka + spring-kafka-test + test + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + -Xjsr305=strict + + + spring + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + + diff --git a/MatchingEngine/matching-ports/matching-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/config/OrderKafkaConfig.kt b/MatchingEngine/matching-ports/matching-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/config/OrderKafkaConfig.kt new file mode 100644 index 000000000..b2ad1fae5 --- /dev/null +++ b/MatchingEngine/matching-ports/matching-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/config/OrderKafkaConfig.kt @@ -0,0 +1,123 @@ +package co.nilin.opex.port.order.kafka.config + +import co.nilin.opex.matching.core.eventh.events.CoreEvent +import co.nilin.opex.port.order.kafka.consumer.EventKafkaListener +import co.nilin.opex.port.order.kafka.consumer.OrderKafkaListener +import co.nilin.opex.port.order.kafka.inout.OrderSubmitRequest +import org.apache.kafka.clients.admin.NewTopic +import org.apache.kafka.clients.consumer.ConsumerConfig +import org.apache.kafka.clients.producer.ProducerConfig +import org.apache.kafka.common.serialization.StringDeserializer +import org.apache.kafka.common.serialization.StringSerializer +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.ApplicationContext +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.context.support.GenericApplicationContext +import org.springframework.kafka.config.TopicBuilder +import org.springframework.kafka.core.ConsumerFactory +import org.springframework.kafka.core.DefaultKafkaConsumerFactory +import org.springframework.kafka.core.DefaultKafkaProducerFactory +import org.springframework.kafka.core.KafkaAdmin +import org.springframework.kafka.core.KafkaTemplate +import org.springframework.kafka.core.ProducerFactory +import org.springframework.kafka.listener.ConcurrentMessageListenerContainer +import org.springframework.kafka.listener.ContainerProperties +import org.springframework.kafka.listener.KafkaMessageListenerContainer +import org.springframework.kafka.support.serializer.JsonDeserializer +import org.springframework.kafka.support.serializer.JsonSerializer +import java.util.* +import java.util.regex.Pattern + +@Configuration +class OrderKafkaConfig { + + @Value("\${spring.kafka.bootstrap-servers}") + private lateinit var bootstrapServers: String + + @Value("\${spring.kafka.consumer.group-id}") + private val groupId: String? = null + + @Value("\${spring.app.symbols}") + private val symbols: String? = null + + @Autowired + private val applicationContext: GenericApplicationContext? = null + + @Autowired + fun createTopics() { + symbols!!.split(",").map { s -> "orders_$s" } + .map { topic -> + applicationContext?.registerBean("topic_${topic}", NewTopic::class.java, topic, 1, 1) + } + } + + @Bean("orderProducerConfigs") + fun producerConfigs(): Map? { + val props: MutableMap = HashMap() + props[ProducerConfig.BOOTSTRAP_SERVERS_CONFIG] = bootstrapServers + props[ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG] = StringSerializer::class.java + props[ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG] = JsonSerializer::class.java + return props + } + + @Bean("orderProducerFactory") + fun producerFactory(@Qualifier("orderProducerConfigs") producerConfigs: Map): ProducerFactory { + return DefaultKafkaProducerFactory(producerConfigs) + } + + @Bean("orderKafkaTemplate") + fun kafkaTemplate(@Qualifier("orderProducerFactory") producerFactory: ProducerFactory): KafkaTemplate { + return KafkaTemplate(producerFactory) + } + + @Bean("orderConsumerConfigs") + fun consumerConfigs(): Map? { + val props: MutableMap = HashMap() + props[ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG] = bootstrapServers + props[ConsumerConfig.GROUP_ID_CONFIG] = groupId + props[ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG] = StringDeserializer::class.java + props[ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG] = JsonDeserializer::class.java + props[JsonDeserializer.TRUSTED_PACKAGES] = "co.nilin.opex.*" + return props + } + + @Bean("orderConsumerFactory") + fun consumerFactory(@Qualifier("orderConsumerConfigs") consumerConfigs: Map): ConsumerFactory { + return DefaultKafkaConsumerFactory(consumerConfigs) + } + + @Bean("eventConsumerFactory") + fun eventConsumerFactory(@Qualifier("orderConsumerConfigs") consumerConfigs: Map): ConsumerFactory { + return DefaultKafkaConsumerFactory(consumerConfigs) + } + + @Autowired + fun configureListener( + orderKafkaListener: OrderKafkaListener, + @Qualifier("orderConsumerFactory") consumerFactory: ConsumerFactory, + kafkaAdmin: KafkaAdmin + ) { + val topics = symbols!!.split(",").map { s -> "orders_$s" }.toTypedArray() + val containerProps = ContainerProperties(*topics) + containerProps.messageListener = orderKafkaListener + val container = KafkaMessageListenerContainer(consumerFactory, containerProps) + container.beanName = "OrderKafkaListenerContainer" + container.start() + } + + @Autowired + fun configureEventListener( + eventListener: EventKafkaListener, + @Qualifier("eventConsumerFactory") consumerFactory: ConsumerFactory + ) { + val containerProps = ContainerProperties(Pattern.compile("events_.*")) + containerProps.messageListener = eventListener + val container = ConcurrentMessageListenerContainer(consumerFactory, containerProps) + container.beanName = "EventKafkaListenerContainer" + container.start() + } + +} \ No newline at end of file diff --git a/MatchingEngine/matching-ports/matching-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/consumer/EventKafkaListener.kt b/MatchingEngine/matching-ports/matching-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/consumer/EventKafkaListener.kt new file mode 100644 index 000000000..e26a14466 --- /dev/null +++ b/MatchingEngine/matching-ports/matching-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/consumer/EventKafkaListener.kt @@ -0,0 +1,29 @@ +package co.nilin.opex.port.order.kafka.consumer + +import co.nilin.opex.matching.core.eventh.events.CoreEvent +import co.nilin.opex.port.order.kafka.spi.EventListener +import org.apache.kafka.clients.consumer.ConsumerRecord +import org.springframework.kafka.listener.MessageListener +import org.springframework.stereotype.Component + +@Component +class EventKafkaListener: MessageListener { + + val eventListeners = arrayListOf() + + override fun onMessage(data: ConsumerRecord) { + eventListeners.forEach{ + tl -> tl.onEvent(data.value(), data.partition(), data.offset(), data.timestamp()) + } + } + + fun addEventListener(tl: EventListener){ + eventListeners.add(tl) + } + + fun removeEventListener(tl: EventListener){ + eventListeners.removeIf { + item -> item.id() == tl.id() + } + } +} \ No newline at end of file diff --git a/MatchingEngine/matching-ports/matching-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/consumer/OrderKafkaListener.kt b/MatchingEngine/matching-ports/matching-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/consumer/OrderKafkaListener.kt new file mode 100644 index 000000000..e888a6832 --- /dev/null +++ b/MatchingEngine/matching-ports/matching-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/consumer/OrderKafkaListener.kt @@ -0,0 +1,30 @@ +package co.nilin.opex.port.order.kafka.consumer + +import co.nilin.opex.port.order.kafka.inout.OrderSubmitRequest +import co.nilin.opex.port.order.kafka.spi.OrderSubmitRequestListener +import kotlinx.coroutines.runBlocking +import org.apache.kafka.clients.consumer.ConsumerRecord +import org.springframework.kafka.listener.MessageListener +import org.springframework.stereotype.Component + +@Component +class OrderKafkaListener : MessageListener { + val orderListeners = arrayListOf() + override fun onMessage(data: ConsumerRecord) { + orderListeners.forEach { tl -> + runBlocking { + tl.onOrder(data.value(), data.partition(), data.offset(), data.timestamp()) + } + } + } + + fun addOrderListener(tl: OrderSubmitRequestListener) { + orderListeners.add(tl) + } + + fun removeOrderListener(tl: OrderSubmitRequestListener) { + orderListeners.removeIf { item -> + item.id() == tl.id() + } + } +} \ No newline at end of file diff --git a/MatchingEngine/matching-ports/matching-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/inout/OrderSubmitRequest.kt b/MatchingEngine/matching-ports/matching-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/inout/OrderSubmitRequest.kt new file mode 100644 index 000000000..7768d1ef7 --- /dev/null +++ b/MatchingEngine/matching-ports/matching-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/inout/OrderSubmitRequest.kt @@ -0,0 +1,42 @@ +package co.nilin.opex.port.order.kafka.inout + +import co.nilin.opex.matching.core.model.MatchConstraint +import co.nilin.opex.matching.core.model.OrderDirection +import co.nilin.opex.matching.core.model.OrderType +import co.nilin.opex.matching.core.model.Pair + +class OrderSubmitRequest() { + + lateinit var ouid: String + lateinit var uuid: String + var orderId: Long? = null + lateinit var pair: Pair + var price: Long = 0 + var quantity: Long = 0 + var direction: OrderDirection = OrderDirection.BID + var matchConstraint: MatchConstraint = MatchConstraint.GTC + var orderType: OrderType = OrderType.LIMIT_ORDER + + + constructor(ouid: String, + uuid: String, + orderId: Long?, + pair: Pair, + price: Long, + quantity: Long, + direction: OrderDirection, + matchConstraint: MatchConstraint, + orderType: OrderType):this(){ + this.ouid = ouid + this.uuid = uuid + this.orderId = orderId + this.pair = pair + this.price = price + this.quantity = quantity + this.direction = direction + this.matchConstraint = matchConstraint + this.orderType = orderType + } + + +} \ No newline at end of file diff --git a/MatchingEngine/matching-ports/matching-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/inout/OrderSubmitResult.kt b/MatchingEngine/matching-ports/matching-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/inout/OrderSubmitResult.kt new file mode 100644 index 000000000..35d4430ec --- /dev/null +++ b/MatchingEngine/matching-ports/matching-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/inout/OrderSubmitResult.kt @@ -0,0 +1,3 @@ +package co.nilin.opex.port.order.kafka.inout + +class OrderSubmitResult(offset: Long?) \ No newline at end of file diff --git a/MatchingEngine/matching-ports/matching-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/spi/EventListener.kt b/MatchingEngine/matching-ports/matching-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/spi/EventListener.kt new file mode 100644 index 000000000..b0f4187fe --- /dev/null +++ b/MatchingEngine/matching-ports/matching-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/spi/EventListener.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.port.order.kafka.spi + +import co.nilin.opex.matching.core.eventh.events.CoreEvent + +interface EventListener { + fun id(): String + fun onEvent(event: CoreEvent, partition: Int, offset: Long, timestamp: Long) +} \ No newline at end of file diff --git a/MatchingEngine/matching-ports/matching-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/spi/OrderSubmitRequestListener.kt b/MatchingEngine/matching-ports/matching-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/spi/OrderSubmitRequestListener.kt new file mode 100644 index 000000000..ee659ae4d --- /dev/null +++ b/MatchingEngine/matching-ports/matching-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/spi/OrderSubmitRequestListener.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.port.order.kafka.spi + +import co.nilin.opex.port.order.kafka.inout.OrderSubmitRequest + +interface OrderSubmitRequestListener { + fun id(): String + suspend fun onOrder(order: OrderSubmitRequest, partition: Int, offset: Long, timestamp: Long) +} \ No newline at end of file diff --git a/MatchingEngine/matching-ports/matching-snapshots-redis/.gitignore b/MatchingEngine/matching-ports/matching-snapshots-redis/.gitignore new file mode 100644 index 000000000..807d27eef --- /dev/null +++ b/MatchingEngine/matching-ports/matching-snapshots-redis/.gitignore @@ -0,0 +1,76 @@ +# Created by .ignore support plugin (hsz.mobi) +### Kotlin template +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +.idea/artifacts +.idea/compiler.xml +.idea/jarRepositories.xml +.idea/modules.xml +.idea/*.iml +.idea/modules +*.iml +*.ipr + +# File-based project format +*.iws + +# IntelliJ +out/ + +target/ + +.DS_Store + diff --git a/MatchingEngine/matching-ports/matching-snapshots-redis/mvnw b/MatchingEngine/matching-ports/matching-snapshots-redis/mvnw new file mode 100644 index 000000000..a16b5431b --- /dev/null +++ b/MatchingEngine/matching-ports/matching-snapshots-redis/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/MatchingEngine/matching-ports/matching-snapshots-redis/mvnw.cmd b/MatchingEngine/matching-ports/matching-snapshots-redis/mvnw.cmd new file mode 100644 index 000000000..c8d43372c --- /dev/null +++ b/MatchingEngine/matching-ports/matching-snapshots-redis/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/MatchingEngine/matching-ports/matching-snapshots-redis/pom.xml b/MatchingEngine/matching-ports/matching-snapshots-redis/pom.xml new file mode 100644 index 000000000..5d1854fe3 --- /dev/null +++ b/MatchingEngine/matching-ports/matching-snapshots-redis/pom.xml @@ -0,0 +1,101 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.4 + + + co.nilin.opex + matching-snapshots-redis + 1.0-SNAPSHOT + matching-snapshots-redis + Persist Matching engine snapshot of Opex on Redis + + + 1.8 + 1.4.31 + ${version} + + + + + co.nilin.opex + matching-core + ${matching.version} + provided + + + + org.springframework.boot + spring-boot-starter-data-redis-reactive + + + + io.projectreactor.kotlin + reactor-kotlin-extensions + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + + + org.jetbrains.kotlinx + kotlinx-coroutines-core + + + io.projectreactor + reactor-test + test + + + + com.fasterxml.jackson.core + jackson-databind + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + -Xjsr305=strict + + + spring + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + + diff --git a/MatchingEngine/matching-ports/matching-snapshots-redis/src/main/kotlin/co/nilin/opex/port/order/redis/config/RedisConfig.kt b/MatchingEngine/matching-ports/matching-snapshots-redis/src/main/kotlin/co/nilin/opex/port/order/redis/config/RedisConfig.kt new file mode 100644 index 000000000..9f688593b --- /dev/null +++ b/MatchingEngine/matching-ports/matching-snapshots-redis/src/main/kotlin/co/nilin/opex/port/order/redis/config/RedisConfig.kt @@ -0,0 +1,30 @@ +package co.nilin.opex.port.order.redis.config + +import co.nilin.opex.matching.core.model.PersistentOrderBook +import com.fasterxml.jackson.annotation.JsonAutoDetect +import com.fasterxml.jackson.annotation.PropertyAccessor +import com.fasterxml.jackson.databind.ObjectMapper +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory +import org.springframework.data.redis.core.ReactiveRedisTemplate +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer +import org.springframework.data.redis.serializer.RedisSerializationContext +import org.springframework.data.redis.serializer.StringRedisSerializer + +@Configuration +class RedisConfig() { + @Bean("snapshotRedisTemplate") + fun snapshotRedisTemplate(factory: ReactiveRedisConnectionFactory): ReactiveRedisTemplate { + val jackson2JsonRedisSerializer = Jackson2JsonRedisSerializer(PersistentOrderBook::class.java) + val objectMapper = ObjectMapper() + objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY) + jackson2JsonRedisSerializer.setObjectMapper(objectMapper) + val serializationContext = RedisSerializationContext.newSerializationContext(StringRedisSerializer()) + .hashKey(StringRedisSerializer()) + .hashValue(jackson2JsonRedisSerializer) + .build() + + return ReactiveRedisTemplate(factory, serializationContext) + } +} \ No newline at end of file diff --git a/MatchingEngine/matching-ports/matching-snapshots-redis/src/main/kotlin/co/nilin/opex/port/order/redis/service/OrderBookPersister.kt b/MatchingEngine/matching-ports/matching-snapshots-redis/src/main/kotlin/co/nilin/opex/port/order/redis/service/OrderBookPersister.kt new file mode 100644 index 000000000..ed6c04d2d --- /dev/null +++ b/MatchingEngine/matching-ports/matching-snapshots-redis/src/main/kotlin/co/nilin/opex/port/order/redis/service/OrderBookPersister.kt @@ -0,0 +1,26 @@ +package co.nilin.opex.port.order.redis.service + +import co.nilin.opex.matching.core.model.PersistentOrderBook +import co.nilin.opex.matching.core.spi.OrderBookPersister +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.data.redis.core.ReactiveRedisTemplate +import org.springframework.stereotype.Component + +@Component +class OrderBookRedisPersister( + @Qualifier("snapshotRedisTemplate") + val redisTemplate: ReactiveRedisTemplate +) : OrderBookPersister { + + override suspend fun storeLastState(orderBook: PersistentOrderBook) { + redisTemplate.opsForHash() + .put("OrderbookSnapshots", orderBook.pair.toString(), orderBook) + .subscribe() + } + + override suspend fun loadLastState(symbol: String): PersistentOrderBook? = + redisTemplate.opsForHash() + .get("OrderbookSnapshots", symbol) + .blockOptional().orElse(null) + +} diff --git a/MatchingEngine/matching-ports/matching-submitter-kafka/.gitignore b/MatchingEngine/matching-ports/matching-submitter-kafka/.gitignore new file mode 100644 index 000000000..36b20dfa2 --- /dev/null +++ b/MatchingEngine/matching-ports/matching-submitter-kafka/.gitignore @@ -0,0 +1,77 @@ +# Created by .ignore support plugin (hsz.mobi) +### Kotlin template +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +.idea/artifacts +.idea/compiler.xml +.idea/jarRepositories.xml +.idea/modules.xml +.idea/*.iml +.idea/modules +*.iml +*.ipr + +# File-based project format +*.iws + +# IntelliJ +out/ + +target/ + +.DS_Store + + diff --git a/MatchingEngine/matching-ports/matching-submitter-kafka/mvnw b/MatchingEngine/matching-ports/matching-submitter-kafka/mvnw new file mode 100644 index 000000000..a16b5431b --- /dev/null +++ b/MatchingEngine/matching-ports/matching-submitter-kafka/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/MatchingEngine/matching-ports/matching-submitter-kafka/mvnw.cmd b/MatchingEngine/matching-ports/matching-submitter-kafka/mvnw.cmd new file mode 100644 index 000000000..c8d43372c --- /dev/null +++ b/MatchingEngine/matching-ports/matching-submitter-kafka/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/MatchingEngine/matching-ports/matching-submitter-kafka/pom.xml b/MatchingEngine/matching-ports/matching-submitter-kafka/pom.xml new file mode 100644 index 000000000..9313485aa --- /dev/null +++ b/MatchingEngine/matching-ports/matching-submitter-kafka/pom.xml @@ -0,0 +1,113 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.4 + + + co.nilin.opex + matching-submitter-kafka + 1.0-SNAPSHOT + matching-submitter-kafka + Matching engine kafka order submitter of Opex + + + 1.8 + 1.4.31 + ${version} + + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-webflux + + + co.nilin.opex + matching-core + ${matching.version} + provided + + + org.springframework.kafka + spring-kafka + + + io.projectreactor.kotlin + reactor-kotlin-extensions + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + + + org.jetbrains.kotlinx + kotlinx-coroutines-core + + + org.springframework.kafka + spring-kafka-test + test + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + -Xjsr305=strict + + + spring + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + + diff --git a/MatchingEngine/matching-ports/matching-submitter-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/config/EventsKafkaConfig.kt b/MatchingEngine/matching-ports/matching-submitter-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/config/EventsKafkaConfig.kt new file mode 100644 index 000000000..0b272fa5b --- /dev/null +++ b/MatchingEngine/matching-ports/matching-submitter-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/config/EventsKafkaConfig.kt @@ -0,0 +1,77 @@ +package co.nilin.opex.port.order.kafka.config + + +import co.nilin.opex.matching.core.eventh.events.CoreEvent +import org.apache.kafka.clients.admin.AdminClientConfig +import org.apache.kafka.clients.admin.NewTopic +import org.apache.kafka.clients.producer.ProducerConfig +import org.apache.kafka.common.serialization.StringSerializer +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.context.support.GenericApplicationContext +import org.springframework.kafka.config.TopicBuilder +import org.springframework.kafka.core.DefaultKafkaProducerFactory +import org.springframework.kafka.core.KafkaAdmin +import org.springframework.kafka.core.KafkaTemplate +import org.springframework.kafka.core.ProducerFactory +import org.springframework.kafka.support.serializer.JsonSerializer +import org.springframework.util.StringUtils +import java.util.* + + +@Configuration +class EventsKafkaConfig() { + @Value("\${spring.kafka.bootstrap-servers}") + private lateinit var bootstrapServers: String + + @Value("\${spring.app.symbols}") + private val symbols: String? = null + + @Autowired + private val applicationContext: GenericApplicationContext? = null + + @Bean("eventsProducerConfigs") + fun producerConfigs(): Map? { + val props: MutableMap = HashMap() + props[ProducerConfig.BOOTSTRAP_SERVERS_CONFIG] = bootstrapServers + props[ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG] = StringSerializer::class.java + props[ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG] = JsonSerializer::class.java + return props + } + + @Bean("eventsProducerFactory") + fun producerFactory(@Qualifier("eventsProducerConfigs") producerConfigs: Map): ProducerFactory { + return DefaultKafkaProducerFactory(producerConfigs) + } + + @Bean("eventsKafkaTemplate") + fun kafkaTemplate(@Qualifier("eventsProducerFactory") producerFactory: ProducerFactory): KafkaTemplate { + return KafkaTemplate(producerFactory) + } + + + @Bean + fun admin(): KafkaAdmin? { + val configs: MutableMap = HashMap() + configs[AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG] = bootstrapServers + return KafkaAdmin(configs) + } + + @Autowired + fun createTopics(){ + symbols!!.split(",") + .map { s -> "events_$s" } + .map { topic -> + applicationContext?.registerBean("topic_${topic}", NewTopic::class.java, topic, 10, 1) + } + symbols.split(",") + .map { s -> "trades_$s" } + .map { topic -> + applicationContext?.registerBean("topic_${topic}", NewTopic::class.java, topic, 10, 1) + } + } + +} \ No newline at end of file diff --git a/MatchingEngine/matching-ports/matching-submitter-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/service/EventsSubmitter.kt b/MatchingEngine/matching-ports/matching-submitter-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/service/EventsSubmitter.kt new file mode 100644 index 000000000..ac88ed673 --- /dev/null +++ b/MatchingEngine/matching-ports/matching-submitter-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/service/EventsSubmitter.kt @@ -0,0 +1,18 @@ +package co.nilin.opex.port.order.kafka.service + +import co.nilin.opex.matching.core.eventh.events.CoreEvent +import co.nilin.opex.matching.core.eventh.events.TradeEvent +import org.springframework.kafka.core.KafkaTemplate +import org.springframework.stereotype.Component +import kotlin.coroutines.suspendCoroutine + +@Component +class EventsSubmitter(val kafkaTemplate: KafkaTemplate) { + suspend fun submit(event: CoreEvent): Unit= suspendCoroutine { cont -> + println("Submit!") + if ( event is TradeEvent) + kafkaTemplate.send("trades_${event.pair.leftSideName}_${event.pair.rightSideName}", event) + kafkaTemplate.send("events_${event.pair.leftSideName}_${event.pair.rightSideName}", event) + } + +} \ No newline at end of file diff --git a/MatchingEngine/pom.xml b/MatchingEngine/pom.xml new file mode 100644 index 000000000..bd686f926 --- /dev/null +++ b/MatchingEngine/pom.xml @@ -0,0 +1,19 @@ + + + 4.0.0 + co.nilin.opex + matching-engine + 1.0-SNAPSHOT + matching-engine + pom + Matching Engine root of Opex + + + matching-core + matching-ports/matching-submitter-kafka + matching-ports/matching-eventlistener-kafka + matching-ports/matching-snapshots-redis + matching-app + + diff --git a/MatchingGateway/.gitignore b/MatchingGateway/.gitignore new file mode 100644 index 000000000..90ffaa853 --- /dev/null +++ b/MatchingGateway/.gitignore @@ -0,0 +1,73 @@ +# Created by .ignore support plugin (hsz.mobi) +### Kotlin template +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +.idea/artifacts +.idea/compiler.xml +.idea/jarRepositories.xml +.idea/modules.xml +.idea/*.iml +.idea/modules +*.iml +*.ipr + +# File-based project format +*.iws + +# IntelliJ +out/ + +target/ diff --git a/MatchingGateway/gateway-app/.gitignore b/MatchingGateway/gateway-app/.gitignore new file mode 100644 index 000000000..7f15adddb --- /dev/null +++ b/MatchingGateway/gateway-app/.gitignore @@ -0,0 +1,77 @@ +# Created by .ignore support plugin (hsz.mobi) +### Kotlin template +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +.idea/artifacts +.idea/compiler.xml +.idea/jarRepositories.xml +.idea/modules.xml +.idea/*.iml +.idea/modules +*.iml +*.ipr + +# File-based project format +*.iws + +# IntelliJ +out/ + +target/ + + + + diff --git a/MatchingGateway/gateway-app/Dockerfile b/MatchingGateway/gateway-app/Dockerfile new file mode 100644 index 000000000..f2cbd4c26 --- /dev/null +++ b/MatchingGateway/gateway-app/Dockerfile @@ -0,0 +1,5 @@ +FROM openjdk:8-jdk-alpine +VOLUME /tmp +ARG JAR_FILE=target/*.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"] \ No newline at end of file diff --git a/MatchingGateway/gateway-app/pom.xml b/MatchingGateway/gateway-app/pom.xml new file mode 100644 index 000000000..576baf60c --- /dev/null +++ b/MatchingGateway/gateway-app/pom.xml @@ -0,0 +1,227 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.4 + + + co.nilin.opex + matching-gateway-app + 1.0-SNAPSHOT + matching-gateway-app + Matching gateway running app Opex + + + 1.8 + 1.4.31 + 2020.0.2 + ${version} + ${version} + + + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + com.fasterxml.jackson.module + jackson-module-kotlin + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-webflux + + + co.nilin.opex + matching-core + ${matching.version} + + + co.nilin.opex + gateway-order-submitter-kafka + ${matching.version} + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + org.springframework.cloud + spring-cloud-starter-consul-all + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-oauth2-resource-server + + + org.bouncycastle + bcprov-jdk15on + 1.60 + + + io.springfox + springfox-boot-starter + 3.0.0 + + + co.nilin.opex + error-handler + ${utility.version} + + + co.nilin.opex + logging-handler + ${utility.version} + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + 2.18 + + + ${skip.unit.tests} + + + **/*IntegrationTest.java + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-test-source + generate-test-sources + + add-test-source + + + + src/test/java + + + + + compile + + add-source + + + + src/main/java + + + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + compile + + + + test-compile + test-compile + + test-compile + + + + + + -Xjsr305=strict + + + spring + + 1.8 + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + compile + compile + + compile + + + + testCompile + test-compile + + testCompile + + + + + + opex-matching-gateway + + + diff --git a/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/MatchingGatewayApp.kt b/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/MatchingGatewayApp.kt new file mode 100644 index 000000000..208a63169 --- /dev/null +++ b/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/MatchingGatewayApp.kt @@ -0,0 +1,17 @@ +package co.nilin.opex.app + +import co.nilin.opex.utility.error.EnableOpexErrorHandler +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication +import org.springframework.context.annotation.ComponentScan +import springfox.documentation.swagger2.annotations.EnableSwagger2 + +@SpringBootApplication +@ComponentScan("co.nilin.opex") +@EnableOpexErrorHandler +@EnableSwagger2 +class MatchingGatewayApp + +fun main(args: Array) { + runApplication(*args) +} \ No newline at end of file diff --git a/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/config/AppConfig.kt b/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/config/AppConfig.kt new file mode 100644 index 000000000..e902db222 --- /dev/null +++ b/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/config/AppConfig.kt @@ -0,0 +1,47 @@ +package co.nilin.opex.app.config + +import co.nilin.opex.app.inout.PairConfig +import co.nilin.opex.app.inout.PairFeeConfig +import co.nilin.opex.app.spi.PairConfigLoader +import co.nilin.opex.app.spi.AccountantApiProxy +import co.nilin.opex.matching.core.model.OrderDirection +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import java.math.BigDecimal + + +@Configuration +class AppConfig { + @Bean + @ConditionalOnMissingBean(AccountantApiProxy::class) + fun accountantApiProxy(): AccountantApiProxy { + return object : AccountantApiProxy { + override suspend fun canCreateOrder(uuid: String, symbol: String, value: BigDecimal): Boolean { + return true + } + + override suspend fun fetchPairFeeConfig( + pair: String, + direction: OrderDirection, + userLevel: String + ): PairFeeConfig { + return PairFeeConfig( + PairConfig( + pair, pair.split("_")[0], pair.split("_")[1], 1.0, 1.0 + ), direction.name, userLevel, 0.01, 0.01 + ) + } + } + } + + @Bean + @ConditionalOnMissingBean(PairConfigLoader::class) + fun pairConfigLoader(accountantApiProxy: AccountantApiProxy): PairConfigLoader { + return object : PairConfigLoader { + override suspend fun load(pair: String, direction: OrderDirection, userLevel: String): PairFeeConfig { + return accountantApiProxy.fetchPairFeeConfig(pair, direction, userLevel) + } + } + } +} \ No newline at end of file diff --git a/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/config/SecurityConfig.kt b/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/config/SecurityConfig.kt new file mode 100644 index 000000000..4ca5c8e4b --- /dev/null +++ b/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/config/SecurityConfig.kt @@ -0,0 +1,42 @@ +package co.nilin.opex.app.config + +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Bean +import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity +import org.springframework.security.config.web.server.ServerHttpSecurity +import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder +import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder +import org.springframework.security.web.server.SecurityWebFilterChain +import org.springframework.web.reactive.function.client.WebClient + +@EnableWebFluxSecurity +class SecurityConfig(private val webClient: WebClient) { + + @Value("\${app.auth.cert-url}") + private lateinit var jwkUrl: String + + @Bean + fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain? { + http.csrf().disable() + .authorizeExchange() + .pathMatchers("/hello").permitAll() + .pathMatchers("/actuator/**").permitAll() + .pathMatchers("/swagger-ui/**").permitAll() + .pathMatchers("/swagger-resources/**").permitAll() + .pathMatchers("/v2/api-docs").permitAll() + .pathMatchers("/**").hasAuthority("SCOPE_trust") + .anyExchange().authenticated() + .and() + .oauth2ResourceServer() + .jwt() + return http.build() + } + + @Bean + @Throws(Exception::class) + fun reactiveJwtDecoder(): ReactiveJwtDecoder? { + return NimbusReactiveJwtDecoder.withJwkSetUri(jwkUrl) + .webClient(webClient) + .build() + } +} diff --git a/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/config/SwaggerConfig.kt b/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/config/SwaggerConfig.kt new file mode 100644 index 000000000..37abadf5e --- /dev/null +++ b/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/config/SwaggerConfig.kt @@ -0,0 +1,98 @@ +package co.nilin.opex.app.config + +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.security.core.annotation.AuthenticationPrincipal +import springfox.documentation.builders.ApiInfoBuilder +import springfox.documentation.builders.OAuthBuilder +import springfox.documentation.builders.PathSelectors +import springfox.documentation.builders.RequestParameterBuilder +import springfox.documentation.service.* +import springfox.documentation.spi.DocumentationType +import springfox.documentation.spi.service.contexts.SecurityContext +import springfox.documentation.spring.web.plugins.Docket +import springfox.documentation.swagger.web.SecurityConfiguration +import springfox.documentation.swagger.web.SecurityConfigurationBuilder +import java.security.Principal +import java.util.* + + +@Configuration +class SwaggerConfig { + @Value("\${swagger.authUrl}") + val authUrl: String = "" + + @Bean + fun opexMatchingGateway(): Docket { + return Docket(DocumentationType.SWAGGER_2) + .groupName("opex-matching-gateway") + .apiInfo(apiInfo()) + .select() + .paths(PathSelectors.regex("^/actuator.*").negate()) + .build() + .globalRequestParameters( + Collections.singletonList( + RequestParameterBuilder() + .name("content-type") + .description("content-type") + .`in`(ParameterType.HEADER) + .required(true) + .build() + ) + ) + .ignoredParameterTypes(AuthenticationPrincipal::class.java, Principal::class.java) + .useDefaultResponseMessages(false) + .securitySchemes(Collections.singletonList(oauth())) + .securityContexts(Collections.singletonList(securityContext())) + } + + private fun apiInfo(): ApiInfo { + return ApiInfoBuilder() + .title("OPEX API") + .description("Backend for opex exchange.") + .license("MIT License") + .licenseUrl("https://github.com/opexdev/Back-end/blob/feature/1-MVP/LICENSE") + .version("0.1") + .build() + } + + private fun oauth(): SecurityScheme { + return OAuthBuilder() + .name("opex") + .grantTypes(grantTypes()) + .scopes(scopes()) + .build() + } + + private fun scopes(): List { + return listOf(AuthorizationScope("openid", "OpenId")) + } + + private fun grantTypes(): List { + val tokenUrl = "$authUrl/auth/realms/opex/protocol/openid-connect/token" + val grantType = ResourceOwnerPasswordCredentialsGrant(tokenUrl) + return Collections.singletonList(grantType) + } + + private fun securityContext(): SecurityContext { + val securityReference = SecurityReference.builder() + .reference("opex") + .scopes(emptyArray()) + .build() + return SecurityContext.builder() + .securityReferences(Collections.singletonList(securityReference)) + .operationSelector { true } + .build() + } + + @Bean + fun securityInfo(): SecurityConfiguration { + return SecurityConfigurationBuilder.builder() + .clientId("admin-cli") + .realm("opex") + .appName("opex") + .scopeSeparator(",") + .build() + } +} \ No newline at end of file diff --git a/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/config/WebClientConfig.kt b/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/config/WebClientConfig.kt new file mode 100644 index 000000000..ae61545dd --- /dev/null +++ b/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/config/WebClientConfig.kt @@ -0,0 +1,38 @@ +package co.nilin.opex.app.config + +import co.nilin.opex.utility.log.interceptor.CustomLogger +import org.springframework.cloud.client.ServiceInstance +import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties +import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer +import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerExchangeFilterFunction +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.http.client.reactive.ReactorClientHttpConnector +import org.springframework.web.reactive.function.client.WebClient +import reactor.netty.http.client.HttpClient + +@Configuration +class WebClientConfig { + + @Bean + fun webClient(loadBalancerFactory: ReactiveLoadBalancer.Factory): WebClient { + val logger = CustomLogger(HttpClient::class.java) + return WebClient.builder() + .clientConnector( + ReactorClientHttpConnector( + HttpClient + .create() + .doOnRequest { request, connection -> + connection.addHandlerFirst(logger) + } + ) + ) + .filter( + ReactorLoadBalancerExchangeFilterFunction( + loadBalancerFactory, LoadBalancerProperties(), emptyList() + ) + ) + .build() + } + +} diff --git a/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/controller/ControllerExceptionHandler.kt b/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/controller/ControllerExceptionHandler.kt new file mode 100644 index 000000000..f4766e4e6 --- /dev/null +++ b/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/controller/ControllerExceptionHandler.kt @@ -0,0 +1,99 @@ +package co.nilin.opex.app.controller + +import co.nilin.opex.app.exception.NotAllowedToSubmitOrderException +import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.fasterxml.jackson.databind.ObjectMapper +import org.slf4j.LoggerFactory +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.ExceptionHandler +import org.springframework.web.bind.annotation.RestControllerAdvice +import org.springframework.web.reactive.function.client.WebClientResponseException +import java.nio.charset.StandardCharsets +import java.util.* + +//@RestControllerAdvice +class ControllerExceptionHandler { + + data class ErrorResponse( + val timestamp: Date, val status: Int, val error: String, val message: String + ) + + val logger = LoggerFactory.getLogger(ControllerExceptionHandler::class.java) + + val objectMapper: ObjectMapper = ObjectMapper() + + @ExceptionHandler(NotAllowedToSubmitOrderException::class) + fun handle(ex: NotAllowedToSubmitOrderException): ResponseEntity { + logger.error("Trace Error {}", ex) + val ret = ResponseEntity.status(500).body( + ErrorResponse( + Date(), -1, ex::class.qualifiedName ?: "", ex.message ?: "" + ) + ) + logger.debug("return error response:{}", ret) + return ret + } + + @JsonIgnoreProperties(ignoreUnknown = true) + class WebClientErrorResponse { + constructor() { + + } + + constructor(timestamp: Date?, path: String?, status: Int?, error: String?, message: String?) { + this.timestamp = timestamp + this.path = path + this.status = status + this.error = error + this.message = message + } + + var timestamp: Date? = null + var path: String? = null + var status: Int? = null + var error: String? = null + var message: String? = null + } + + @ExceptionHandler(WebClientResponseException::class) + fun handle(ex: WebClientResponseException): ResponseEntity { + logger.error("Trace Error {}", ex) + try { + val body = objectMapper.readValue( + ex.responseBodyAsByteArray.toString(StandardCharsets.UTF_8), + WebClientErrorResponse::class.java + ) + val ret = ResponseEntity.status(body.status ?: ex.rawStatusCode).body( + ErrorResponse( + Date(), + body.status ?: ex.rawStatusCode, + body.error ?: ex::class.qualifiedName ?: "", + body.message ?: "Internal Server Error" + ) + ) + logger.debug("return error response:{}", ret) + return ret + } catch (je: Exception) { + logger.error("Trace Error {}", je) + val ret = ResponseEntity.status(ex.statusCode).body( + ErrorResponse( + Date(), ex.rawStatusCode, ex::class.qualifiedName ?: "", "Internal Server Error" + ) + ) + logger.debug("return error response:{}", ret) + return ret + } + } + + @ExceptionHandler(Throwable::class) + fun handle(ex: Throwable): ResponseEntity { + logger.error("Trace Error {}", ex) + val ret = ResponseEntity.status(500).body( + ErrorResponse( + Date(), 500, ex::class.qualifiedName ?: "", "Internal Server Error" + ) + ) + logger.debug("return error response:{}", ret) + return ret + } +} diff --git a/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/controller/OrderController.kt b/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/controller/OrderController.kt new file mode 100644 index 000000000..9b243d2df --- /dev/null +++ b/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/controller/OrderController.kt @@ -0,0 +1,52 @@ +package co.nilin.opex.app.controller + +import co.nilin.opex.app.inout.CancelOrderRequest +import co.nilin.opex.app.inout.CreateOrderRequest +import co.nilin.opex.app.service.OrderService +import co.nilin.opex.port.order.kafka.inout.OrderSubmitResult +import io.swagger.annotations.ApiResponse +import io.swagger.annotations.Example +import io.swagger.annotations.ExampleProperty +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RestController +import java.security.Principal + +@RestController +class OrderController(val orderService: OrderService) { + + @PostMapping("/order") + @ApiResponse( + message = "OK", + code = 200, + examples = Example( + ExampleProperty( + value = "{ }", + mediaType = "application/json" + ) + ) + ) + suspend fun submitNewOrder( + principal: Principal, + @RequestBody createOrderRequest: CreateOrderRequest + ): OrderSubmitResult { + createOrderRequest.uuid = principal.name + return orderService.submitNewOrder(createOrderRequest) + } + + @PostMapping("/order/cancel") + @ApiResponse( + message = "OK", + code = 200, + examples = Example( + ExampleProperty( + value = "{ }", + mediaType = "application/json" + ) + ) + ) + suspend fun cancelOrder(principal: Principal, @RequestBody request: CancelOrderRequest): OrderSubmitResult { + request.uuid = principal.name + return orderService.cancelOrder(request) + } +} \ No newline at end of file diff --git a/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/exception/NotAllowedToSubmitOrderException.kt b/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/exception/NotAllowedToSubmitOrderException.kt new file mode 100644 index 000000000..28cb47ced --- /dev/null +++ b/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/exception/NotAllowedToSubmitOrderException.kt @@ -0,0 +1,6 @@ +package co.nilin.opex.app.exception + +import java.lang.RuntimeException + +class NotAllowedToSubmitOrderException: RuntimeException() { +} \ No newline at end of file diff --git a/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/inout/CancelOrderRequest.kt b/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/inout/CancelOrderRequest.kt new file mode 100644 index 000000000..e7a0fa1da --- /dev/null +++ b/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/inout/CancelOrderRequest.kt @@ -0,0 +1,3 @@ +package co.nilin.opex.app.inout + +class CancelOrderRequest(val ouid: String, var uuid: String, val orderId: Long, val symbol: String) \ No newline at end of file diff --git a/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/inout/CreateOrderRequest.kt b/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/inout/CreateOrderRequest.kt new file mode 100644 index 000000000..1b9d84bc5 --- /dev/null +++ b/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/inout/CreateOrderRequest.kt @@ -0,0 +1,16 @@ +package co.nilin.opex.app.inout + +import co.nilin.opex.matching.core.model.MatchConstraint +import co.nilin.opex.matching.core.model.OrderDirection +import co.nilin.opex.matching.core.model.OrderType +import java.math.BigDecimal + +data class CreateOrderRequest( + var uuid: String?, + val pair: String, + val price: BigDecimal, + val quantity: BigDecimal, + val direction: OrderDirection, + val matchConstraint: MatchConstraint, + val orderType: OrderType +) \ No newline at end of file diff --git a/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/inout/PairConfig.kt b/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/inout/PairConfig.kt new file mode 100644 index 000000000..3cc48f34e --- /dev/null +++ b/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/inout/PairConfig.kt @@ -0,0 +1,9 @@ +package co.nilin.opex.app.inout + +class PairConfig( + val pair: String, + val leftSideWalletSymbol: String, //can be same as pair left side + val rightSideWalletSymbol: String, //can be same as pair right side + val rightSideFraction: Double, + val leftSideFraction: Double +) \ No newline at end of file diff --git a/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/inout/PairFeeConfig.kt b/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/inout/PairFeeConfig.kt new file mode 100644 index 000000000..43d92f866 --- /dev/null +++ b/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/inout/PairFeeConfig.kt @@ -0,0 +1,9 @@ +package co.nilin.opex.app.inout + +class PairFeeConfig( + val pairConfig: PairConfig, + val direction: String?, + val userLevel: String?, + val makerFee: Double, + val takerFee: Double +) \ No newline at end of file diff --git a/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/proxy/AccountantProxyImpl.kt b/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/proxy/AccountantProxyImpl.kt new file mode 100644 index 000000000..621551558 --- /dev/null +++ b/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/proxy/AccountantProxyImpl.kt @@ -0,0 +1,51 @@ +package co.nilin.opex.app.proxy + +import co.nilin.opex.app.inout.PairFeeConfig +import co.nilin.opex.app.spi.AccountantApiProxy +import co.nilin.opex.matching.core.model.OrderDirection +import kotlinx.coroutines.reactive.awaitFirst +import org.springframework.beans.factory.annotation.Value +import org.springframework.core.ParameterizedTypeReference +import org.springframework.stereotype.Component +import org.springframework.web.reactive.function.client.WebClient +import java.math.BigDecimal +import java.net.URI + +inline fun typeRef(): ParameterizedTypeReference = object : ParameterizedTypeReference() {} + +@Component +class AccountantProxyImpl( + @Value("\${app.accountant.url}") val accountantBaseUrl: String, val webClient: WebClient +) : AccountantApiProxy { + override suspend fun canCreateOrder(uuid: String, symbol: String, value: BigDecimal): Boolean { + data class BooleanResponse(val result: Boolean) + return webClient.get() + .uri(URI.create("$accountantBaseUrl/$uuid/create_order/${value}_${symbol}/allowed")) + .header("Content-Type", "application/json") + .retrieve() + .onStatus({ t -> t.isError }, { it.createException()}) + .bodyToMono(typeRef()) + .log() + .awaitFirst() + .result + } + + override suspend fun fetchPairFeeConfig(pair: String, direction: OrderDirection, userLevel: String): PairFeeConfig { + return webClient.get() + .uri( + URI.create( + if (userLevel.isBlank()) { + "$accountantBaseUrl/config/${pair}/fee/${direction}" + } else { + "$accountantBaseUrl/config/${pair}/fee/${direction}-${userLevel}" + } + ) + ) + .header("Content-Type", "application/json") + .retrieve() + .onStatus({ t -> t.isError }, { it.createException()}) + .bodyToMono(typeRef()) + .log() + .awaitFirst() + } +} \ No newline at end of file diff --git a/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/service/OrderService.kt b/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/service/OrderService.kt new file mode 100644 index 000000000..7b46616fe --- /dev/null +++ b/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/service/OrderService.kt @@ -0,0 +1,78 @@ +package co.nilin.opex.app.service + +import co.nilin.opex.app.inout.CancelOrderRequest +import co.nilin.opex.app.inout.CreateOrderRequest +import co.nilin.opex.app.spi.AccountantApiProxy +import co.nilin.opex.app.spi.PairConfigLoader +import co.nilin.opex.matching.core.eventh.events.CancelOrderEvent +import co.nilin.opex.matching.core.model.OrderDirection +import co.nilin.opex.matching.core.model.Pair +import co.nilin.opex.port.order.kafka.inout.OrderSubmitRequest +import co.nilin.opex.port.order.kafka.inout.OrderSubmitResult +import co.nilin.opex.port.order.kafka.service.EventSubmitter +import co.nilin.opex.port.order.kafka.service.OrderSubmitter +import co.nilin.opex.utility.error.data.OpexError +import co.nilin.opex.utility.error.data.throwError +import org.springframework.stereotype.Service +import java.util.* + +@Service +class OrderService( + val accountantApiProxy: AccountantApiProxy, + val orderSubmitter: OrderSubmitter, + val eventSubmitter: EventSubmitter, + val pairConfigLoader: PairConfigLoader +) { + suspend fun submitNewOrder(createOrderRequest: CreateOrderRequest): OrderSubmitResult { + val symbolSides = createOrderRequest.pair.split("_") + val symbol = if (createOrderRequest.direction == OrderDirection.ASK) + symbolSides[0] + else + symbolSides[1] + if (!accountantApiProxy.canCreateOrder( + createOrderRequest.uuid!!, + symbol, + if (createOrderRequest.direction == OrderDirection.ASK) + createOrderRequest.quantity + else + createOrderRequest.quantity.multiply(createOrderRequest.price) + ) + ) { + throwError(OpexError.SubmitOrderForbiddenByAccountant) + } + val pairFeeConfig = pairConfigLoader.load( + createOrderRequest.pair, createOrderRequest.direction, "" + ) + val orderSubmitRequest = OrderSubmitRequest( + UUID.randomUUID().toString(), + createOrderRequest.uuid!! //get from auth2 + , + null, + Pair(symbolSides[0], symbolSides[1]), + createOrderRequest.price.divide( + pairFeeConfig.pairConfig.rightSideFraction + .toBigDecimal() + ).longValueExact(), + createOrderRequest.quantity.divide( + pairFeeConfig.pairConfig.leftSideFraction + .toBigDecimal() + ) + .longValueExact(), + createOrderRequest.direction, + createOrderRequest.matchConstraint, + createOrderRequest.orderType + ) + return orderSubmitter.submit(orderSubmitRequest) + } + + suspend fun cancelOrder(request: CancelOrderRequest): OrderSubmitResult { + val symbols = request.symbol.split("_") + val event = CancelOrderEvent().apply { + ouid = request.ouid + uuid = request.uuid + orderId = request.orderId + pair = Pair(symbols[0], symbols[1]) + } + return eventSubmitter.submit(event) + } +} \ No newline at end of file diff --git a/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/spi/AccountantApiProxy.kt b/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/spi/AccountantApiProxy.kt new file mode 100644 index 000000000..3d0e8b4dd --- /dev/null +++ b/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/spi/AccountantApiProxy.kt @@ -0,0 +1,11 @@ +package co.nilin.opex.app.spi + +import co.nilin.opex.app.inout.PairFeeConfig +import co.nilin.opex.matching.core.model.OrderDirection +import java.math.BigDecimal + + +interface AccountantApiProxy { + suspend fun canCreateOrder(uuid: String, symbol: String, value: BigDecimal): Boolean + suspend fun fetchPairFeeConfig(pair: String, direction: OrderDirection, userLevel: String): PairFeeConfig +} \ No newline at end of file diff --git a/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/spi/PairConfigLoader.kt b/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/spi/PairConfigLoader.kt new file mode 100644 index 000000000..c2108a08b --- /dev/null +++ b/MatchingGateway/gateway-app/src/main/kotlin/co/nilin/opex/app/spi/PairConfigLoader.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.app.spi + +import co.nilin.opex.app.inout.PairFeeConfig +import co.nilin.opex.matching.core.model.OrderDirection + +interface PairConfigLoader { + suspend fun load(pair: String, direction: OrderDirection, userLevel: String): PairFeeConfig +} \ No newline at end of file diff --git a/MatchingGateway/gateway-app/src/main/resources/application-docker.yml b/MatchingGateway/gateway-app/src/main/resources/application-docker.yml new file mode 100644 index 000000000..2d59b2e9c --- /dev/null +++ b/MatchingGateway/gateway-app/src/main/resources/application-docker.yml @@ -0,0 +1,26 @@ +server.port: 8093 +spring: + application: + name: opex-gateway + main: + allow-bean-definition-overriding: false + kafka: + bootstrap-servers: ${KAFKA_IP_PORT} + consumer: + group-id: gateway + cloud: + bootstrap: + enabled: true + consul: + host: ${CONSUL_HOST} + port: 8500 + discovery: + #healthCheckPath: ${management.context-path}/health + instance-id: ${spring.application.name}:${server.port} + healthCheckInterval: 20s + prefer-ip-address: true +app: + accountant: + url: lb://opex-accountant + auth: + cert-url: lb://opex-auth/auth/realms/opex/protocol/openid-connect/certs \ No newline at end of file diff --git a/MatchingGateway/gateway-app/src/main/resources/application.yml b/MatchingGateway/gateway-app/src/main/resources/application.yml new file mode 100644 index 000000000..3ced8b234 --- /dev/null +++ b/MatchingGateway/gateway-app/src/main/resources/application.yml @@ -0,0 +1,29 @@ +server.port: 8093 +logging: + level: + co.nilin: DEBUG + reactor.netty.http.client: DEBUG +spring: + application: + name: opex-gateway + main: + allow-bean-definition-overriding: false + kafka: + bootstrap-servers: localhost:9092 + consumer: + group-id: gateway + cloud: + bootstrap: + enabled: true + consul: + port: 8500 + discovery: + #healthCheckPath: ${management.context-path}/health + instance-id: ${spring.application.name}:${server.port} + healthCheckInterval: 20s + prefer-ip-address: true +app: + accountant: + url: lb://opex-accountant + +swagger.authUrl: https://api.opex.dev \ No newline at end of file diff --git a/MatchingGateway/gateway-port/order-submitter-kafka/.gitignore b/MatchingGateway/gateway-port/order-submitter-kafka/.gitignore new file mode 100644 index 000000000..7f15adddb --- /dev/null +++ b/MatchingGateway/gateway-port/order-submitter-kafka/.gitignore @@ -0,0 +1,77 @@ +# Created by .ignore support plugin (hsz.mobi) +### Kotlin template +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +.idea/artifacts +.idea/compiler.xml +.idea/jarRepositories.xml +.idea/modules.xml +.idea/*.iml +.idea/modules +*.iml +*.ipr + +# File-based project format +*.iws + +# IntelliJ +out/ + +target/ + + + + diff --git a/MatchingGateway/gateway-port/order-submitter-kafka/mvnw b/MatchingGateway/gateway-port/order-submitter-kafka/mvnw new file mode 100644 index 000000000..a16b5431b --- /dev/null +++ b/MatchingGateway/gateway-port/order-submitter-kafka/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/MatchingGateway/gateway-port/order-submitter-kafka/mvnw.cmd b/MatchingGateway/gateway-port/order-submitter-kafka/mvnw.cmd new file mode 100644 index 000000000..c8d43372c --- /dev/null +++ b/MatchingGateway/gateway-port/order-submitter-kafka/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/MatchingGateway/gateway-port/order-submitter-kafka/pom.xml b/MatchingGateway/gateway-port/order-submitter-kafka/pom.xml new file mode 100644 index 000000000..0e915bdbb --- /dev/null +++ b/MatchingGateway/gateway-port/order-submitter-kafka/pom.xml @@ -0,0 +1,113 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.4 + + + co.nilin.opex + gateway-order-submitter-kafka + 1.0-SNAPSHOT + gateway-order-submitter-kafka + Matching gateway kafka order submitter of Opex + + + 1.8 + 1.4.31 + ${version} + + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-webflux + + + co.nilin.opex + matching-core + ${matching.version} + provided + + + org.springframework.kafka + spring-kafka + + + io.projectreactor.kotlin + reactor-kotlin-extensions + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + + + org.jetbrains.kotlinx + kotlinx-coroutines-core + + + org.springframework.kafka + spring-kafka-test + test + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + -Xjsr305=strict + + + spring + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + + diff --git a/MatchingGateway/gateway-port/order-submitter-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/config/OrderKafkaConfig.kt b/MatchingGateway/gateway-port/order-submitter-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/config/OrderKafkaConfig.kt new file mode 100644 index 000000000..540eb2c4d --- /dev/null +++ b/MatchingGateway/gateway-port/order-submitter-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/config/OrderKafkaConfig.kt @@ -0,0 +1,52 @@ +package co.nilin.opex.port.order.kafka.config + +import co.nilin.opex.matching.core.eventh.events.CoreEvent +import co.nilin.opex.port.order.kafka.inout.OrderSubmitRequest +import org.apache.kafka.clients.producer.ProducerConfig +import org.apache.kafka.common.serialization.StringSerializer +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.kafka.core.DefaultKafkaProducerFactory +import org.springframework.kafka.core.KafkaTemplate +import org.springframework.kafka.core.ProducerFactory +import org.springframework.kafka.support.serializer.JsonSerializer +import java.util.* + +@Configuration +class OrderKafkaConfig { + + @Value("\${spring.kafka.bootstrap-servers}") + private lateinit var bootstrapServers: String + + @Bean("orderProducerConfigs") + fun producerConfigs(): Map? { + val props: MutableMap = HashMap() + props[ProducerConfig.BOOTSTRAP_SERVERS_CONFIG] = bootstrapServers + props[ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG] = StringSerializer::class.java + props[ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG] = JsonSerializer::class.java + return props + } + + @Bean("orderProducerFactory") + fun producerFactory(@Qualifier("orderProducerConfigs") producerConfigs: Map): ProducerFactory { + return DefaultKafkaProducerFactory(producerConfigs) + } + + @Bean("orderKafkaTemplate") + fun kafkaTemplate(@Qualifier("orderProducerFactory") producerFactory: ProducerFactory): KafkaTemplate { + return KafkaTemplate(producerFactory) + } + + @Bean("gatewayEventProducerFactory") + fun eventProducerFactory(@Qualifier("orderProducerConfigs") producerConfigs: Map): ProducerFactory { + return DefaultKafkaProducerFactory(producerConfigs) + } + + @Bean("gatewayEventKafkaTemplate") + fun eventKafkaTemplate(@Qualifier("gatewayEventProducerFactory") producerFactory: ProducerFactory): KafkaTemplate { + return KafkaTemplate(producerFactory) + } + +} \ No newline at end of file diff --git a/MatchingGateway/gateway-port/order-submitter-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/inout/OrderSubmitRequest.kt b/MatchingGateway/gateway-port/order-submitter-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/inout/OrderSubmitRequest.kt new file mode 100644 index 000000000..7768d1ef7 --- /dev/null +++ b/MatchingGateway/gateway-port/order-submitter-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/inout/OrderSubmitRequest.kt @@ -0,0 +1,42 @@ +package co.nilin.opex.port.order.kafka.inout + +import co.nilin.opex.matching.core.model.MatchConstraint +import co.nilin.opex.matching.core.model.OrderDirection +import co.nilin.opex.matching.core.model.OrderType +import co.nilin.opex.matching.core.model.Pair + +class OrderSubmitRequest() { + + lateinit var ouid: String + lateinit var uuid: String + var orderId: Long? = null + lateinit var pair: Pair + var price: Long = 0 + var quantity: Long = 0 + var direction: OrderDirection = OrderDirection.BID + var matchConstraint: MatchConstraint = MatchConstraint.GTC + var orderType: OrderType = OrderType.LIMIT_ORDER + + + constructor(ouid: String, + uuid: String, + orderId: Long?, + pair: Pair, + price: Long, + quantity: Long, + direction: OrderDirection, + matchConstraint: MatchConstraint, + orderType: OrderType):this(){ + this.ouid = ouid + this.uuid = uuid + this.orderId = orderId + this.pair = pair + this.price = price + this.quantity = quantity + this.direction = direction + this.matchConstraint = matchConstraint + this.orderType = orderType + } + + +} \ No newline at end of file diff --git a/MatchingGateway/gateway-port/order-submitter-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/inout/OrderSubmitResult.kt b/MatchingGateway/gateway-port/order-submitter-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/inout/OrderSubmitResult.kt new file mode 100644 index 000000000..35d4430ec --- /dev/null +++ b/MatchingGateway/gateway-port/order-submitter-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/inout/OrderSubmitResult.kt @@ -0,0 +1,3 @@ +package co.nilin.opex.port.order.kafka.inout + +class OrderSubmitResult(offset: Long?) \ No newline at end of file diff --git a/MatchingGateway/gateway-port/order-submitter-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/service/EventSubmitter.kt b/MatchingGateway/gateway-port/order-submitter-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/service/EventSubmitter.kt new file mode 100644 index 000000000..5a47b6311 --- /dev/null +++ b/MatchingGateway/gateway-port/order-submitter-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/service/EventSubmitter.kt @@ -0,0 +1,28 @@ +package co.nilin.opex.port.order.kafka.service + +import co.nilin.opex.matching.core.eventh.events.CoreEvent +import co.nilin.opex.port.order.kafka.inout.OrderSubmitResult +import org.slf4j.LoggerFactory +import org.springframework.kafka.core.KafkaTemplate +import org.springframework.stereotype.Component +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + +@Component +class EventSubmitter(val kafkaTemplate: KafkaTemplate) { + + private val logger = LoggerFactory.getLogger(EventSubmitter::class.java) + + suspend fun submit(event: CoreEvent): OrderSubmitResult = suspendCoroutine { + logger.info("Submit event for pair ${event.pair} = ${event::class.java}") + val sendFuture = kafkaTemplate.send("events_${event.pair.leftSideName}_${event.pair.rightSideName}", event) + + sendFuture.addCallback({ sendResult -> + it.resume(OrderSubmitResult(sendResult?.recordMetadata?.offset())) + }, { exception -> + it.resumeWithException(exception) + }) + } + +} \ No newline at end of file diff --git a/MatchingGateway/gateway-port/order-submitter-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/service/OrderSubmitter.kt b/MatchingGateway/gateway-port/order-submitter-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/service/OrderSubmitter.kt new file mode 100644 index 000000000..d8cd3d16e --- /dev/null +++ b/MatchingGateway/gateway-port/order-submitter-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/service/OrderSubmitter.kt @@ -0,0 +1,28 @@ +package co.nilin.opex.port.order.kafka.service + +import co.nilin.opex.port.order.kafka.inout.OrderSubmitRequest +import co.nilin.opex.port.order.kafka.inout.OrderSubmitResult +import org.springframework.kafka.core.KafkaTemplate +import org.springframework.stereotype.Component +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + +@Component +class OrderSubmitter(val kafkaTemplate: KafkaTemplate) { + suspend fun submit(order: OrderSubmitRequest): OrderSubmitResult = suspendCoroutine { cont -> + println("OrderSubmit!") + val sendFuture = kafkaTemplate.send("orders_${order.pair.leftSideName}_${order.pair.rightSideName}", order) + sendFuture.addCallback({ sendResult -> + cont.resume(OrderSubmitResult(sendResult?.recordMetadata?.offset())) + }, { exception -> + cont.resumeWithException(exception) + }) + /*cont.invokeOnCancellation { + sendFuture.cancel(true) + }*/ + } + + + +} \ No newline at end of file diff --git a/MatchingGateway/pom.xml b/MatchingGateway/pom.xml new file mode 100644 index 000000000..c50dd1464 --- /dev/null +++ b/MatchingGateway/pom.xml @@ -0,0 +1,15 @@ + + + 4.0.0 + co.nilin.opex + matching-gateway-root + 1.0-SNAPSHOT + matching-gateway-root + pom + Matching Api Gateway root of Opex + + gateway-app + gateway-port/order-submitter-kafka + + diff --git a/UserManagement/.gitignore b/UserManagement/.gitignore new file mode 100644 index 000000000..807d27eef --- /dev/null +++ b/UserManagement/.gitignore @@ -0,0 +1,76 @@ +# Created by .ignore support plugin (hsz.mobi) +### Kotlin template +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +.idea/artifacts +.idea/compiler.xml +.idea/jarRepositories.xml +.idea/modules.xml +.idea/*.iml +.idea/modules +*.iml +*.ipr + +# File-based project format +*.iws + +# IntelliJ +out/ + +target/ + +.DS_Store + diff --git a/UserManagement/keycloak-gateway/.gitignore b/UserManagement/keycloak-gateway/.gitignore new file mode 100644 index 000000000..90ffaa853 --- /dev/null +++ b/UserManagement/keycloak-gateway/.gitignore @@ -0,0 +1,73 @@ +# Created by .ignore support plugin (hsz.mobi) +### Kotlin template +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +.idea/artifacts +.idea/compiler.xml +.idea/jarRepositories.xml +.idea/modules.xml +.idea/*.iml +.idea/modules +*.iml +*.ipr + +# File-based project format +*.iws + +# IntelliJ +out/ + +target/ diff --git a/UserManagement/keycloak-gateway/Dockerfile b/UserManagement/keycloak-gateway/Dockerfile new file mode 100644 index 000000000..99b369c3c --- /dev/null +++ b/UserManagement/keycloak-gateway/Dockerfile @@ -0,0 +1,6 @@ +FROM openjdk:8-jdk-alpine +VOLUME /tmp +ARG JAR_FILE=target/*.jar +COPY ${JAR_FILE} app.jar +COPY target/classes/opex-master-realm.json opex-master-realm.json +ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"] \ No newline at end of file diff --git a/UserManagement/keycloak-gateway/pom.xml b/UserManagement/keycloak-gateway/pom.xml new file mode 100644 index 000000000..98a1ef20f --- /dev/null +++ b/UserManagement/keycloak-gateway/pom.xml @@ -0,0 +1,239 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.4 + + + co.nilin.opex + keycloak-gateway + 1.0-SNAPSHOT + keycloak-gateway + Keycloak gateway app Opex + + + 13 + 13 + 13 + 1.4.31 + 12.0.4 + 3.13.2.Final + 11.0.10.Final + 2020.0.2 + + + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + com.fasterxml.jackson.module + jackson-module-kotlin + + + org.springframework.boot + spring-boot-configuration-processor + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.cloud + spring-cloud-starter-consul-all + + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.jboss.resteasy + resteasy-jackson2-provider + ${keycloak.resteasy.version} + + + org.jboss.resteasy + resteasy-client + ${keycloak.resteasy.version} + + + org.keycloak + keycloak-dependencies-server-all + ${keycloak.version} + pom + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + org.postgresql + postgresql + runtime + + + com.zaxxer + HikariCP + + + org.springframework.kafka + spring-kafka + + + org.springframework.kafka + spring-kafka-test + test + + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + 2.18 + + + ${skip.unit.tests} + + + **/*IntegrationTest.java + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-test-source + generate-test-sources + + add-test-source + + + + src/test/java + + + + + compile + + add-source + + + + src/main/java + + + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + compile + + + + test-compile + test-compile + + test-compile + + + + + + -Xjsr305=strict + + + spring + + 1.8 + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + compile + compile + + compile + + + + testCompile + test-compile + + testCompile + + + + + + opex-auth + + diff --git a/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/ApplicationContextHolder.kt b/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/ApplicationContextHolder.kt new file mode 100644 index 000000000..29ba3db07 --- /dev/null +++ b/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/ApplicationContextHolder.kt @@ -0,0 +1,11 @@ +package co.nilin.opex.auth.gateway + +import org.springframework.context.ApplicationContext + +class ApplicationContextHolder { + companion object { + var applicationContext: ApplicationContext? = null + fun getCurrentContext(): ApplicationContext? { return applicationContext } + fun setCurrentContext(applicationContext: ApplicationContext) { Companion.applicationContext = applicationContext } + } +} \ No newline at end of file diff --git a/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/KeycloakGatewayApp.kt b/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/KeycloakGatewayApp.kt new file mode 100644 index 000000000..029650524 --- /dev/null +++ b/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/KeycloakGatewayApp.kt @@ -0,0 +1,25 @@ +package co.nilin.opex.auth.gateway + +import co.nilin.opex.auth.gateway.config.KeycloakServerProperties +import co.nilin.opex.auth.gateway.config.SimplePlatformProvider +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration +import org.springframework.boot.autoconfigure.web.ServerProperties +import org.springframework.boot.context.event.ApplicationReadyEvent +import org.springframework.boot.context.properties.EnableConfigurationProperties +import org.springframework.boot.runApplication +import org.springframework.context.ApplicationListener +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.ComponentScan +import org.springframework.core.io.ClassPathResource + +@SpringBootApplication(exclude = [LiquibaseAutoConfiguration::class]) +@ComponentScan(basePackages = arrayOf("co.nilin.opex.auth.gateway")) +@EnableConfigurationProperties +class KeycloakGatewayApp + +fun main(args: Array) { + ApplicationContextHolder.setCurrentContext(runApplication(*args)) +} \ No newline at end of file diff --git a/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/config/AppConfig.kt b/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/config/AppConfig.kt new file mode 100644 index 000000000..dcd562eff --- /dev/null +++ b/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/config/AppConfig.kt @@ -0,0 +1,27 @@ +package co.nilin.opex.auth.gateway.config + +import co.nilin.opex.auth.gateway.KeycloakGatewayApp +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.boot.autoconfigure.web.ServerProperties +import org.springframework.boot.context.event.ApplicationReadyEvent +import org.springframework.context.ApplicationListener +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +class AppConfig { + private val LOG: Logger = LoggerFactory.getLogger(KeycloakGatewayApp::class.java) + + @Bean + fun onApplicationReadyEventListener( + serverProperties: ServerProperties, + keycloakServerProperties: KeycloakServerProperties + ): ApplicationListener { + return ApplicationListener { evt -> + val port = serverProperties.port + val keycloakContextPath = keycloakServerProperties.contextPath + LOG.info("Embedded Keycloak started: http://localhost:{}{} to use keycloak", port, keycloakContextPath) + } + } +} diff --git a/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/config/EmbeddedKeycloakApplication.kt b/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/config/EmbeddedKeycloakApplication.kt new file mode 100644 index 000000000..496e1d39c --- /dev/null +++ b/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/config/EmbeddedKeycloakApplication.kt @@ -0,0 +1,79 @@ +package co.nilin.opex.auth.gateway.config + +import org.keycloak.Config +import org.keycloak.representations.idm.RealmRepresentation + +import org.keycloak.util.JsonSerialization + +import org.springframework.core.io.ClassPathResource + +import org.keycloak.services.managers.RealmManager + +import org.keycloak.services.managers.ApplianceBootstrap + +import java.util.NoSuchElementException + +import org.keycloak.services.util.JsonConfigProviderFactory + +import org.keycloak.services.resources.KeycloakApplication +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.core.io.Resource +import java.lang.Exception + + +class EmbeddedKeycloakApplication() : KeycloakApplication() { + private val LOG: Logger = LoggerFactory.getLogger(EmbeddedKeycloakApplication::class.java) + + companion object { + var keycloakServerProperties: KeycloakServerProperties? = null + } + + init { + createMasterRealmAdminUser() + createOpexRealm() + } + + override fun loadConfig() { + val factory: JsonConfigProviderFactory = RegularJsonConfigProviderFactory() + Config.init(factory.create() + .orElseThrow { NoSuchElementException("No value present") }) + } + + private fun createMasterRealmAdminUser() { + val session = getSessionFactory().create() + val applianceBootstrap = ApplianceBootstrap(session) + val admin = keycloakServerProperties!!.adminUser + try { + session.transactionManager.begin() + applianceBootstrap.createMasterRealmUser(admin.username, admin.password) + session.transactionManager.commit() + } catch (ex: Exception) { + LOG.warn("Couldn't create keycloak master admin user: {}", ex.message) + session.transactionManager.rollback() + } + session.close() + } + + private fun createOpexRealm() { + val session = getSessionFactory().create() + try { + session.transactionManager.begin() + val manager = RealmManager(session) + val realmImportFile: Resource = ClassPathResource( + keycloakServerProperties!!.realmImportFile + ) + manager.importRealm( + JsonSerialization.readValue( + realmImportFile.getInputStream(), + RealmRepresentation::class.java + ) + ) + session.transactionManager.commit() + } catch (ex: Exception) { + LOG.warn("Failed to import Realm json file: {}", ex.message) + session.transactionManager.rollback() + } + session.close() + } +} \ No newline at end of file diff --git a/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/config/EmbeddedKeycloakConfig.kt b/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/config/EmbeddedKeycloakConfig.kt new file mode 100644 index 000000000..cf4c51004 --- /dev/null +++ b/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/config/EmbeddedKeycloakConfig.kt @@ -0,0 +1,77 @@ +package co.nilin.opex.auth.gateway.config + +import org.jboss.resteasy.plugins.server.servlet.HttpServlet30Dispatcher +import org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters +import org.springframework.boot.web.servlet.FilterRegistrationBean +import org.springframework.boot.web.servlet.ServletRegistrationBean +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import java.util.* +import java.util.concurrent.Executors +import javax.naming.* +import javax.naming.spi.InitialContextFactory +import javax.naming.spi.NamingManager +import javax.sql.DataSource + +@Configuration +class EmbeddedKeycloakConfig { + @Bean + @Throws(Exception::class) + fun keycloakJaxRsApplication( + keycloakServerProperties: KeycloakServerProperties, dataSource: DataSource + ): ServletRegistrationBean? { + mockJndiEnvironment(dataSource) + EmbeddedKeycloakApplication.keycloakServerProperties = keycloakServerProperties + val servlet = ServletRegistrationBean( + HttpServlet30Dispatcher() + ) + servlet.addInitParameter("javax.ws.rs.Application", EmbeddedKeycloakApplication::class.java.getName()) + servlet.addInitParameter( + ResteasyContextParameters.RESTEASY_SERVLET_MAPPING_PREFIX, + keycloakServerProperties.contextPath + ) + servlet.addInitParameter(ResteasyContextParameters.RESTEASY_USE_CONTAINER_FORM_PARAMS, "true") + servlet.addUrlMappings(keycloakServerProperties.contextPath + "/*") + servlet.setLoadOnStartup(1) + servlet.setAsyncSupported(true) + return servlet + } + + @Bean + fun keycloakSessionManagement(keycloakServerProperties: KeycloakServerProperties): FilterRegistrationBean? { + val filter: FilterRegistrationBean = + FilterRegistrationBean() + filter.setName("Keycloak Session Management") + filter.setFilter(EmbeddedKeycloakRequestFilter()) + filter.addUrlPatterns(keycloakServerProperties.contextPath + "/*") + return filter + } + + @Throws(NamingException::class) + private fun mockJndiEnvironment(dataSource: DataSource) { + NamingManager.setInitialContextFactoryBuilder { env: Hashtable<*, *>? -> + InitialContextFactory { environment: Hashtable<*, *>? -> + object : InitialContext() { + @Throws(NamingException::class) + fun KeycloakInitialContext(environment: Hashtable<*, *>?) { + } + + @Throws(NamingException::class) + override fun lookup(name: Name): Any? { + return lookup(name.toString()) + } + + @Throws(NamingException::class) + override fun lookup(name: String): Any? { + return Optional.ofNullable(environment!![name]) + .orElseThrow { NamingException("Name $name not found") } + } + + override fun getNameParser(name: String?): NameParser { + return NameParser { CompositeName() } + } + } + } + } + } +} \ No newline at end of file diff --git a/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/config/EmbeddedKeycloakRequestFilter.kt b/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/config/EmbeddedKeycloakRequestFilter.kt new file mode 100644 index 000000000..b9c0637ae --- /dev/null +++ b/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/config/EmbeddedKeycloakRequestFilter.kt @@ -0,0 +1,52 @@ +package co.nilin.opex.auth.gateway.config + +import org.keycloak.common.ClientConnection +import org.keycloak.models.KeycloakSession +import org.keycloak.services.filters.AbstractRequestFilter +import java.io.UnsupportedEncodingException +import java.lang.Exception +import javax.servlet.Filter +import javax.servlet.FilterChain +import javax.servlet.ServletRequest +import javax.servlet.ServletResponse +import javax.servlet.http.HttpServletRequest + + +class EmbeddedKeycloakRequestFilter : AbstractRequestFilter(), Filter { + @Throws(UnsupportedEncodingException::class) + override fun doFilter(servletRequest: ServletRequest, servletResponse: ServletResponse?, filterChain: FilterChain) { + servletRequest.setCharacterEncoding("UTF-8") + val clientConnection = createConnection(servletRequest as HttpServletRequest) + filter(clientConnection) { session: KeycloakSession? -> + try { + filterChain.doFilter(servletRequest, servletResponse) + } catch (e: Exception) { + throw RuntimeException(e) + } + } + } + + private fun createConnection(request: HttpServletRequest): ClientConnection { + return object : ClientConnection { + override fun getRemoteAddr(): String { + return request.getRemoteAddr() + } + + override fun getRemoteHost(): String { + return request.getRemoteHost() + } + + override fun getRemotePort(): Int { + return request.getRemotePort() + } + + override fun getLocalAddr(): String { + return request.getLocalAddr() + } + + override fun getLocalPort(): Int { + return request.getLocalPort() + } + } + } +} \ No newline at end of file diff --git a/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/config/KafkaConfig.kt b/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/config/KafkaConfig.kt new file mode 100644 index 000000000..4c4103150 --- /dev/null +++ b/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/config/KafkaConfig.kt @@ -0,0 +1,47 @@ +package co.nilin.opex.auth.gateway.config + +import co.nilin.opex.auth.gateway.model.AuthEvent +import org.apache.kafka.clients.admin.NewTopic +import org.apache.kafka.common.serialization.StringSerializer +import org.apache.kafka.clients.producer.ProducerConfig +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.context.support.GenericApplicationContext +import org.springframework.kafka.core.DefaultKafkaProducerFactory +import org.springframework.kafka.core.KafkaTemplate +import org.springframework.kafka.core.ProducerFactory +import org.springframework.kafka.support.serializer.JsonSerializer +import java.util.HashMap + +@Configuration +class KafkaConfig { + @Value("\${spring.kafka.bootstrap-servers}") + private lateinit var bootstrapServers: String + + @Bean("authProducerConfigs") + fun producerConfigs(): Map? { + val props: MutableMap = HashMap() + props[ProducerConfig.BOOTSTRAP_SERVERS_CONFIG] = bootstrapServers + props[ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG] = StringSerializer::class.java + props[ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG] = JsonSerializer::class.java + return props + } + + @Bean("authProducerFactory") + fun producerFactory(@Qualifier("authProducerConfigs") producerConfigs: Map): ProducerFactory { + return DefaultKafkaProducerFactory(producerConfigs) + } + + @Bean("authKafkaTemplate") + fun kafkaTemplate(@Qualifier("authProducerFactory") producerFactory: ProducerFactory): KafkaTemplate { + return KafkaTemplate(producerFactory) + } + + @Autowired + fun createUserCreatedTopics(applicationContext: GenericApplicationContext){ + applicationContext.registerBean("topic_auth_user_created", NewTopic::class.java, "auth_user_created", 1 ,1) + } +} \ No newline at end of file diff --git a/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/config/KeycloakServerProperties.kt b/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/config/KeycloakServerProperties.kt new file mode 100644 index 000000000..73f2a7645 --- /dev/null +++ b/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/config/KeycloakServerProperties.kt @@ -0,0 +1,18 @@ +package co.nilin.opex.auth.gateway.config + +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.context.annotation.Configuration + +@Configuration +@ConfigurationProperties(prefix = "keycloak.server") +class KeycloakServerProperties { + var contextPath = "/auth" + var realmImportFile = "/opex-realm.json" + var adminUser = AdminUser() + + class AdminUser { + var username = "admin" + var password = "admin" + } +} + diff --git a/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/config/RegularJsonConfigProviderFactory.kt b/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/config/RegularJsonConfigProviderFactory.kt new file mode 100644 index 000000000..9d1f51313 --- /dev/null +++ b/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/config/RegularJsonConfigProviderFactory.kt @@ -0,0 +1,6 @@ +package co.nilin.opex.auth.gateway.config + +import org.keycloak.services.util.JsonConfigProviderFactory + +class RegularJsonConfigProviderFactory: JsonConfigProviderFactory() { +} \ No newline at end of file diff --git a/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/config/Resteasy3Provider.kt b/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/config/Resteasy3Provider.kt new file mode 100644 index 000000000..19bc13fd4 --- /dev/null +++ b/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/config/Resteasy3Provider.kt @@ -0,0 +1,24 @@ +package co.nilin.opex.auth.gateway.config + +import org.jboss.resteasy.core.Dispatcher +import org.jboss.resteasy.spi.ResteasyProviderFactory +import org.keycloak.common.util.ResteasyProvider + +class Resteasy3Provider: ResteasyProvider { + override fun getContextData(type: Class?): R { + return ResteasyProviderFactory.getContextData(type) + } + + override fun pushDefaultContextObject(type: Class<*>?, instance: Any?) { + getContextData(Dispatcher::class.java).defaultContextObjects[type] = + instance + } + + override fun pushContext(type: Class, instance: Any) { + ResteasyProviderFactory.pushContext(type, instance) + } + + override fun clearContextData() { + ResteasyProviderFactory.clearContextData() + } +} \ No newline at end of file diff --git a/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/config/SimplePlatformProvider.kt b/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/config/SimplePlatformProvider.kt new file mode 100644 index 000000000..bb6cc7f4c --- /dev/null +++ b/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/config/SimplePlatformProvider.kt @@ -0,0 +1,30 @@ +package co.nilin.opex.auth.gateway.config + +import org.keycloak.services.ServicesLogger + +import org.keycloak.platform.PlatformProvider + + +class SimplePlatformProvider : PlatformProvider { + var shutdownHook: Runnable? = null + override fun onStartup(startupHook: Runnable) { + startupHook.run() + } + + override fun onShutdown(shutdownHook: Runnable) { + this.shutdownHook = shutdownHook + } + + override fun exit(cause: Throwable) { + ServicesLogger.LOGGER.fatal(cause) + exit(1) + } + + private fun exit(status: Int) { + object : Thread() { + override fun run() { + System.exit(status) + } + }.start() + } +} \ No newline at end of file diff --git a/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/config/SystemPropertyConfig.kt b/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/config/SystemPropertyConfig.kt new file mode 100644 index 000000000..9fe7d25c3 --- /dev/null +++ b/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/config/SystemPropertyConfig.kt @@ -0,0 +1,47 @@ +package co.nilin.opex.auth.gateway.config + +import org.springframework.beans.factory.BeanFactory +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.config.ConfigurableBeanFactory +import org.springframework.context.annotation.Configuration +import org.springframework.core.env.StandardEnvironment +import org.springframework.core.env.MapPropertySource +import org.springframework.core.io.ClassPathResource +import java.io.File +import java.util.* + + +@Configuration +class SystemPropertyConfig { + @Autowired + fun addYmlToSystemProperty( + env: StandardEnvironment, + beanFactory: ConfigurableBeanFactory + ) { + for (propertySource in env.propertySources) { + if (propertySource is MapPropertySource) { + val propertySourceName = propertySource.getName() + val sysProperties: Properties = System.getProperties() + println("setting sysprops from $propertySourceName") + val mapPropertySource = propertySource + println("source.name: ${mapPropertySource.name}") + for (key in mapPropertySource.propertyNames) { + val value = mapPropertySource.getProperty(key) + if (sysProperties[key] == null) { + if (value is String) { + var resolvedValue = beanFactory.resolveEmbeddedValue("\${$key}") ?: value + if (resolvedValue.startsWith("classpath:")) { + resolvedValue = + ClassPathResource(resolvedValue.substring("classpath:".length)).file.absolutePath + } + sysProperties[key] = resolvedValue + } else { + sysProperties[key] = value + } + println("$key -> ${sysProperties[key]}") + } + } + } + } + } +} diff --git a/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/extension/ExtendedEventListenerProvider.kt b/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/extension/ExtendedEventListenerProvider.kt new file mode 100644 index 000000000..a29f9ffc3 --- /dev/null +++ b/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/extension/ExtendedEventListenerProvider.kt @@ -0,0 +1,122 @@ +package co.nilin.opex.auth.gateway.extension + +import co.nilin.opex.auth.gateway.ApplicationContextHolder +import co.nilin.opex.auth.gateway.model.AuthEvent +import co.nilin.opex.auth.gateway.model.UserCreatedEvent +import com.fasterxml.jackson.databind.DeserializationFeature +import org.keycloak.events.Event +import org.keycloak.events.EventListenerProvider +import org.keycloak.events.EventType +import org.keycloak.events.admin.AdminEvent +import org.keycloak.events.admin.OperationType +import org.keycloak.events.admin.ResourceType +import org.keycloak.models.AbstractKeycloakTransaction +import org.keycloak.models.KeycloakSession +import org.keycloak.models.RealmProvider +import org.slf4j.LoggerFactory +import org.springframework.kafka.core.KafkaTemplate + +class ExtendedEventListenerProvider(private val session: KeycloakSession) : EventListenerProvider { + val logger = LoggerFactory.getLogger(ExtendedEventListenerProvider::class.java) + private val model: RealmProvider + val objectMapper = com.fasterxml.jackson.module.kotlin.jacksonObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + + data class UserData( + val username: String, + val enabled: Boolean, + val emailVerified: Boolean, + val firstName: String, + val lastName: String, + val email: String + ) + + data class UserUuidDto(val type: String, val uuid: String, val email: String) + + + override fun onEvent(event: Event) { + logger.info("## NEW %s EVENT", event.getType()) + logger.info("-----------------------------------------------------------") + event.getDetails().forEach { key, value -> logger.info(key.toString() + ": " + value) } + + // USE CASE SCENARIO, I'm sure there are better use case scenario's :p + // + // Let's assume for whatever reason you only want the user + // to be able to verify his account if a transaction we make succeeds. + // Let's say an external call to a service needs to return a 200 response code or we throw an exception. + + // When the user tries to login after a failed attempt, + // the user remains unverified and when trying to login will receive another verify account email. + if (EventType.VERIFY_EMAIL.equals(event.getType())) { + val realm = model.getRealm(event.getRealmId()) + val user = session.users().getUserById(event.getUserId(), realm) + if (user != null && user.email != null && user.isEmailVerified) { + logger.info("USER HAS VERIFIED EMAIL : " + event.getUserId()) + + // Example of adding an attribute when this event happens + user.setSingleAttribute("attribute-key", "attribute-value") + val userUuidDto = UserUuidDto(event.getType().name, event.getUserId(), user.email) + val userVerifiedTransaction = UserVerifiedTransaction(userUuidDto) + + // enlistPrepare -> if our transaction fails than the user is NOT verified + // enlist -> if our transaction fails than the user is still verified + // enlistAfterCompletion -> if our transaction fails our user is still verified + session.transactionManager.enlistPrepare(userVerifiedTransaction) + } + } + logger.info("-----------------------------------------------------------") + } + + override fun onEvent(adminEvent: AdminEvent, b: Boolean) { + logger.info("## NEW ADMIN EVENT") + logger.info("-----------------------------------------------------------") + logger.info("Resource path" + ": " + adminEvent.resourcePath) + logger.info("Resource type" + ": " + adminEvent.resourceType) + logger.info("Operation type" + ": " + adminEvent.operationType) + if (ResourceType.USER.equals(adminEvent.resourceType) + && OperationType.CREATE.equals(adminEvent.operationType) + ) { + logger.info("A new user has been created") + val userData = objectMapper.readValue(adminEvent.representation, UserData::class.java) + val uuid = adminEvent.resourcePath.substringAfter("/") + val kafkaEvent = UserCreatedEvent(uuid, userData.firstName, userData.lastName, userData.email) + (ApplicationContextHolder.getCurrentContext()!! + .getBean("authKafkaTemplate") as KafkaTemplate) + .send("auth_user_created", kafkaEvent) + logger.info("{} produced in kafka topic", kafkaEvent) + } + logger.info("-----------------------------------------------------------") + } + + override fun close() { + // Nothing to close + } + + + init { + model = session.realms() + } + + class UserVerifiedTransaction(private val userUuidDto: UserUuidDto) : AbstractKeycloakTransaction() { + override fun commitImpl() { + logger.info("## USER VERIFIED TRANSACTION") + logger.info("-----------------------------------------------------------") + logger.info(userUuidDto.toString()) + logger.info("-----------------------------------------------------------") + + // You could make a http call here and send the object. + // When we throw an exception here, the user would not be verified when using .enlistPrepare + //throw new RuntimeException("External call failed!"); + try { + + } catch (e: Exception) { + throw RuntimeException("##### USER VERIFIED TRANSACTION FAILED !", e) + } + } + + override fun rollbackImpl() { + // + } + + } +} \ No newline at end of file diff --git a/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/extension/ExtendedEventListenerProviderFactory.kt b/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/extension/ExtendedEventListenerProviderFactory.kt new file mode 100644 index 000000000..b9b381619 --- /dev/null +++ b/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/extension/ExtendedEventListenerProviderFactory.kt @@ -0,0 +1,28 @@ +package co.nilin.opex.auth.gateway.extension + +import org.keycloak.Config +import org.keycloak.events.EventListenerProviderFactory +import org.keycloak.models.KeycloakSession +import org.keycloak.models.KeycloakSessionFactory + +class ExtendedEventListenerProviderFactory : EventListenerProviderFactory { + override fun create(keycloakSession: KeycloakSession): ExtendedEventListenerProvider { + return ExtendedEventListenerProvider(keycloakSession) + } + + override fun init(scope: Config.Scope) { + // + } + + override fun postInit(keycloakSessionFactory: KeycloakSessionFactory) { + // + } + + override fun close() { + // + } + + override fun getId(): String { + return "pl_event_listener" + } +} \ No newline at end of file diff --git a/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/model/AuthEvent.kt b/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/model/AuthEvent.kt new file mode 100644 index 000000000..0ecf3780e --- /dev/null +++ b/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/model/AuthEvent.kt @@ -0,0 +1,7 @@ +package co.nilin.opex.auth.gateway.model + +import java.time.LocalDateTime + +open class AuthEvent { + var eventDate: LocalDateTime = LocalDateTime.now() +} \ No newline at end of file diff --git a/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/model/UserCreatedEvent.kt b/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/model/UserCreatedEvent.kt new file mode 100644 index 000000000..e80ade9ca --- /dev/null +++ b/UserManagement/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/model/UserCreatedEvent.kt @@ -0,0 +1,23 @@ +package co.nilin.opex.auth.gateway.model + +class UserCreatedEvent: AuthEvent { + lateinit var uuid: String + lateinit var firstName: String + lateinit var lastName: String + lateinit var email: String + + + constructor(uuid: String, firstName: String, lastName: String, email: String) : super() { + this.uuid = uuid + this.firstName = firstName + this.lastName = lastName + this.email = email + } + + constructor() : super() + + override fun toString(): String { + return "UserCreatedEvent(uuid='$uuid', firstName='$firstName', lastName='$lastName', email='$email')" + } + +} \ No newline at end of file diff --git a/UserManagement/keycloak-gateway/src/main/resources/META-INF/keycloak-server.json b/UserManagement/keycloak-gateway/src/main/resources/META-INF/keycloak-server.json new file mode 100644 index 000000000..45b4bfc91 --- /dev/null +++ b/UserManagement/keycloak-gateway/src/main/resources/META-INF/keycloak-server.json @@ -0,0 +1,240 @@ +{ + + "hostname": { + "provider": "${keycloak.hostname.provider:default}", + + "fixed": { + "hostname": "${keycloak.hostname.fixed.hostname:localhost}", + "httpPort": "${keycloak.hostname.fixed.httpPort:-1}", + "httpsPort": "${keycloak.hostname.fixed.httpsPort:-1}", + "alwaysHttps": "${keycloak.hostname.fixed.alwaysHttps:false}" + }, + + "default": { + "frontendUrl": "${keycloak.frontendUrl:}", + "adminUrl": "${keycloak.adminUrl:}", + "forceBackendUrlToFrontendUrl": "${keycloak.hostname.default.forceBackendUrlToFrontendUrl:false}" + } + }, + + "admin": { + "realm": "master" + }, + + "eventsStore": { + "provider": "${keycloak.eventsStore.provider:jpa}", + "jpa": { + "max-detail-length": "${keycloak.eventsStore.maxDetailLength:1000}" + } + }, + + "eventsListener": { + "jboss-logging" : { + "success-level": "debug", + "error-level": "warn" + }, + "event-queue": {} + }, + + "realm": { + "provider": "${keycloak.realm.provider:jpa}" + }, + + "user": { + "provider": "${keycloak.user.provider:jpa}" + }, + + "client": { + "provider": "${keycloak.client.provider:jpa}" + }, + + "clientScope": { + "provider": "${keycloak.clientScope.provider:jpa}" + }, + + "group": { + "provider": "${keycloak.group.provider:jpa}" + }, + + "role": { + "provider": "${keycloak.role.provider:jpa}" + }, + + "authenticationSessions": { + "provider": "${keycloak.authSession.provider:infinispan}" + }, + + "mapStorage": { + "provider": "${keycloak.mapStorage.provider:concurrenthashmap}", + "concurrenthashmap": { + "dir": "${project.build.directory:target}" + } + }, + + "userFederatedStorage": { + "provider": "${keycloak.userFederatedStorage.provider:jpa}" + }, + + "userSessionPersister": { + "provider": "${keycloak.userSessionPersister.provider:jpa}" + }, + + "authorizationPersister": { + "provider": "${keycloak.authorization.provider:jpa}" + }, + + "userCache": { + "provider": "${keycloak.user.cache.provider:default}", + "default" : { + "enabled": true + }, + "mem": { + "maxSize": 20000 + } + }, + + "userSessions": { + "provider" : "${keycloak.userSessions.provider:infinispan}" + }, + + "timer": { + "provider": "basic" + }, + + "theme": { + "staticMaxAge": "${keycloak.theme.staticMaxAge:2592000}", + "cacheTemplates": "${keycloak.theme.cacheTemplates:true}", + "cacheThemes": "${keycloak.theme.cacheThemes:true}", + "folder": { + "dir": "${keycloak.theme.dir}" + } + }, + + "login": { + "provider": "freemarker" + }, + + "account": { + "provider": "freemarker" + }, + + "email": { + "provider": "freemarker" + }, + + "scheduled": { + "interval": 900 + }, + + "connectionsHttpClient": { + "default": { + "reuse-connections": false + } + }, + + + "connectionsJpa": { + "default": { + "url": "${spring.datasource.url}", + "driver": "${spring.datasource.driver-class-name}", + "driverDialect": "${spring.jpa.properties.hibernate.dialect}", + "user": "${spring.datasource.username}", + "password": "${spring.datasource.password}", + "initializeEmpty": true, + "migrationStrategy": "update", + "showSql": "true", + "formatSql": "true", + "globalStatsInterval": "-1" + } + }, + + "realmCache": { + "provider": "${keycloak.realm.cache.provider:default}", + "default" : { + "enabled": true + } + }, + + "connectionsInfinispan": { + "default": { + "jgroupsUdpMcastAddr": "${keycloak.connectionsInfinispan.jgroupsUdpMcastAddr:234.56.78.90}", + "nodeName": "${keycloak.connectionsInfinispan.nodeName,jboss.node.name:}", + "siteName": "${keycloak.connectionsInfinispan.siteName,jboss.site.name:}", + "clustered": "${keycloak.connectionsInfinispan.clustered:false}", + "async": "${keycloak.connectionsInfinispan.async:false}", + "sessionsOwners": "${keycloak.connectionsInfinispan.sessionsOwners:1}", + "l1Lifespan": "${keycloak.connectionsInfinispan.l1Lifespan:600000}", + "remoteStoreEnabled": "${keycloak.connectionsInfinispan.remoteStoreEnabled:false}", + "remoteStoreHost": "${keycloak.connectionsInfinispan.remoteStoreServer:localhost}", + "remoteStorePort": "${keycloak.connectionsInfinispan.remoteStorePort:11222}", + "hotrodProtocolVersion": "${keycloak.connectionsInfinispan.hotrodProtocolVersion}", + "embedded": "${keycloak.connectionsInfinispan.embedded:true}" + } + }, + + "truststore": { + "file": { + "disabled": "${keycloak.truststore.disabled:true}" + } + }, + + "jta-lookup": { + "provider": "${keycloak.jta.lookup.provider:jboss}", + "jboss" : { + "enabled": true + } + + }, + + "login-protocol": { + "saml": { + "knownProtocols": [ + "http=${auth.server.http.port}", + "https=${auth.server.https.port}" + ] + } + }, + + "userProfile": { + "legacy-user-profile": { + "read-only-attributes": [ "deniedFoo", "deniedBar*", "deniedSome/thing", "deniedsome*thing" ], + "admin-read-only-attributes": [ "deniedSomeAdmin" ] + } + }, + + "x509cert-lookup": { + "provider": "${keycloak.x509cert.lookup.provider:default}", + "default": { + "enabled": true + }, + "haproxy": { + "enabled": true, + "sslClientCert": "x-ssl-client-cert", + "sslCertChainPrefix": "x-ssl-client-cert-chain", + "certificateChainLength": 1 + }, + "apache": { + "enabled": true, + "sslClientCert": "x-ssl-client-cert", + "sslCertChainPrefix": "x-ssl-client-cert-chain", + "certificateChainLength": 1 + }, + "nginx": { + "enabled": true, + "sslClientCert": "x-ssl-client-cert", + "sslCertChainPrefix": "x-ssl-client-cert-chain", + "certificateChainLength": 1 + } + }, + + "vault": { + "files-plaintext": { + "dir": "target/dependency/vault", + "enabled": "${keycloak.vault.files-plaintext.provider.enabled:false}" + } + }, + + "saml-artifact-resolver": { + "provider": "${keycloak.saml-artifact-resolver.provider:default}" + } +} diff --git a/UserManagement/keycloak-gateway/src/main/resources/META-INF/services/org.keycloak.common.util.ResteasyProvider b/UserManagement/keycloak-gateway/src/main/resources/META-INF/services/org.keycloak.common.util.ResteasyProvider new file mode 100644 index 000000000..86578665f --- /dev/null +++ b/UserManagement/keycloak-gateway/src/main/resources/META-INF/services/org.keycloak.common.util.ResteasyProvider @@ -0,0 +1 @@ +co.nilin.opex.auth.gateway.config.Resteasy3Provider \ No newline at end of file diff --git a/UserManagement/keycloak-gateway/src/main/resources/META-INF/services/org.keycloak.events.EventListenerProviderFactory b/UserManagement/keycloak-gateway/src/main/resources/META-INF/services/org.keycloak.events.EventListenerProviderFactory new file mode 100644 index 000000000..6cffc650f --- /dev/null +++ b/UserManagement/keycloak-gateway/src/main/resources/META-INF/services/org.keycloak.events.EventListenerProviderFactory @@ -0,0 +1 @@ +co.nilin.opex.auth.gateway.extension.ExtendedEventListenerProviderFactory \ No newline at end of file diff --git a/UserManagement/keycloak-gateway/src/main/resources/META-INF/services/org.keycloak.platform.PlatformProvider b/UserManagement/keycloak-gateway/src/main/resources/META-INF/services/org.keycloak.platform.PlatformProvider new file mode 100644 index 000000000..a30ad70f1 --- /dev/null +++ b/UserManagement/keycloak-gateway/src/main/resources/META-INF/services/org.keycloak.platform.PlatformProvider @@ -0,0 +1 @@ +co.nilin.opex.auth.gateway.config.SimplePlatformProvider \ No newline at end of file diff --git a/UserManagement/keycloak-gateway/src/main/resources/application-docker.yml b/UserManagement/keycloak-gateway/src/main/resources/application-docker.yml new file mode 100644 index 000000000..f52df428b --- /dev/null +++ b/UserManagement/keycloak-gateway/src/main/resources/application-docker.yml @@ -0,0 +1,17 @@ +spring: + kafka: + bootstrap-servers: ${KAFKA_IP_PORT} + datasource: + url: jdbc:postgresql://${DB_IP_PORT}/opex_auth + username: opex + password: hiopex + cloud: + consul: + host: ${CONSUL_HOST} + port: 8500 + +keycloak: + migration: + file: /opex-master-realm.json + adminUrl: https://api.opex.dev/auth + frontendUrl: https://api.opex.dev/auth diff --git a/UserManagement/keycloak-gateway/src/main/resources/application.yml b/UserManagement/keycloak-gateway/src/main/resources/application.yml new file mode 100644 index 000000000..d51e1ef4c --- /dev/null +++ b/UserManagement/keycloak-gateway/src/main/resources/application.yml @@ -0,0 +1,46 @@ +server.port: 8083 +spring: + application: + name: opex-auth + main: + allow-bean-definition-overriding: false + kafka: + bootstrap-servers: localhost:9092 + consumer: + group-id: auth + datasource: + platform: postgres + driver-class-name: org.postgresql.Driver + url: jdbc:postgresql://localhost/opex_auth + username: opex + password: hiopex + initialization-mode: always + jpa: + hibernate: + ddl-auto: none + properties.hibernate: + jdbc.lob.non_contextual_creation: true + dialect: org.hibernate.dialect.PostgreSQLDialect + open-in-view: false + cloud: + bootstrap: + enabled: true + consul: + port: 8500 + discovery: + #healthCheckPath: ${management.context-path}/health + instance-id: ${spring.application.name}:${server.port} + healthCheckInterval: 20s + prefer-ip-address: true +keycloak: + server: + contextPath: /auth + adminUser: + username: opex-admin + password: hiopex + realmImportFile: /opex-realm.json + migration: + action: import + provider: singleFile + file: classpath:/opex-master-realm.json + strategy: OVERWRITE_EXISTING diff --git a/UserManagement/keycloak-gateway/src/main/resources/opex-master-realm.json b/UserManagement/keycloak-gateway/src/main/resources/opex-master-realm.json new file mode 100644 index 000000000..7ee034e06 --- /dev/null +++ b/UserManagement/keycloak-gateway/src/main/resources/opex-master-realm.json @@ -0,0 +1,47 @@ +{ + "id" : "master", + "realm" : "master", + "notBefore" : 0, + "revokeRefreshToken" : false, + "refreshTokenMaxReuse" : 0, + "accessTokenLifespan" : 300, + "accessTokenLifespanForImplicitFlow" : 900, + "ssoSessionIdleTimeout" : 1800, + "ssoSessionMaxLifespan" : 36000, + "ssoSessionIdleTimeoutRememberMe" : 0, + "ssoSessionMaxLifespanRememberMe" : 0, + "offlineSessionIdleTimeout" : 2592000, + "offlineSessionMaxLifespanEnabled" : false, + "offlineSessionMaxLifespan" : 5184000, + "accessCodeLifespan" : 60, + "accessCodeLifespanUserAction" : 300, + "accessCodeLifespanLogin" : 1800, + "actionTokenGeneratedByAdminLifespan" : 43200, + "actionTokenGeneratedByUserLifespan" : 300, + "enabled" : true, + "sslRequired" : "none", + "registrationAllowed" : true, + "registrationEmailAsUsername" : false, + "rememberMe" : false, + "verifyEmail" : false, + "loginWithEmailAllowed" : true, + "duplicateEmailsAllowed" : false, + "resetPasswordAllowed" : true, + "editUsernameAllowed" : false, + "bruteForceProtected" : true, + "permanentLockout" : false, + "maxFailureWaitSeconds" : 900, + "minimumQuickLoginWaitSeconds" : 60, + "waitIncrementSeconds" : 60, + "quickLoginCheckMilliSeconds" : 1000, + "maxDeltaTimeSeconds" : 43200, + "failureFactor" : 30, + "smtpServer" : { + "host": "smtp.elasticemail.com", + "port": 2525, + "from": "for.demo.purpose.only@opex.dev", + "auth": true, + "user": "for.demo.purpose.only@opex.dev", + "password": "642467973026C6F093FB1E39C4BFC0D15042" + } +} \ No newline at end of file diff --git a/UserManagement/keycloak-gateway/src/main/resources/opex-realm.json b/UserManagement/keycloak-gateway/src/main/resources/opex-realm.json new file mode 100644 index 000000000..bccc3db87 --- /dev/null +++ b/UserManagement/keycloak-gateway/src/main/resources/opex-realm.json @@ -0,0 +1,2301 @@ +{ + "id": "opex", + "realm": "opex", + "notBefore": 0, + "revokeRefreshToken": false, + "refreshTokenMaxReuse": 0, + "accessTokenLifespan": 300, + "accessTokenLifespanForImplicitFlow": 900, + "ssoSessionIdleTimeout": 1800, + "ssoSessionMaxLifespan": 36000, + "ssoSessionIdleTimeoutRememberMe": 0, + "ssoSessionMaxLifespanRememberMe": 0, + "offlineSessionIdleTimeout": 2592000, + "offlineSessionMaxLifespanEnabled": false, + "offlineSessionMaxLifespan": 5184000, + "clientSessionIdleTimeout": 0, + "clientSessionMaxLifespan": 0, + "clientOfflineSessionIdleTimeout": 0, + "clientOfflineSessionMaxLifespan": 0, + "accessCodeLifespan": 60, + "accessCodeLifespanUserAction": 300, + "accessCodeLifespanLogin": 1800, + "actionTokenGeneratedByAdminLifespan": 43200, + "actionTokenGeneratedByUserLifespan": 300, + "enabled": true, + "sslRequired": "none", + "registrationAllowed": true, + "registrationEmailAsUsername": false, + "rememberMe": false, + "verifyEmail": false, + "loginWithEmailAllowed": true, + "duplicateEmailsAllowed": false, + "resetPasswordAllowed": true, + "editUsernameAllowed": false, + "bruteForceProtected": false, + "permanentLockout": false, + "maxFailureWaitSeconds": 900, + "minimumQuickLoginWaitSeconds": 60, + "waitIncrementSeconds": 60, + "quickLoginCheckMilliSeconds": 1000, + "maxDeltaTimeSeconds": 43200, + "failureFactor": 30, + "roles": { + "realm": [ + { + "id": "3b6109f5-6e5a-4578-83c3-791ec3e2bf9e", + "name": "offline_access", + "description": "${role_offline-access}", + "composite": false, + "clientRole": false, + "containerId": "opex", + "attributes": {} + }, + { + "id": "0dd6a8c7-d669-4941-9ea1-521980e9c53f", + "name": "uma_authorization", + "description": "${role_uma_authorization}", + "composite": false, + "clientRole": false, + "containerId": "opex", + "attributes": {} + }, + { + "id": "ca962095-7f9b-49e2-a190-e391a0d4b704", + "name": "user", + "composite": false, + "clientRole": false, + "containerId": "opex", + "attributes": {} + } + ], + "client": { + "newClient": [], + "realm-management": [ + { + "id": "5d00243f-ceec-4b0c-995e-d86d5b8a0ae6", + "name": "view-clients", + "description": "${role_view-clients}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-clients" + ] + } + }, + "clientRole": true, + "containerId": "6a4bfbd0-576d-4778-af56-56f876647355", + "attributes": {} + }, + { + "id": "941612de-bd85-47a5-8dfa-37c270dde28c", + "name": "view-authorization", + "description": "${role_view-authorization}", + "composite": false, + "clientRole": true, + "containerId": "6a4bfbd0-576d-4778-af56-56f876647355", + "attributes": {} + }, + { + "id": "5ea9810d-63cc-4277-9b32-ba8a3d3c6091", + "name": "manage-realm", + "description": "${role_manage-realm}", + "composite": false, + "clientRole": true, + "containerId": "6a4bfbd0-576d-4778-af56-56f876647355", + "attributes": {} + }, + { + "id": "8b7b0dd8-350b-473e-b8cd-8acad34f1358", + "name": "query-clients", + "description": "${role_query-clients}", + "composite": false, + "clientRole": true, + "containerId": "6a4bfbd0-576d-4778-af56-56f876647355", + "attributes": {} + }, + { + "id": "0f8e5ee8-b014-4b7c-9b69-50f46abcba5f", + "name": "query-groups", + "description": "${role_query-groups}", + "composite": false, + "clientRole": true, + "containerId": "6a4bfbd0-576d-4778-af56-56f876647355", + "attributes": {} + }, + { + "id": "911b1489-9383-4734-b134-bf49bf992ce9", + "name": "manage-clients", + "description": "${role_manage-clients}", + "composite": false, + "clientRole": true, + "containerId": "6a4bfbd0-576d-4778-af56-56f876647355", + "attributes": {} + }, + { + "id": "5d48274c-bd6b-4c26-ad54-f1a2254beac0", + "name": "view-realm", + "description": "${role_view-realm}", + "composite": false, + "clientRole": true, + "containerId": "6a4bfbd0-576d-4778-af56-56f876647355", + "attributes": {} + }, + { + "id": "3ea43b64-316f-4693-8346-9ee78b24adaf", + "name": "manage-identity-providers", + "description": "${role_manage-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "6a4bfbd0-576d-4778-af56-56f876647355", + "attributes": {} + }, + { + "id": "49735614-96ec-49b2-98fe-3af9bcd1a33a", + "name": "create-client", + "description": "${role_create-client}", + "composite": false, + "clientRole": true, + "containerId": "6a4bfbd0-576d-4778-af56-56f876647355", + "attributes": {} + }, + { + "id": "e8f8c3cc-0ff1-4f72-a271-db6821a3cdb6", + "name": "manage-users", + "description": "${role_manage-users}", + "composite": false, + "clientRole": true, + "containerId": "6a4bfbd0-576d-4778-af56-56f876647355", + "attributes": {} + }, + { + "id": "387418b1-4f80-4b00-b9dd-805ca041f805", + "name": "view-identity-providers", + "description": "${role_view-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "6a4bfbd0-576d-4778-af56-56f876647355", + "attributes": {} + }, + { + "id": "427c27d4-521a-464b-a0df-16d7f537e8d5", + "name": "realm-admin", + "description": "${role_realm-admin}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "view-clients", + "view-authorization", + "manage-realm", + "query-clients", + "query-groups", + "manage-clients", + "view-realm", + "manage-identity-providers", + "create-client", + "manage-users", + "view-identity-providers", + "query-users", + "query-realms", + "view-users", + "manage-authorization", + "impersonation", + "view-events", + "manage-events" + ] + } + }, + "clientRole": true, + "containerId": "6a4bfbd0-576d-4778-af56-56f876647355", + "attributes": {} + }, + { + "id": "a574cf01-03e4-4573-ab9e-276d13a1ce8d", + "name": "query-users", + "description": "${role_query-users}", + "composite": false, + "clientRole": true, + "containerId": "6a4bfbd0-576d-4778-af56-56f876647355", + "attributes": {} + }, + { + "id": "c3a253a8-a1b6-4d38-9677-f728f32482ad", + "name": "query-realms", + "description": "${role_query-realms}", + "composite": false, + "clientRole": true, + "containerId": "6a4bfbd0-576d-4778-af56-56f876647355", + "attributes": {} + }, + { + "id": "f3cb93da-273e-419a-b2f4-93f09896abcf", + "name": "view-users", + "description": "${role_view-users}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-users", + "query-groups" + ] + } + }, + "clientRole": true, + "containerId": "6a4bfbd0-576d-4778-af56-56f876647355", + "attributes": {} + }, + { + "id": "0332e99b-3dfc-4193-9e13-5728f8f3e6d6", + "name": "impersonation", + "description": "${role_impersonation}", + "composite": false, + "clientRole": true, + "containerId": "6a4bfbd0-576d-4778-af56-56f876647355", + "attributes": {} + }, + { + "id": "6eedf2b7-50ef-4495-a89b-54aef751b7fa", + "name": "manage-authorization", + "description": "${role_manage-authorization}", + "composite": false, + "clientRole": true, + "containerId": "6a4bfbd0-576d-4778-af56-56f876647355", + "attributes": {} + }, + { + "id": "aac3def5-f193-4a6c-9065-1667a0746a8a", + "name": "manage-events", + "description": "${role_manage-events}", + "composite": false, + "clientRole": true, + "containerId": "6a4bfbd0-576d-4778-af56-56f876647355", + "attributes": {} + }, + { + "id": "b690cb9c-0f4a-4be5-ade0-b40443d8149d", + "name": "view-events", + "description": "${role_view-events}", + "composite": false, + "clientRole": true, + "containerId": "6a4bfbd0-576d-4778-af56-56f876647355", + "attributes": {} + } + ], + "security-admin-console": [], + "admin-cli": [], + "account-console": [], + "broker": [ + { + "id": "397b5703-4c81-48fd-a24c-a7e8177ef657", + "name": "read-token", + "description": "${role_read-token}", + "composite": false, + "clientRole": true, + "containerId": "4b9609f0-48d1-4e71-9381-2ecec08616f9", + "attributes": {} + } + ], + "account": [ + { + "id": "8daa8096-d14e-4d1c-ad1f-83f822016aa1", + "name": "manage-account", + "description": "${role_manage-account}", + "composite": true, + "composites": { + "client": { + "account": [ + "manage-account-links" + ] + } + }, + "clientRole": true, + "containerId": "12eebf0b-a3eb-49f8-9ecf-173cf8a00145", + "attributes": {} + }, + { + "id": "cc86369d-55fc-47ed-9592-9e89116032c0", + "name": "view-consent", + "description": "${role_view-consent}", + "composite": false, + "clientRole": true, + "containerId": "12eebf0b-a3eb-49f8-9ecf-173cf8a00145", + "attributes": {} + }, + { + "id": "ca726012-91f9-4c58-bc7e-e435c9c244ab", + "name": "manage-consent", + "description": "${role_manage-consent}", + "composite": true, + "composites": { + "client": { + "account": [ + "view-consent" + ] + } + }, + "clientRole": true, + "containerId": "12eebf0b-a3eb-49f8-9ecf-173cf8a00145", + "attributes": {} + }, + { + "id": "f6839de1-e7fc-42bb-be60-578ea3d9361b", + "name": "view-applications", + "description": "${role_view-applications}", + "composite": false, + "clientRole": true, + "containerId": "12eebf0b-a3eb-49f8-9ecf-173cf8a00145", + "attributes": {} + }, + { + "id": "948269c7-a69c-4c82-a7f3-88868713dfd9", + "name": "manage-account-links", + "description": "${role_manage-account-links}", + "composite": false, + "clientRole": true, + "containerId": "12eebf0b-a3eb-49f8-9ecf-173cf8a00145", + "attributes": {} + }, + { + "id": "ee9cf17f-b9b1-4f4a-b521-83b5d1d7388b", + "name": "delete-account", + "description": "${role_delete-account}", + "composite": false, + "clientRole": true, + "containerId": "12eebf0b-a3eb-49f8-9ecf-173cf8a00145", + "attributes": {} + }, + { + "id": "aed18201-2433-4998-8fa3-0979b0b31c10", + "name": "view-profile", + "description": "${role_view-profile}", + "composite": false, + "clientRole": true, + "containerId": "12eebf0b-a3eb-49f8-9ecf-173cf8a00145", + "attributes": {} + } + ] + } + }, + "groups": [], + "defaultRoles": [ + "offline_access", + "uma_authorization" + ], + "requiredCredentials": [ + "password" + ], + "otpPolicyType": "totp", + "otpPolicyAlgorithm": "HmacSHA1", + "otpPolicyInitialCounter": 0, + "otpPolicyDigits": 6, + "otpPolicyLookAheadWindow": 1, + "otpPolicyPeriod": 30, + "otpSupportedApplications": [ + "FreeOTP", + "Google Authenticator" + ], + "webAuthnPolicyRpEntityName": "keycloak", + "webAuthnPolicySignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyRpId": "", + "webAuthnPolicyAttestationConveyancePreference": "not specified", + "webAuthnPolicyAuthenticatorAttachment": "not specified", + "webAuthnPolicyRequireResidentKey": "not specified", + "webAuthnPolicyUserVerificationRequirement": "not specified", + "webAuthnPolicyCreateTimeout": 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyAcceptableAaguids": [], + "webAuthnPolicyPasswordlessRpEntityName": "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyPasswordlessRpId": "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey": "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified", + "webAuthnPolicyPasswordlessCreateTimeout": 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyPasswordlessAcceptableAaguids": [], + "users": [ + { + "id": "cb6af759-5e4f-42b3-86ea-b3754ce4d422", + "createdTimestamp": 1624136397065, + "username": "service-account-account-console", + "enabled": true, + "totp": false, + "emailVerified": false, + "serviceAccountClientId": "account-console", + "disableableCredentialTypes": [], + "requiredActions": [], + "realmRoles": [ + "offline_access", + "uma_authorization" + ], + "clientRoles": { + "realm-management": [ + "manage-users" + ], + "account": [ + "manage-account", + "view-profile" + ] + }, + "notBefore": 0, + "groups": [] + } + ], + "scopeMappings": [ + { + "client": "account-console", + "roles": [ + "offline_access", + "uma_authorization", + "user" + ] + }, + { + "clientScope": "offline_access", + "roles": [ + "offline_access" + ] + } + ], + "clientScopeMappings": { + "realm-management": [ + { + "client": "account-console", + "roles": [ + "impersonation", + "realm-admin", + "manage-users" + ] + } + ], + "account": [ + { + "client": "account-console", + "roles": [ + "manage-account" + ] + } + ] + }, + "clients": [ + { + "id": "12eebf0b-a3eb-49f8-9ecf-173cf8a00145", + "clientId": "account", + "name": "${client_account}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/opex/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "fae6f87e-5b66-435c-b5aa-fd42c7641604", + "defaultRoles": [ + "manage-account", + "view-profile" + ], + "redirectUris": [ + "/realms/opex/account/*", + "http://localhost:3000/*" + ], + "webOrigins": [ + "*" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "saml.assertion.signature": "false", + "saml.multivalued.roles": "false", + "saml.force.post.binding": "false", + "saml.encrypt": "false", + "saml.server.signature": "false", + "backchannel.logout.revoke.offline.tokens": "false", + "saml.server.signature.keyinfo.ext": "false", + "exclude.session.state.from.auth.response": "false", + "backchannel.logout.session.required": "false", + "client_credentials.use_refresh_token": "false", + "saml_force_name_id_format": "false", + "saml.client.signature": "false", + "tls.client.certificate.bound.access.tokens": "false", + "saml.authnstatement": "false", + "display.on.consent.screen": "false", + "saml.onetimeuse.condition": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "trust", + "role_list", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "ceabb7ca-b063-4755-90fb-8de2cc7e5e00", + "clientId": "account-console", + "name": "${client_account-console}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/opex/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "fae6f87e-5b66-435c-b5aa-fd42c7641604", + "redirectUris": [ + "http://localhost:3000/*", + "/realms/opex/account/*", + "https://opex.dev/*" + ], + "webOrigins": [ + "+" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": true, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "saml.assertion.signature": "false", + "saml.multivalued.roles": "false", + "saml.force.post.binding": "false", + "saml.encrypt": "false", + "saml.server.signature": "false", + "backchannel.logout.revoke.offline.tokens": "false", + "saml.server.signature.keyinfo.ext": "false", + "exclude.session.state.from.auth.response": "false", + "backchannel.logout.session.required": "false", + "client_credentials.use_refresh_token": "false", + "saml_force_name_id_format": "false", + "saml.client.signature": "false", + "tls.client.certificate.bound.access.tokens": "false", + "saml.authnstatement": "false", + "display.on.consent.screen": "false", + "pkce.code.challenge.method": "S256", + "saml.onetimeuse.condition": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "b83a852e-e3e3-46dc-9e76-3d175fc4a5d4", + "name": "Client Host", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientHost", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientHost", + "jsonType.label": "String" + } + }, + { + "id": "6b335962-bc9b-4095-ad36-48163e443a6f", + "name": "Client ID", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientId", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientId", + "jsonType.label": "String" + } + }, + { + "id": "4c6842a7-9120-49b4-92eb-6cd4a3ff742d", + "name": "Client IP Address", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientAddress", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientAddress", + "jsonType.label": "String" + } + }, + { + "id": "6551bb7e-54af-455e-8de6-0e5acb1b3527", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ], + "defaultClientScopes": [ + "trust", + "web-origins", + "role_list", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "13d76feb-d762-4409-bb84-7a75bc395a61", + "clientId": "admin-cli", + "name": "${client_admin-cli}", + "rootUrl": "", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "**********", + "redirectUris": [], + "webOrigins": [ + "*" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": false, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "saml.assertion.signature": "false", + "access.token.lifespan": "3600", + "saml.multivalued.roles": "false", + "saml.force.post.binding": "false", + "saml.encrypt": "false", + "saml.server.signature": "false", + "backchannel.logout.revoke.offline.tokens": "false", + "saml.server.signature.keyinfo.ext": "false", + "exclude.session.state.from.auth.response": "false", + "backchannel.logout.session.required": "false", + "client_credentials.use_refresh_token": "false", + "saml_force_name_id_format": "false", + "saml.client.signature": "false", + "tls.client.certificate.bound.access.tokens": "false", + "saml.authnstatement": "false", + "display.on.consent.screen": "false", + "saml.onetimeuse.condition": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "trust", + "role_list", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "4b9609f0-48d1-4e71-9381-2ecec08616f9", + "clientId": "broker", + "name": "${client_broker}", + "rootUrl": "", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "**********", + "redirectUris": [ + "http://localhost:3000/*", + "https://opex.dev/*" + ], + "webOrigins": [ + "*" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "saml.assertion.signature": "false", + "saml.multivalued.roles": "false", + "saml.force.post.binding": "false", + "saml.encrypt": "false", + "saml.server.signature": "false", + "backchannel.logout.revoke.offline.tokens": "false", + "saml.server.signature.keyinfo.ext": "false", + "exclude.session.state.from.auth.response": "false", + "backchannel.logout.session.required": "false", + "client_credentials.use_refresh_token": "false", + "saml_force_name_id_format": "false", + "saml.client.signature": "false", + "tls.client.certificate.bound.access.tokens": "false", + "saml.authnstatement": "false", + "display.on.consent.screen": "false", + "saml.onetimeuse.condition": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "trust", + "role_list", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "b88ce206-63d6-43b6-87c9-ea09d8c02f32", + "clientId": "newClient", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "**********", + "redirectUris": [ + "http://localhost:8082/new-client/login/oauth2/code/custom", + "http://localhost:3000/*", + "http://localhost:8089/auth/redirect/", + "https://opex.dev/*" + ], + "webOrigins": [ + "+" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": true, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "saml.assertion.signature": "false", + "saml.force.post.binding": "false", + "saml.multivalued.roles": "false", + "saml.encrypt": "false", + "saml.server.signature": "false", + "backchannel.logout.revoke.offline.tokens": "false", + "saml.server.signature.keyinfo.ext": "false", + "exclude.session.state.from.auth.response": "false", + "backchannel.logout.session.required": "false", + "client_credentials.use_refresh_token": "false", + "saml_force_name_id_format": "false", + "saml.client.signature": "false", + "tls.client.certificate.bound.access.tokens": "false", + "saml.authnstatement": "false", + "display.on.consent.screen": "false", + "saml.onetimeuse.condition": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [ + "trust", + "role_list", + "profile" + ], + "optionalClientScopes": [ + "web-origins", + "address", + "read", + "phone", + "roles", + "offline_access", + "microprofile-jwt", + "write", + "email" + ] + }, + { + "id": "6a4bfbd0-576d-4778-af56-56f876647355", + "clientId": "realm-management", + "name": "${client_realm-management}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "**********", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "role_list", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "8e358d2f-b085-4243-8e6e-c175431e5eeb", + "clientId": "security-admin-console", + "name": "${client_security-admin-console}", + "rootUrl": "${authAdminUrl}", + "baseUrl": "/admin/opex/console/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "**********", + "redirectUris": [ + "/admin/opex/console/*" + ], + "webOrigins": [ + "+" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "saml.assertion.signature": "false", + "saml.multivalued.roles": "false", + "saml.force.post.binding": "false", + "saml.encrypt": "false", + "saml.server.signature": "false", + "backchannel.logout.revoke.offline.tokens": "false", + "saml.server.signature.keyinfo.ext": "false", + "exclude.session.state.from.auth.response": "false", + "backchannel.logout.session.required": "false", + "client_credentials.use_refresh_token": "false", + "saml_force_name_id_format": "false", + "saml.client.signature": "false", + "tls.client.certificate.bound.access.tokens": "false", + "saml.authnstatement": "false", + "display.on.consent.screen": "false", + "pkce.code.challenge.method": "S256", + "saml.onetimeuse.condition": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "9cfca9ee-493d-4b5e-8170-2d364149de59", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "role_list", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + } + ], + "clientScopes": [ + { + "id": "77c7e29d-1a22-4419-bbfb-4a62bb033449", + "name": "address", + "description": "OpenID Connect built-in scope: address", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${addressScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "94e1879d-b49e-4178-96e0-bf8d7f32c160", + "name": "address", + "protocol": "openid-connect", + "protocolMapper": "oidc-address-mapper", + "consentRequired": false, + "config": { + "user.attribute.formatted": "formatted", + "user.attribute.country": "country", + "user.attribute.postal_code": "postal_code", + "userinfo.token.claim": "true", + "user.attribute.street": "street", + "id.token.claim": "true", + "user.attribute.region": "region", + "access.token.claim": "true", + "user.attribute.locality": "locality" + } + } + ] + }, + { + "id": "b3526ac1-10e2-4344-8621-9c5a0853e97a", + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${emailScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "d30270dc-baa6-455a-8ff6-ddccf8a78d86", + "name": "email verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "emailVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email_verified", + "jsonType.label": "boolean" + } + }, + { + "id": "f5b1684d-e479-4134-8578-457fa64717da", + "name": "email", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "c658ae14-e96a-4745-b21b-2ed5c4c63f5f", + "name": "microprofile-jwt", + "description": "Microprofile - JWT built-in scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "959521bc-5ffd-465b-95f2-5b0c20d1909c", + "name": "upn", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "upn", + "jsonType.label": "String" + } + }, + { + "id": "07b8550c-b298-4cce-9ffb-900182575b76", + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "multivalued": "true", + "userinfo.token.claim": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "569b3d44-4ecd-4768-a58c-70ff38f4b4fe", + "name": "offline_access", + "description": "OpenID Connect built-in scope: offline_access", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${offlineAccessScopeConsentText}", + "display.on.consent.screen": "true" + } + }, + { + "id": "a3e7b19d-df6c-437e-9eea-06fec1becb2f", + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${phoneScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "72a070f7-4363-4c88-8153-6fd2d12b9b04", + "name": "phone number verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumberVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number_verified", + "jsonType.label": "boolean" + } + }, + { + "id": "24b42c6d-a93c-4aa1-9a03-2a2b55954c13", + "name": "phone number", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumber", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "ba8c9950-fd0b-4434-8be6-b58456d7b6d4", + "name": "profile", + "description": "OpenID Connect built-in scope: profile", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${profileScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "0a9ddd71-309c-40f0-8ea6-a0791070c6ed", + "name": "profile", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "profile", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "profile", + "jsonType.label": "String" + } + }, + { + "id": "fbf53bbd-1ad0-4bf8-8030-50f81696d8ee", + "name": "nickname", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "nickname", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "nickname", + "jsonType.label": "String" + } + }, + { + "id": "423be2cd-42c0-462e-9030-18f9b28ff2d3", + "name": "gender", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "gender", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "gender", + "jsonType.label": "String" + } + }, + { + "id": "53eb9006-4b81-474a-8b60-80f775d54b63", + "name": "picture", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "picture", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "picture", + "jsonType.label": "String" + } + }, + { + "id": "4d8bc82a-eaeb-499e-8eb2-0f1dcbe91699", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + }, + { + "id": "d3b25485-4042-419d-afff-cfd63a76e229", + "name": "full name", + "protocol": "openid-connect", + "protocolMapper": "oidc-full-name-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "id": "422cfa5a-f2f4-4f36-82df-91b47ae1ea50", + "name": "given name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "firstName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "given_name", + "jsonType.label": "String" + } + }, + { + "id": "3f2863c1-d98d-45b5-b08f-af9c4d9c10f8", + "name": "website", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "website", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "website", + "jsonType.label": "String" + } + }, + { + "id": "c98c063d-eee4-41a0-9130-595afd709d1f", + "name": "middle name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "middleName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "middle_name", + "jsonType.label": "String" + } + }, + { + "id": "8dbed80a-d672-4185-8dda-4bba2a56ec83", + "name": "family name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "lastName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "family_name", + "jsonType.label": "String" + } + }, + { + "id": "5e5c690c-93cf-489d-a054-b109eab8911b", + "name": "updated at", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "updatedAt", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "updated_at", + "jsonType.label": "String" + } + }, + { + "id": "3b985202-af8a-42f1-ac5f-0966a404f5d7", + "name": "birthdate", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "birthdate", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "birthdate", + "jsonType.label": "String" + } + }, + { + "id": "6eafd1b3-7121-4919-ad1e-039fa58acc32", + "name": "username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "preferred_username", + "jsonType.label": "String" + } + }, + { + "id": "73cba925-8c31-443f-9601-b1514e6396c1", + "name": "zoneinfo", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "zoneinfo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "zoneinfo", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "c1a2eb23-25c6-4be7-a791-bbdca99c83f7", + "name": "read", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true" + } + }, + { + "id": "18e141bf-dabe-4858-879c-dbc439cdead4", + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "10cbe37f-0198-4d65-bc8a-bfe5ad8145d1", + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + }, + { + "id": "111ed87a-5fd3-4cee-96df-8dbfb88cfdc0", + "name": "roles", + "description": "OpenID Connect scope for add user roles to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "true", + "consent.screen.text": "${rolesScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "24924d8d-6071-4a93-b40f-326176cb335e", + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "2f6a9bdf-3758-484c-996d-e4f93555559f", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + }, + { + "id": "804d4798-d9a3-4fd3-8b28-d12142e8cb3d", + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}.roles", + "jsonType.label": "String", + "multivalued": "true" + } + } + ] + }, + { + "id": "51d49314-b511-43e0-9258-bfb873758a78", + "name": "web-origins", + "description": "OpenID Connect scope for add allowed web origins to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false", + "consent.screen.text": "" + }, + "protocolMappers": [ + { + "id": "2b384cd0-9e85-4a87-8eeb-2b480b0587b7", + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": false, + "config": {} + } + ] + }, + { + "id": "c3e253fb-7361-47cf-9d4a-86245686fdf1", + "name": "write", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true" + } + }, + { + "id": "d4e253fb-7361-47cf-9d4a-86245686fdf2", + "name": "trust", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true" + } + } + ], + "defaultDefaultClientScopes": [ + "roles", + "role_list", + "web-origins", + "email", + "profile", + "trust" + ], + "defaultOptionalClientScopes": [ + "offline_access", + "address", + "phone", + "microprofile-jwt" + ], + "browserSecurityHeaders": { + "contentSecurityPolicyReportOnly": "", + "xContentTypeOptions": "nosniff", + "xRobotsTag": "none", + "xFrameOptions": "SAMEORIGIN", + "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection": "1; mode=block", + "strictTransportSecurity": "max-age=31536000; includeSubDomains" + }, + "smtpServer": { + "password": "642467973026C6F093FB1E39C4BFC0D15042", + "auth": "true", + "port": "2525", + "host": "smtp.elasticemail.com", + "from": "for.demo.purpose.only@opex.dev", + "user": "for.demo.purpose.only@opex.dev" + }, + "eventsEnabled": false, + "eventsListeners": [ + "jboss-logging", + "pl_event_listener" + ], + "enabledEventTypes": [], + "adminEventsEnabled": false, + "adminEventsDetailsEnabled": false, + "identityProviders": [], + "identityProviderMappers": [], + "components": { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ + { + "id": "84305f42-4b6d-4b0a-ac7c-53e406e3ac63", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "c7c38a95-744f-4558-a403-9cf692fe1944", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "365b2899-befe-4417-b89b-562650ec4446", + "name": "Full Scope Disabled", + "providerId": "scope", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "81c32244-7921-43e9-9356-a3469259b78c", + "name": "Trusted Hosts", + "providerId": "trusted-hosts", + "subType": "anonymous", + "subComponents": {}, + "config": { + "host-sending-registration-request-must-match": [ + "true" + ], + "trusted-hosts": [ + "https://opex.dev/bdemo/login" + ], + "client-uris-must-match": [ + "true" + ] + } + }, + { + "id": "d09b2147-afea-4f7f-a49c-0aec7eee10de", + "name": "Max Clients Limit", + "providerId": "max-clients", + "subType": "anonymous", + "subComponents": {}, + "config": { + "max-clients": [ + "200" + ] + } + }, + { + "id": "41ffde1b-72a2-416f-87a7-94989e940dc0", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "oidc-address-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-usermodel-property-mapper", + "saml-role-list-mapper", + "saml-user-property-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-full-name-mapper", + "saml-user-attribute-mapper" + ] + } + }, + { + "id": "76075388-2782-4656-a986-313493239a9f", + "name": "Consent Required", + "providerId": "consent-required", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "3caaf57a-9cd7-48c1-b709-b40b887414f7", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "saml-user-property-mapper", + "saml-role-list-mapper", + "oidc-usermodel-attribute-mapper", + "saml-user-attribute-mapper", + "oidc-usermodel-property-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-address-mapper", + "oidc-full-name-mapper" + ] + } + } + ], + "org.keycloak.keys.KeyProvider": [ + { + "id": "d67a940a-52e4-44a5-9f69-6ffdd67a188f", + "name": "rsa-generated", + "providerId": "rsa-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "48d40de3-6234-42e8-9449-f68f56abb54b", + "name": "aes-generated", + "providerId": "aes-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "52ea1c5d-2a30-459f-b66a-249f298b32f8", + "name": "hmac-generated", + "providerId": "hmac-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "HS256" + ] + } + } + ] + }, + "internationalizationEnabled": false, + "supportedLocales": [], + "authenticationFlows": [ + { + "id": "1994872d-58ab-40e9-9f3c-bd94d498fde9", + "alias": "Handle Existing Account", + "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-confirm-link", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "requirement": "REQUIRED", + "priority": 20, + "flowAlias": "Handle Existing Account - Alternatives - 0", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "2d9ee8ef-c756-41b6-a504-2b500650a966", + "alias": "Handle Existing Account - Alternatives - 0", + "description": "Subflow of Handle Existing Account with alternative executions", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-email-verification", + "requirement": "ALTERNATIVE", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "requirement": "ALTERNATIVE", + "priority": 20, + "flowAlias": "Verify Existing Account by Re-authentication", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "424ef9c8-da39-4813-9093-964f16f8eed9", + "alias": "Verify Existing Account by Re-authentication", + "description": "Reauthentication of existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-username-password-form", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "requirement": "CONDITIONAL", + "priority": 20, + "flowAlias": "Verify Existing Account by Re-authentication - auth-otp-form - Conditional", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "5321e985-f444-4772-8a7d-44eeab19d8fa", + "alias": "Verify Existing Account by Re-authentication - auth-otp-form - Conditional", + "description": "Flow to determine if the auth-otp-form authenticator should be used or not.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-otp-form", + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "06842669-a459-456d-b9fb-11d9236c3377", + "alias": "browser", + "description": "browser based authentication", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-cookie", + "requirement": "ALTERNATIVE", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-spnego", + "requirement": "DISABLED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "identity-provider-redirector", + "requirement": "ALTERNATIVE", + "priority": 25, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "requirement": "ALTERNATIVE", + "priority": 30, + "flowAlias": "forms", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "e869296d-430f-4721-a5cd-2d5fe66294e0", + "alias": "clients", + "description": "Base authentication for clients", + "providerId": "client-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "client-secret", + "requirement": "ALTERNATIVE", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "client-jwt", + "requirement": "ALTERNATIVE", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "client-secret-jwt", + "requirement": "ALTERNATIVE", + "priority": 30, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "client-x509", + "requirement": "ALTERNATIVE", + "priority": 40, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "0e7775f8-7b6d-41a2-9f55-72416864fd87", + "alias": "direct grant", + "description": "OpenID Connect Resource Owner Grant", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "direct-grant-validate-username", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "direct-grant-validate-password", + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "requirement": "CONDITIONAL", + "priority": 30, + "flowAlias": "direct grant - direct-grant-validate-otp - Conditional", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "5fa95cf1-9d79-4066-93bb-86da3c4a1fa5", + "alias": "direct grant - direct-grant-validate-otp - Conditional", + "description": "Flow to determine if the direct-grant-validate-otp authenticator should be used or not.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "direct-grant-validate-otp", + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "89c5f0a2-310b-4388-b9fc-be14f35cb36a", + "alias": "docker auth", + "description": "Used by Docker clients to authenticate against the IDP", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "docker-http-basic-authenticator", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "ba4ec48d-de84-4847-9c53-cb16a9114cf8", + "alias": "first broker login", + "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "review profile config", + "authenticator": "idp-review-profile", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "requirement": "REQUIRED", + "priority": 20, + "flowAlias": "first broker login - Alternatives - 0", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "fdccc5f5-2ef8-4e94-acd2-34ed9b12c3d1", + "alias": "first broker login - Alternatives - 0", + "description": "Subflow of first broker login with alternative executions", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "create unique user config", + "authenticator": "idp-create-user-if-unique", + "requirement": "ALTERNATIVE", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "requirement": "ALTERNATIVE", + "priority": 20, + "flowAlias": "Handle Existing Account", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "ceef545f-aade-4066-9aa2-13d21abda764", + "alias": "forms", + "description": "Username, password, otp and other auth forms.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-username-password-form", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "requirement": "CONDITIONAL", + "priority": 20, + "flowAlias": "forms - auth-otp-form - Conditional", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "031b60ca-098a-4301-acdc-5e91e3c00de6", + "alias": "forms - auth-otp-form - Conditional", + "description": "Flow to determine if the auth-otp-form authenticator should be used or not.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-otp-form", + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "303db066-ce61-40e1-99c5-d8dc28903585", + "alias": "http challenge", + "description": "An authentication flow based on challenge-response HTTP Authentication Schemes", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "no-cookie-redirect", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "basic-auth", + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "basic-auth-otp", + "requirement": "DISABLED", + "priority": 30, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-spnego", + "requirement": "DISABLED", + "priority": 40, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "52600ab5-b57f-4ad6-85ea-397afe32cbda", + "alias": "registration", + "description": "registration flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-page-form", + "requirement": "REQUIRED", + "priority": 10, + "flowAlias": "registration form", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "895289c7-ab6f-4388-9088-1ccb21725996", + "alias": "registration form", + "description": "registration form", + "providerId": "form-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-user-creation", + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "registration-profile-action", + "requirement": "REQUIRED", + "priority": 40, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "registration-password-action", + "requirement": "REQUIRED", + "priority": 50, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "registration-recaptcha-action", + "requirement": "DISABLED", + "priority": 60, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "dcc7bdcc-548c-4b40-af94-b3cad69a3b87", + "alias": "reset credentials", + "description": "Reset credentials for a user if they forgot their password or something", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "reset-credentials-choose-user", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "reset-credential-email", + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "reset-password", + "requirement": "REQUIRED", + "priority": 30, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "requirement": "CONDITIONAL", + "priority": 40, + "flowAlias": "reset credentials - reset-otp - Conditional", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "96dff9e4-56be-4c62-a146-2507d00b88c9", + "alias": "reset credentials - reset-otp - Conditional", + "description": "Flow to determine if the reset-otp authenticator should be used or not.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "reset-otp", + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "fc10429a-01a5-4e8a-bad3-7b4794ee7688", + "alias": "saml ecp", + "description": "SAML ECP Profile Authentication Flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "http-basic-authenticator", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + } + ], + "authenticatorConfig": [ + { + "id": "cda25df3-6d3b-49f9-bb52-b24ab4d1ee57", + "alias": "create unique user config", + "config": { + "require.password.update.after.registration": "false" + } + }, + { + "id": "fbf2034e-a602-44d7-8c11-8d27450f930b", + "alias": "review profile config", + "config": { + "update.profile.on.first.login": "missing" + } + } + ], + "requiredActions": [ + { + "alias": "CONFIGURE_TOTP", + "name": "Configure OTP", + "providerId": "CONFIGURE_TOTP", + "enabled": true, + "defaultAction": false, + "priority": 10, + "config": {} + }, + { + "alias": "terms_and_conditions", + "name": "Terms and Conditions", + "providerId": "terms_and_conditions", + "enabled": false, + "defaultAction": false, + "priority": 20, + "config": {} + }, + { + "alias": "UPDATE_PASSWORD", + "name": "Update Password", + "providerId": "UPDATE_PASSWORD", + "enabled": true, + "defaultAction": false, + "priority": 30, + "config": {} + }, + { + "alias": "UPDATE_PROFILE", + "name": "Update Profile", + "providerId": "UPDATE_PROFILE", + "enabled": true, + "defaultAction": false, + "priority": 40, + "config": {} + }, + { + "alias": "VERIFY_EMAIL", + "name": "Verify Email", + "providerId": "VERIFY_EMAIL", + "enabled": true, + "defaultAction": false, + "priority": 50, + "config": {} + }, + { + "alias": "delete_account", + "name": "Delete Account", + "providerId": "delete_account", + "enabled": false, + "defaultAction": false, + "priority": 60, + "config": {} + }, + { + "alias": "update_user_locale", + "name": "Update User Locale", + "providerId": "update_user_locale", + "enabled": true, + "defaultAction": false, + "priority": 1000, + "config": {} + } + ], + "browserFlow": "browser", + "registrationFlow": "registration", + "directGrantFlow": "direct grant", + "resetCredentialsFlow": "reset credentials", + "clientAuthenticationFlow": "clients", + "dockerAuthenticationFlow": "docker auth", + "attributes": { + "clientOfflineSessionMaxLifespan": "0", + "clientSessionIdleTimeout": "0", + "clientSessionMaxLifespan": "0", + "clientOfflineSessionIdleTimeout": "0" + }, + "keycloakVersion": "12.0.4", + "userManagedAccessAllowed": false +} \ No newline at end of file diff --git a/UserManagement/pom.xml b/UserManagement/pom.xml new file mode 100644 index 000000000..8749a2ce1 --- /dev/null +++ b/UserManagement/pom.xml @@ -0,0 +1,14 @@ + + + 4.0.0 + co.nilin.opex + user-management-root + 1.0-SNAPSHOT + user-management-root + pom + User Management root of Opex + + keycloak-gateway + + diff --git a/Utility/.gitignore b/Utility/.gitignore new file mode 100644 index 000000000..d6953c444 --- /dev/null +++ b/Utility/.gitignore @@ -0,0 +1,3 @@ +*.iml +.idea +/.idea/ diff --git a/Utility/error-handler/.gitignore b/Utility/error-handler/.gitignore new file mode 100644 index 000000000..549e00a2a --- /dev/null +++ b/Utility/error-handler/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/Utility/error-handler/pom.xml b/Utility/error-handler/pom.xml new file mode 100644 index 000000000..7004e656e --- /dev/null +++ b/Utility/error-handler/pom.xml @@ -0,0 +1,87 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.4 + + + co.nilin.opex + error-handler + 1.0-SNAPSHOT + error-handler + REST error handler + + + 1.8 + 1.4.31 + + + + + org.springframework.boot + spring-boot-starter-webflux + + + com.fasterxml.jackson.module + jackson-module-kotlin + + + io.projectreactor.kotlin + reactor-kotlin-extensions + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + + + + org.springframework.boot + spring-boot-starter-test + test + + + + io.projectreactor + reactor-test + test + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + -Xjsr305=strict + + + spring + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + + + diff --git a/Utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/Config.kt b/Utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/Config.kt new file mode 100644 index 000000000..2ae3ebccb --- /dev/null +++ b/Utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/Config.kt @@ -0,0 +1,24 @@ +package co.nilin.opex.utility.error + +import co.nilin.opex.utility.error.spi.ErrorTranslator +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.registerKotlinModule +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +class Config { + + @Bean + @ConditionalOnMissingBean + fun translator(): ErrorTranslator { + return DefaultErrorTranslator() + } + + @Bean + fun mapper(): ObjectMapper { + return ObjectMapper().registerKotlinModule() + } + +} \ No newline at end of file diff --git a/Utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/DefaultErrorTranslator.kt b/Utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/DefaultErrorTranslator.kt new file mode 100644 index 000000000..f42f30c6f --- /dev/null +++ b/Utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/DefaultErrorTranslator.kt @@ -0,0 +1,22 @@ +package co.nilin.opex.utility.error + +import co.nilin.opex.utility.error.data.DefaultExceptionResponse +import co.nilin.opex.utility.error.data.OpexException +import co.nilin.opex.utility.error.spi.ErrorTranslator +import co.nilin.opex.utility.error.spi.ExceptionResponse +import org.springframework.stereotype.Component + +@Component +class DefaultErrorTranslator : ErrorTranslator { + + override fun translate(ex: OpexException): ExceptionResponse { + return DefaultExceptionResponse( + ex.error.name, + ex.error.code, + ex.message ?: ex.error.message, + ex.status ?: ex.error.status, + ex.data, + ex.crimeScene + ) + } +} \ No newline at end of file diff --git a/Utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/EnableOpexErrorHandler.kt b/Utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/EnableOpexErrorHandler.kt new file mode 100644 index 000000000..cee46b0d2 --- /dev/null +++ b/Utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/EnableOpexErrorHandler.kt @@ -0,0 +1,10 @@ +package co.nilin.opex.utility.error + +import co.nilin.opex.utility.error.controller.ExceptionController +import org.springframework.context.annotation.Import + +@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +@MustBeDocumented +@Import(ExceptionController::class) +annotation class EnableOpexErrorHandler \ No newline at end of file diff --git a/Utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/controller/ExceptionController.kt b/Utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/controller/ExceptionController.kt new file mode 100644 index 000000000..1d0ba0ca5 --- /dev/null +++ b/Utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/controller/ExceptionController.kt @@ -0,0 +1,93 @@ +package co.nilin.opex.utility.error.controller + +import co.nilin.opex.utility.error.data.DefaultExceptionResponse +import co.nilin.opex.utility.error.data.OpexError +import co.nilin.opex.utility.error.data.OpexException +import co.nilin.opex.utility.error.spi.ErrorTranslator +import co.nilin.opex.utility.error.spi.ExceptionResponse +import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.fasterxml.jackson.databind.ObjectMapper +import org.slf4j.LoggerFactory +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.ExceptionHandler +import org.springframework.web.bind.annotation.RestControllerAdvice +import org.springframework.web.reactive.function.client.WebClientResponseException +import org.springframework.web.server.ServerWebInputException +import java.nio.charset.StandardCharsets +import java.util.* +import org.springframework.http.HttpStatus + +@RestControllerAdvice +class ExceptionController( + private val mapper: ObjectMapper, + private val translator: ErrorTranslator +) { + + @JsonIgnoreProperties(ignoreUnknown = true) + data class WebClientErrorResponse( + val timestamp: Date?, + val path: String?, + val status: Int?, + val error: String?, + val message: String?, + val code: Int? + ) + + private val logger = LoggerFactory.getLogger(ExceptionController::class.java) + + @ExceptionHandler(OpexException::class) + fun handle(e: OpexException): ResponseEntity { + val error = translator.translate(e) + if (error is DefaultExceptionResponse) + logger.error("Opex error happened at ${e.crimeScene?.name}", e) + else + logger.error("Opex error", e) + return response(error) + } + + @ExceptionHandler(WebClientResponseException::class) + fun handle(e: WebClientResponseException): ResponseEntity { + logger.error("Webclient error", e) + return try { + val body = mapper.readValue( + e.responseBodyAsByteArray.toString(StandardCharsets.UTF_8), + WebClientErrorResponse::class.java + ) + + val opexError = OpexError.findByCode(body.code) + val er = translator.translate(OpexException(opexError ?: OpexError.InternalServerError)) + response(er) + } catch (ex: Exception) { + val opEx = OpexException(OpexError.InternalServerError) + val er = translator.translate(opEx) + response(er) + } + } + + @ExceptionHandler(ServerWebInputException::class) + fun handleMissingServletRequestParameter(ex: ServerWebInputException): ResponseEntity { + logger.error("Web input error", ex) + val name = ex.methodParameter?.parameterName + + val error = OpexError.InvalidRequestParam + val er = translator.translate( + OpexException( + error, + String.format(error.message!!, name) + ) + ) + return response(er) + } + + @ExceptionHandler(Throwable::class) + fun handle(e: Throwable): ResponseEntity { + logger.error("Generic error", e) + val opexException = OpexException(status = HttpStatus.INTERNAL_SERVER_ERROR, error = OpexError.InternalServerError) + val error = translator.translate(opexException) + return response(error) + } + + private fun response(er: ExceptionResponse): ResponseEntity = + ResponseEntity.status(er.status).body(er) + +} \ No newline at end of file diff --git a/Utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/data/DefaultExceptionResponse.kt b/Utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/data/DefaultExceptionResponse.kt new file mode 100644 index 000000000..13eba9b3d --- /dev/null +++ b/Utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/data/DefaultExceptionResponse.kt @@ -0,0 +1,19 @@ +package co.nilin.opex.utility.error.data + +import co.nilin.opex.utility.error.spi.ExceptionResponse +import com.fasterxml.jackson.annotation.JsonIgnore +import com.fasterxml.jackson.annotation.JsonInclude +import org.springframework.http.HttpStatus +import java.util.* + +@JsonInclude(JsonInclude.Include.NON_NULL) +class DefaultExceptionResponse( + val error: String?, + val code: Int, + val message: String?, + status: HttpStatus, + val data: Any? = null, + @JsonIgnore + val crimeScene: Class<*>?, + val timestamp: Date = Date() +) : ExceptionResponse(status) \ No newline at end of file diff --git a/Utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/data/OpexError.kt b/Utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/data/OpexError.kt new file mode 100644 index 000000000..d03fdfe8c --- /dev/null +++ b/Utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/data/OpexError.kt @@ -0,0 +1,55 @@ +package co.nilin.opex.utility.error.data + +import org.springframework.http.HttpStatus + +enum class OpexError(val code: Int, val message: String?, val status: HttpStatus) { + + // Code 1000: general + Error(1000, "Generic error", HttpStatus.INTERNAL_SERVER_ERROR), + InternalServerError(1001, "Internal server error", HttpStatus.INTERNAL_SERVER_ERROR), + BadRequest(1002, "Bad request", HttpStatus.BAD_REQUEST), + UnAuthorized(1003, "Unauthorized", HttpStatus.UNAUTHORIZED), + Forbidden(1004, "Forbidden", HttpStatus.FORBIDDEN), + NotFound(1005, "Not found", HttpStatus.NOT_FOUND), + InvalidRequestParam(1020, "Parameter '%s' is either missing or invalid", HttpStatus.BAD_REQUEST), + + // code 2000: accountant + InvalidPair(2001, "%s is not available", HttpStatus.BAD_REQUEST), + InvalidPairFee(2002, "%s fee is not available", HttpStatus.BAD_REQUEST), + + // code 3000: matching-engine + + // code 4000: matching-gateway + SubmitOrderForbiddenByAccountant(4001, null, HttpStatus.BAD_REQUEST), + + // code 5000: user-management + + // code 6000: wallet + WalletOwnerNotFound(6001, null, HttpStatus.NOT_FOUND), + + // code 7000: api + OrderNotFound(7001, "No order found", HttpStatus.NOT_FOUND), + SymbolNotFound(7002, "No symbol found", HttpStatus.NOT_FOUND), + InvalidLimitForOrderBook(7003, "Valid limits: [5, 10, 20, 50, 100, 500, 1000, 5000]", HttpStatus.BAD_REQUEST), + InvalidLimitForRecentTrades(7004, "Valid limits: 1 min - 1000 max", HttpStatus.BAD_REQUEST), + InvalidPriceChangeDuration(7005, "Valid durations: [24h, 7d, 1m]", HttpStatus.BAD_REQUEST), + CancelOrderNotAllowed(7006, "Canceling this order is not allowed", HttpStatus.FORBIDDEN); + + companion object { + fun findByCode(code: Int?): OpexError? { + code ?: return null + return values().find { it.code == code } + } + } + +} + +@Throws(OpexException::class) +inline fun T.throwError( + error: OpexError, + message: String? = null, + status: HttpStatus? = null, + data: Any? = null +) { + throw OpexException(error, message, status, data, T::class.java) +} \ No newline at end of file diff --git a/Utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/data/OpexException.kt b/Utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/data/OpexException.kt new file mode 100644 index 000000000..187a18d29 --- /dev/null +++ b/Utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/data/OpexException.kt @@ -0,0 +1,11 @@ +package co.nilin.opex.utility.error.data + +import org.springframework.http.HttpStatus + +class OpexException( + val error: OpexError, + message: String? = null, + val status: HttpStatus? = null, + val data: Any? = null, + val crimeScene: Class<*>? = null +) : RuntimeException(message ?: error.message) \ No newline at end of file diff --git a/Utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/spi/ErrorTranslator.kt b/Utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/spi/ErrorTranslator.kt new file mode 100644 index 000000000..3b596a578 --- /dev/null +++ b/Utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/spi/ErrorTranslator.kt @@ -0,0 +1,9 @@ +package co.nilin.opex.utility.error.spi + +import co.nilin.opex.utility.error.data.OpexException + +interface ErrorTranslator { + + fun translate(ex: OpexException): ExceptionResponse + +} \ No newline at end of file diff --git a/Utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/spi/ExceptionResponse.kt b/Utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/spi/ExceptionResponse.kt new file mode 100644 index 000000000..92ed722f7 --- /dev/null +++ b/Utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/spi/ExceptionResponse.kt @@ -0,0 +1,6 @@ +package co.nilin.opex.utility.error.spi + +import com.fasterxml.jackson.annotation.JsonIgnore +import org.springframework.http.HttpStatus + +abstract class ExceptionResponse(@JsonIgnore val status: HttpStatus) \ No newline at end of file diff --git a/Utility/error-handler/src/main/resources/application.yml b/Utility/error-handler/src/main/resources/application.yml new file mode 100644 index 000000000..e69de29bb diff --git a/Utility/interceptors/.gitignore b/Utility/interceptors/.gitignore new file mode 100644 index 000000000..549e00a2a --- /dev/null +++ b/Utility/interceptors/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/Utility/interceptors/pom.xml b/Utility/interceptors/pom.xml new file mode 100644 index 000000000..7783aa213 --- /dev/null +++ b/Utility/interceptors/pom.xml @@ -0,0 +1,90 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.4 + + + co.nilin.opex + interceptors + 1.0-SNAPSHOT + interceptors + REST interceptors + + + 1.8 + 1.4.31 + + + + + org.springframework.boot + spring-boot-starter-webflux + + + com.fasterxml.jackson.module + jackson-module-kotlin + + + io.projectreactor.kotlin + reactor-kotlin-extensions + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + + + io.projectreactor.netty + reactor-netty + 0.9.12.RELEASE + + + org.springframework.boot + spring-boot-starter-test + test + + + io.projectreactor + reactor-test + test + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + -Xjsr305=strict + + + spring + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + + + diff --git a/Utility/interceptors/src/main/kotlin/co/nilin/opex/utility/interceptor/FormDataWorkaroundFilter.java b/Utility/interceptors/src/main/kotlin/co/nilin/opex/utility/interceptor/FormDataWorkaroundFilter.java new file mode 100644 index 000000000..17dccc05f --- /dev/null +++ b/Utility/interceptors/src/main/kotlin/co/nilin/opex/utility/interceptor/FormDataWorkaroundFilter.java @@ -0,0 +1,45 @@ +package co.nilin.opex.utility.interceptor; + +import co.nilin.opex.utility.interceptor.decorator.*; +import org.springframework.http.codec.multipart.*; +import org.springframework.http.server.reactive.*; +import org.springframework.util.*; +import org.springframework.web.server.*; +import reactor.core.publisher.*; + +import java.util.*; + + +public class FormDataWorkaroundFilter implements WebFilter { + + @Override + public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { + final ServerHttpRequest request = exchange.getRequest(); + + final MultiValueMap queryParams = new LinkedMultiValueMap<>(); + + //add all content from form data to query params + exchange.getFormData().subscribe(queryParams::putAll); + + exchange.getMultipartData().subscribe(map -> { + map.forEach((key, value) -> { + List list = value; + list.forEach(item -> { + //add each form field parts to query params + if (item instanceof FormFieldPart) { + final FormFieldPart formFieldPart = (FormFieldPart) item; + queryParams.add(key, formFieldPart.value()); + } + }); + + }); + }); + + //add original query params to win identical name war + queryParams.putAll(request.getQueryParams()); + + return chain.filter(new FormDataServerWebExchangeDecorator(queryParams, exchange)); + } + + +} diff --git a/Utility/interceptors/src/main/kotlin/co/nilin/opex/utility/interceptor/decorator/FormDataServerHttpRequestDecorator.java b/Utility/interceptors/src/main/kotlin/co/nilin/opex/utility/interceptor/decorator/FormDataServerHttpRequestDecorator.java new file mode 100644 index 000000000..670365200 --- /dev/null +++ b/Utility/interceptors/src/main/kotlin/co/nilin/opex/utility/interceptor/decorator/FormDataServerHttpRequestDecorator.java @@ -0,0 +1,19 @@ +package co.nilin.opex.utility.interceptor.decorator; + +import org.springframework.http.server.reactive.*; +import org.springframework.util.*; + +public class FormDataServerHttpRequestDecorator extends ServerHttpRequestDecorator { + + private MultiValueMap queryParams; + + FormDataServerHttpRequestDecorator(MultiValueMap queryParams, ServerHttpRequest delegate) { + super(delegate); + this.queryParams = queryParams; + } + + @Override + public MultiValueMap getQueryParams() { + return queryParams; + } +} \ No newline at end of file diff --git a/Utility/interceptors/src/main/kotlin/co/nilin/opex/utility/interceptor/decorator/FormDataServerWebExchangeDecorator.java b/Utility/interceptors/src/main/kotlin/co/nilin/opex/utility/interceptor/decorator/FormDataServerWebExchangeDecorator.java new file mode 100644 index 000000000..57679d517 --- /dev/null +++ b/Utility/interceptors/src/main/kotlin/co/nilin/opex/utility/interceptor/decorator/FormDataServerWebExchangeDecorator.java @@ -0,0 +1,25 @@ +package co.nilin.opex.utility.interceptor.decorator; + +import org.slf4j.*; +import org.springframework.http.server.reactive.*; +import org.springframework.util.*; +import org.springframework.web.server.*; + +public class FormDataServerWebExchangeDecorator extends ServerWebExchangeDecorator { + + private Logger log = LoggerFactory.getLogger( FormDataServerWebExchangeDecorator.class); + + private FormDataServerHttpRequestDecorator requestDecorator; + + + public FormDataServerWebExchangeDecorator(MultiValueMap queryParams, ServerWebExchange delegate) { + super(delegate); + requestDecorator = new FormDataServerHttpRequestDecorator(queryParams, delegate.getRequest()); + } + + @Override + public ServerHttpRequest getRequest() { + return requestDecorator; + } + +} \ No newline at end of file diff --git a/Utility/logging-handler/.gitignore b/Utility/logging-handler/.gitignore new file mode 100644 index 000000000..549e00a2a --- /dev/null +++ b/Utility/logging-handler/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/Utility/logging-handler/pom.xml b/Utility/logging-handler/pom.xml new file mode 100644 index 000000000..3c6138820 --- /dev/null +++ b/Utility/logging-handler/pom.xml @@ -0,0 +1,90 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.4 + + + co.nilin.opex + logging-handler + 1.0-SNAPSHOT + logging-handler + REST logging handler + + + 1.8 + 1.4.31 + + + + + org.springframework.boot + spring-boot-starter-webflux + + + com.fasterxml.jackson.module + jackson-module-kotlin + + + io.projectreactor.kotlin + reactor-kotlin-extensions + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + + + io.projectreactor.netty + reactor-netty + 0.9.12.RELEASE + + + org.springframework.boot + spring-boot-starter-test + test + + + io.projectreactor + reactor-test + test + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + -Xjsr305=strict + + + spring + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + + + diff --git a/Utility/logging-handler/src/main/kotlin/co/nilin/opex/utility/log/interceptor/LogUtils.java b/Utility/logging-handler/src/main/kotlin/co/nilin/opex/utility/log/interceptor/LogUtils.java new file mode 100644 index 000000000..9c5fa48e9 --- /dev/null +++ b/Utility/logging-handler/src/main/kotlin/co/nilin/opex/utility/log/interceptor/LogUtils.java @@ -0,0 +1,58 @@ +package co.nilin.opex.utility.log.interceptor; + +import io.netty.buffer.UnpooledByteBufAllocator; +import org.slf4j.Logger; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.core.io.buffer.DataBufferUtils; +import org.springframework.core.io.buffer.NettyDataBufferFactory; +import org.springframework.http.MediaType; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.util.Arrays; +import java.util.List; + +public class LogUtils { + + public static final List legalLogMediaTypes = Arrays.asList( + MediaType.TEXT_XML, + MediaType.APPLICATION_XML, + MediaType.APPLICATION_JSON, + MediaType.TEXT_PLAIN, + MediaType.TEXT_XML); + + public static T loggingRequest(Logger log, String tracing, T buffer) { + return logging(log, tracing,"request: ", buffer); + } + + public static T loggingResponse(Logger log, String tracing, T buffer) { + return logging(log, tracing,"response: ", buffer); + } + + private static T logging(Logger log, String tracing,String inOrOut, T buffer) { + InputStream dataBuffer = buffer.asInputStream(); + byte[] bytes = toByteArray(dataBuffer); + NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(new UnpooledByteBufAllocator(false)); + if (log.isDebugEnabled()) { + log.debug("{}-{}: {}", tracing, inOrOut, new String(bytes)); + } + DataBufferUtils.release(buffer); + return (T) nettyDataBufferFactory.wrap(bytes); + } + + private static byte[] toByteArray(InputStream inStream) { + ByteArrayOutputStream swapStream = new ByteArrayOutputStream(); + byte[] buff = new byte[100]; + int rc = 0; + byte[] in_b = new byte[]{}; + try { + while ((rc = inStream.read(buff, 0, 100)) > 0) { + swapStream.write(buff, 0, rc); + } + in_b = swapStream.toByteArray(); + } catch (Exception e) { + e.printStackTrace(); + } + return in_b; + } +} \ No newline at end of file diff --git a/Utility/logging-handler/src/main/kotlin/co/nilin/opex/utility/log/interceptor/RequestFilter.java b/Utility/logging-handler/src/main/kotlin/co/nilin/opex/utility/log/interceptor/RequestFilter.java new file mode 100644 index 000000000..83d68b9cc --- /dev/null +++ b/Utility/logging-handler/src/main/kotlin/co/nilin/opex/utility/log/interceptor/RequestFilter.java @@ -0,0 +1,35 @@ +package co.nilin.opex.utility.log.interceptor; + +import co.nilin.opex.utility.log.interceptor.decorator.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebFilter; +import org.springframework.web.server.WebFilterChain; +import reactor.core.publisher.Mono; + +import java.util.UUID; + +@Component +@Order(2) +public class RequestFilter implements WebFilter { + + private Logger log = LoggerFactory.getLogger( RequestFilter.class); + + @Override + public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { + if ( exchange.getRequest().getPath().toString().startsWith("/actuator/health")) + return chain.filter(exchange); + long startTime = System.currentTimeMillis(); + String tracing = UUID.randomUUID().toString(); + return chain.filter(new PayloadServerWebExchangeDecorator(tracing, exchange)) + .doOnSuccess((done) -> success(tracing, startTime)); + } + + private void success(String tracing, long startTime) { + log.info("{}-Response Time:{} s", tracing, (System.currentTimeMillis() - startTime) / 1000.0); + } + +} \ No newline at end of file diff --git a/Utility/logging-handler/src/main/kotlin/co/nilin/opex/utility/log/interceptor/WebClientInterceptor.kt b/Utility/logging-handler/src/main/kotlin/co/nilin/opex/utility/log/interceptor/WebClientInterceptor.kt new file mode 100644 index 000000000..5dd922df3 --- /dev/null +++ b/Utility/logging-handler/src/main/kotlin/co/nilin/opex/utility/log/interceptor/WebClientInterceptor.kt @@ -0,0 +1,49 @@ +package co.nilin.opex.utility.log.interceptor + +import io.netty.buffer.ByteBuf +import io.netty.buffer.ByteBufHolder +import io.netty.channel.ChannelHandlerContext +import io.netty.handler.logging.LoggingHandler +import io.netty.util.internal.PlatformDependent.allocateUninitializedArray +import java.nio.charset.Charset +import java.nio.charset.Charset.defaultCharset + + +class CustomLogger(clazz: Class<*>?) : LoggingHandler(clazz) { + + override fun format(ctx: ChannelHandlerContext?, event: String?, arg: Any?): String { + return when (arg) { + is ByteBuf -> { + val msg = arg + decode(msg, msg.readerIndex(), msg.readableBytes(), defaultCharset()) + } + is ByteBufHolder -> { + val msg = arg.content() + decode(msg, msg.readerIndex(), msg.readableBytes(), defaultCharset()) + } + else -> { + super.format(ctx, event, arg) + } + } + } + + private fun decode(src: ByteBuf, readerIndex: Int, len: Int, charset: Charset): String { + if (len != 0) { + val array: ByteArray + val offset: Int + if (src.hasArray()) { + array = src.array() + offset = src.arrayOffset() + readerIndex + } else { + array = allocateUninitializedArray(Math.max(len, 1024)) + offset = 0 + src.getBytes(readerIndex, array, 0, len) + } + return String(array, offset, len, charset) + } + return "" + } + + + +} \ No newline at end of file diff --git a/Utility/logging-handler/src/main/kotlin/co/nilin/opex/utility/log/interceptor/decorator/PayloadBufferServerHttpRequestDecorator.java b/Utility/logging-handler/src/main/kotlin/co/nilin/opex/utility/log/interceptor/decorator/PayloadBufferServerHttpRequestDecorator.java new file mode 100644 index 000000000..36cbd2287 --- /dev/null +++ b/Utility/logging-handler/src/main/kotlin/co/nilin/opex/utility/log/interceptor/decorator/PayloadBufferServerHttpRequestDecorator.java @@ -0,0 +1,55 @@ +package co.nilin.opex.utility.log.interceptor.decorator; + +import co.nilin.opex.utility.log.interceptor.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpRequestDecorator; +import org.springframework.util.StringUtils; +import reactor.core.publisher.Flux; + + +import java.util.Optional; +import java.util.stream.Collectors; + +import static reactor.core.scheduler.Schedulers.single; + +public class PayloadBufferServerHttpRequestDecorator extends ServerHttpRequestDecorator { + + private Flux body; + private Logger log = LoggerFactory.getLogger(PayloadBufferServerHttpRequestDecorator.class); + + PayloadBufferServerHttpRequestDecorator(String tracing, ServerHttpRequest delegate) { + super(delegate); + final String path = delegate.getURI().getPath(); + final String query = delegate.getURI().getQuery(); + final String method = Optional.ofNullable(delegate.getMethod()).orElse(HttpMethod.GET).name(); + final String headers = delegate.getHeaders().entrySet() + .stream() + .map(entry -> " " + entry.getKey() + ": [" + String.join(";", entry.getValue()) + "]") + .collect(Collectors.joining("\n")); + final MediaType contentType = delegate.getHeaders().getContentType(); + if (log.isDebugEnabled()) { + log.debug("{}-" + + "HttpMethod : {}\n" + + "Uri : {}\n" + + "Headers : \n" + + "{}", tracing, method, path + (StringUtils.hasText(query) ? "?" + query : ""), headers); + } + Flux flux = super.getBody(); + if (LogUtils.legalLogMediaTypes.contains(contentType)) { + body = flux.publishOn(single()).map(dataBuffer -> LogUtils.loggingRequest(log, tracing, dataBuffer)); + } else { + body = flux; + } + } + + @Override + public Flux getBody() { + return body; + } + +} \ No newline at end of file diff --git a/Utility/logging-handler/src/main/kotlin/co/nilin/opex/utility/log/interceptor/decorator/PayloadBufferServerHttpResponseDecorator.java b/Utility/logging-handler/src/main/kotlin/co/nilin/opex/utility/log/interceptor/decorator/PayloadBufferServerHttpResponseDecorator.java new file mode 100644 index 000000000..90880f7b0 --- /dev/null +++ b/Utility/logging-handler/src/main/kotlin/co/nilin/opex/utility/log/interceptor/decorator/PayloadBufferServerHttpResponseDecorator.java @@ -0,0 +1,58 @@ +package co.nilin.opex.utility.log.interceptor.decorator; + + +import co.nilin.opex.utility.log.interceptor.*; +import org.reactivestreams.*; +import org.slf4j.*; +import org.springframework.core.io.buffer.*; +import org.springframework.http.*; +import org.springframework.http.server.reactive.*; +import reactor.core.publisher.*; + +import java.util.stream.*; + +import static reactor.core.scheduler.Schedulers.*; + +public class PayloadBufferServerHttpResponseDecorator extends ServerHttpResponseDecorator { + private Logger log = LoggerFactory.getLogger(PayloadBufferServerHttpResponseDecorator.class); + + private String tracing; + + PayloadBufferServerHttpResponseDecorator(String tracing, ServerHttpResponse delegate) { + super(delegate); + this.tracing = tracing; + final String headers = delegate.getHeaders().entrySet() + .stream() + .map(entry -> " " + entry.getKey() + ": [" + String.join(";", entry.getValue()) + "]") + .collect(Collectors.joining("\n")); + if (log.isDebugEnabled()) { + log.debug("{}-" + + "Response Status : {} \n" + + "Response Headers : \n" + + "{}", tracing, delegate.getRawStatusCode(), headers); + } + } + + @Override + public Mono writeAndFlushWith(Publisher> body) { + return super.writeAndFlushWith(body); + } + + @SuppressWarnings("unchecked") + @Override + public Mono writeWith(Publisher body) { + final MediaType contentType = super.getHeaders().getContentType(); + if (LogUtils.legalLogMediaTypes.stream().anyMatch(mt -> mt.isCompatibleWith(contentType))) { + if (body instanceof Mono) { + final Mono monoBody = (Mono) body; + return super.writeWith(monoBody.publishOn(single()) + .map(dataBuffer -> LogUtils.loggingResponse(log, tracing, dataBuffer))); + } else if (body instanceof Flux) { + final Flux monoBody = (Flux) body; + return super.writeWith(monoBody.publishOn(single()) + .map(dataBuffer -> LogUtils.loggingResponse(log, tracing, dataBuffer))); + } + } + return super.writeWith(body); + } +} \ No newline at end of file diff --git a/Utility/logging-handler/src/main/kotlin/co/nilin/opex/utility/log/interceptor/decorator/PayloadServerWebExchangeDecorator.java b/Utility/logging-handler/src/main/kotlin/co/nilin/opex/utility/log/interceptor/decorator/PayloadServerWebExchangeDecorator.java new file mode 100644 index 000000000..95593ec04 --- /dev/null +++ b/Utility/logging-handler/src/main/kotlin/co/nilin/opex/utility/log/interceptor/decorator/PayloadServerWebExchangeDecorator.java @@ -0,0 +1,33 @@ +package co.nilin.opex.utility.log.interceptor.decorator; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.ServerWebExchangeDecorator; + +public class PayloadServerWebExchangeDecorator extends ServerWebExchangeDecorator { + + private Logger log = LoggerFactory.getLogger( PayloadServerWebExchangeDecorator.class); + + private PayloadBufferServerHttpRequestDecorator requestDecorator; + + private PayloadBufferServerHttpResponseDecorator responseDecorator; + + public PayloadServerWebExchangeDecorator(String tracing, ServerWebExchange delegate) { + super(delegate); + requestDecorator = new PayloadBufferServerHttpRequestDecorator(tracing, delegate.getRequest()); + responseDecorator = new PayloadBufferServerHttpResponseDecorator(tracing, delegate.getResponse()); + } + + @Override + public ServerHttpRequest getRequest() { + return requestDecorator; + } + + @Override + public ServerHttpResponse getResponse() { + return responseDecorator; + } +} \ No newline at end of file diff --git a/Utility/pom.xml b/Utility/pom.xml new file mode 100644 index 000000000..2a7f7fc15 --- /dev/null +++ b/Utility/pom.xml @@ -0,0 +1,18 @@ + + + 4.0.0 + co.nilin.opex + utility-root + 1.0-SNAPSHOT + utility-root + pom + Utility root of Opex + + + error-handler + logging-handler + interceptors + + \ No newline at end of file diff --git a/Utility/utility-root.ipr b/Utility/utility-root.ipr new file mode 100644 index 000000000..eae68a1a1 --- /dev/null +++ b/Utility/utility-root.ipr @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Utility/utility-root.iws b/Utility/utility-root.iws new file mode 100644 index 000000000..03c854e98 --- /dev/null +++ b/Utility/utility-root.iws @@ -0,0 +1,418 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Wallet/.gitignore b/Wallet/.gitignore new file mode 100644 index 000000000..785786ed7 --- /dev/null +++ b/Wallet/.gitignore @@ -0,0 +1,47 @@ +# Created by .ignore support plugin (hsz.mobi) +### Kotlin template +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### JetBrains template +.idea/** +.idea +.idea/ +*.iml +*.ipr + +# File-based project format +*.iws + +# IntelliJ +out/ + +target/ + + + + +!/.idea/ + +.DS_Store diff --git a/Wallet/pom.xml b/Wallet/pom.xml new file mode 100644 index 000000000..cef5678c4 --- /dev/null +++ b/Wallet/pom.xml @@ -0,0 +1,18 @@ + + + 4.0.0 + co.nilin.opex + wallets + 1.0-SNAPSHOT + wallets + pom + Wallet managment root of Opex + + + wallet-core + wallet-app + wallet-ports/wallet-persister-postgres + wallet-ports/wallet-eventlistener-kafka + + diff --git a/Wallet/wallet-app/.gitignore b/Wallet/wallet-app/.gitignore new file mode 100644 index 000000000..0d6c2228e --- /dev/null +++ b/Wallet/wallet-app/.gitignore @@ -0,0 +1,35 @@ +HELP.md +target/ +.mvn/ +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +.DS_Store diff --git a/Wallet/wallet-app/Dockerfile b/Wallet/wallet-app/Dockerfile new file mode 100644 index 000000000..f2cbd4c26 --- /dev/null +++ b/Wallet/wallet-app/Dockerfile @@ -0,0 +1,5 @@ +FROM openjdk:8-jdk-alpine +VOLUME /tmp +ARG JAR_FILE=target/*.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"] \ No newline at end of file diff --git a/Wallet/wallet-app/mvnw b/Wallet/wallet-app/mvnw new file mode 100644 index 000000000..a16b5431b --- /dev/null +++ b/Wallet/wallet-app/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/Wallet/wallet-app/mvnw.cmd b/Wallet/wallet-app/mvnw.cmd new file mode 100644 index 000000000..c8d43372c --- /dev/null +++ b/Wallet/wallet-app/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/Wallet/wallet-app/pom.xml b/Wallet/wallet-app/pom.xml new file mode 100644 index 000000000..75143ea27 --- /dev/null +++ b/Wallet/wallet-app/pom.xml @@ -0,0 +1,252 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.4 + + + co.nilin.opex + wallet-app + 1.0-SNAPSHOT + wallet-app + Wallet managment app of Opex + + + 1.8 + 1.4.31 + 2020.0.2 + ${version} + ${version} + + + + + org.springframework.boot + spring-boot-starter-webflux + + + com.fasterxml.jackson.module + jackson-module-kotlin + + + + org.jetbrains.kotlin + kotlin-stdlib + + + io.projectreactor.kotlin + reactor-kotlin-extensions + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + + + org.jetbrains.kotlinx + kotlinx-coroutines-core + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + io.projectreactor + reactor-test + test + + + co.nilin.opex + wallet-core + ${wallet.version} + + + co.nilin.opex + wallet-persister-postgres + ${wallet.version} + + + co.nilin.opex + wallet-eventlistener-kafka + ${wallet.version} + + + co.nilin.opex + error-handler + ${utility.version} + + + co.nilin.opex + logging-handler + ${utility.version} + + + org.springframework.cloud + spring-cloud-starter-consul-all + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-oauth2-resource-server + + + org.bouncycastle + bcprov-jdk15on + 1.60 + + + io.springfox + springfox-boot-starter + 3.0.0 + + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + 2.18 + + + ${skip.unit.tests} + + + **/*IntegrationTest.java + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-test-source + generate-test-sources + + add-test-source + + + + src/test/java + + + + + compile + + add-source + + + + src/main/java + + + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + compile + + + + test-compile + test-compile + + test-compile + + + + + + -Xjsr305=strict + + + spring + + 1.8 + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + compile + compile + + compile + + + + testCompile + test-compile + + testCompile + + + + + + opex-wallet + + + diff --git a/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/WalletApp.kt b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/WalletApp.kt new file mode 100644 index 000000000..f0207afdd --- /dev/null +++ b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/WalletApp.kt @@ -0,0 +1,32 @@ +package co.nilin.opex.wallet.app + +import co.nilin.opex.utility.error.EnableOpexErrorHandler +import org.springframework.beans.factory.annotation.Value +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.ComponentScan +import org.springframework.security.core.annotation.AuthenticationPrincipal +import springfox.documentation.builders.ApiInfoBuilder +import springfox.documentation.builders.OAuthBuilder +import springfox.documentation.builders.PathSelectors.regex +import springfox.documentation.builders.RequestParameterBuilder +import springfox.documentation.service.* +import springfox.documentation.spi.DocumentationType +import springfox.documentation.spi.service.contexts.SecurityContext +import springfox.documentation.spring.web.plugins.Docket +import springfox.documentation.swagger.web.SecurityConfiguration +import springfox.documentation.swagger.web.SecurityConfigurationBuilder +import springfox.documentation.swagger2.annotations.EnableSwagger2 +import java.security.Principal +import java.util.Collections.singletonList + +@SpringBootApplication +@ComponentScan("co.nilin.opex") +@EnableSwagger2 +@EnableOpexErrorHandler +class WalletApp + +fun main(args: Array) { + runApplication(*args) +} diff --git a/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/config/AppConfig.kt b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/config/AppConfig.kt new file mode 100644 index 000000000..4b7dd85ce --- /dev/null +++ b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/config/AppConfig.kt @@ -0,0 +1,49 @@ +package co.nilin.opex.wallet.app.config + +import co.nilin.opex.auth.gateway.model.UserCreatedEvent +import co.nilin.opex.port.wallet.kafka.consumer.UserCreatedKafkaListener +import co.nilin.opex.port.wallet.kafka.spi.UserCreatedEventListener +import co.nilin.opex.wallet.app.service.UserRegistrationService +import kotlinx.coroutines.runBlocking +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Configuration +import org.springframework.stereotype.Component +import java.math.BigDecimal + +@Configuration +class AppConfig { + + @Value("\${app.gift.symbol}") + val symbol: String? = null + + @Value("\${app.gift.amount}") + val amount: BigDecimal? = null + + + @Autowired + fun configureUserCreatedEventListener( + useCreatedKafkaListener: UserCreatedKafkaListener, + userCreatedEventListener: UserCreatedEventListener + ) { + useCreatedKafkaListener.addEventListener(userCreatedEventListener) + } + + @Component + class WalletUserCreatedEventListener( + val userRegistrationService: UserRegistrationService + ) : UserCreatedEventListener { + + override fun id(): String { + return "UserCreatedEventListener" + } + + override fun onEvent(event: UserCreatedEvent, partition: Int, offset: Long, timestamp: Long) { + println("UserCreatedEvent " + event) + runBlocking(AppDispatchers.kafkaExecutor) { + userRegistrationService.registerNewUser(event) + } + println("onUserCreatedEvent") + } + } +} \ No newline at end of file diff --git a/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/config/AppDispatchers.kt b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/config/AppDispatchers.kt new file mode 100644 index 000000000..285781ccc --- /dev/null +++ b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/config/AppDispatchers.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.wallet.app.config + +import kotlinx.coroutines.asCoroutineDispatcher +import java.util.concurrent.Executors + +object AppDispatchers { + val kafkaExecutor = Executors.newSingleThreadExecutor().asCoroutineDispatcher() +} \ No newline at end of file diff --git a/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/config/SecurityConfig.kt b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/config/SecurityConfig.kt new file mode 100644 index 000000000..a87dd4114 --- /dev/null +++ b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/config/SecurityConfig.kt @@ -0,0 +1,39 @@ +package co.nilin.opex.wallet.app.config + +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Bean +import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity +import org.springframework.security.config.web.server.ServerHttpSecurity +import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder +import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder +import org.springframework.security.web.server.SecurityWebFilterChain +import org.springframework.web.reactive.function.client.WebClient + +@EnableWebFluxSecurity +class SecurityConfig(private val webClient: WebClient) { + + @Value("\${app.auth.cert-url}") + private lateinit var jwkUrl: String + + @Bean + fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain? { + http.csrf().disable() + .authorizeExchange() + .pathMatchers("/balanceOf/**").hasAuthority("SCOPE_trust") + .pathMatchers("/owner/**").hasAuthority("SCOPE_trust") + .pathMatchers("/**").permitAll() + .anyExchange().authenticated() + .and() + .oauth2ResourceServer() + .jwt() + return http.build() + } + + @Bean + @Throws(Exception::class) + fun reactiveJwtDecoder(): ReactiveJwtDecoder? { + return NimbusReactiveJwtDecoder.withJwkSetUri(jwkUrl) + .webClient(webClient) + .build() + } +} diff --git a/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/config/SwaggerConfig.kt b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/config/SwaggerConfig.kt new file mode 100644 index 000000000..76b80d954 --- /dev/null +++ b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/config/SwaggerConfig.kt @@ -0,0 +1,99 @@ +package co.nilin.opex.wallet.app.config + +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.security.core.annotation.AuthenticationPrincipal +import springfox.documentation.builders.ApiInfoBuilder +import springfox.documentation.builders.OAuthBuilder +import springfox.documentation.builders.PathSelectors +import springfox.documentation.builders.RequestParameterBuilder +import springfox.documentation.service.* +import springfox.documentation.spi.DocumentationType +import springfox.documentation.spi.service.contexts.SecurityContext +import springfox.documentation.spring.web.plugins.Docket +import springfox.documentation.swagger.web.SecurityConfiguration +import springfox.documentation.swagger.web.SecurityConfigurationBuilder +import java.security.Principal +import java.util.* + +@Configuration +class SwaggerConfig { + @Value("\${swagger.authUrl}") + val authUrl: String = "" + + @Bean + fun opexWallet(): Docket { + return Docket(DocumentationType.SWAGGER_2) + .groupName("opex-wallet") + .apiInfo(apiInfo()) + .select() + .paths(PathSelectors.regex("^/actuator.*").negate()) + .build() + .globalRequestParameters( + Collections.singletonList( + RequestParameterBuilder() + .name("content-type") + .description("content-type") + .`in`(ParameterType.HEADER) + .required(true) + .build() + ) + ) + .ignoredParameterTypes(AuthenticationPrincipal::class.java, Principal::class.java) + .useDefaultResponseMessages(false) + .securitySchemes(Collections.singletonList(oauth())) + .securityContexts(Collections.singletonList(securityContext())) + } + + private fun apiInfo(): ApiInfo { + return ApiInfoBuilder() + .title("OPEX API") + .description("Backend for opex exchange.") + .license("MIT License") + .licenseUrl("https://github.com/opexdev/Back-end/blob/feature/1-MVP/LICENSE") + .version("0.1") + .build() + } + + private fun oauth(): SecurityScheme { + return OAuthBuilder() + .name("opex") + .grantTypes(grantTypes()) + .scopes(scopes()) + .build() + } + + private fun scopes(): List { + return listOf(AuthorizationScope("openid", "OpenId")) + } + + private fun grantTypes(): List { + val tokenUrl = "$authUrl/auth/realms/opex/protocol/openid-connect/token" + val grantType = ResourceOwnerPasswordCredentialsGrant(tokenUrl) + return Collections.singletonList(grantType) + } + + private fun securityContext(): SecurityContext { + val securityReference = SecurityReference.builder() + .reference("opex") + .scopes(emptyArray()) + .build() + return SecurityContext.builder() + .securityReferences(Collections.singletonList(securityReference)) + .operationSelector { + it.requestMappingPattern().matches(Regex("^/(balanceOf|owner)/.*")) + } + .build() + } + + @Bean + fun securityInfo(): SecurityConfiguration { + return SecurityConfigurationBuilder.builder() + .clientId("admin-cli") + .realm("opex") + .appName("opex") + .scopeSeparator(",") + .build() + } +} \ No newline at end of file diff --git a/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/config/WebClientConfig.kt b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/config/WebClientConfig.kt new file mode 100644 index 000000000..877b7f232 --- /dev/null +++ b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/config/WebClientConfig.kt @@ -0,0 +1,25 @@ +package co.nilin.opex.wallet.app.config + +import org.springframework.cloud.client.ServiceInstance +import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties +import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer +import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerExchangeFilterFunction +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.web.reactive.function.client.WebClient + +@Configuration +class WebClientConfig { + + @Bean + fun webClient(loadBalancerFactory: ReactiveLoadBalancer.Factory): WebClient { + return WebClient.builder() + .filter( + ReactorLoadBalancerExchangeFilterFunction( + loadBalancerFactory, LoadBalancerProperties(), emptyList() + ) + ) + .build() + } + +} diff --git a/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/BalanceController.kt b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/BalanceController.kt new file mode 100644 index 000000000..714a9d207 --- /dev/null +++ b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/BalanceController.kt @@ -0,0 +1,46 @@ +package co.nilin.opex.wallet.app.controller + +import co.nilin.opex.wallet.core.spi.WalletManager +import co.nilin.opex.wallet.core.spi.WalletOwnerManager +import io.swagger.annotations.ApiResponse +import io.swagger.annotations.Example +import io.swagger.annotations.ExampleProperty +import org.slf4j.LoggerFactory +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.RestController +import java.math.BigDecimal +import java.security.Principal + +@RestController +class BalanceController( + val walletManager: WalletManager, val walletOwnerManager: WalletOwnerManager +) { + val logger = LoggerFactory.getLogger(BalanceController::class.java) + + data class BalanceResponse(val balance: BigDecimal) + + @GetMapping("/balanceOf/wallet_type/{wallet_type}/currency/{currency}") + @ApiResponse( + message = "OK", + code = 200, + examples = Example( + ExampleProperty( + value = "{ \"balance\": 990 }", + mediaType = "application/json" + ) + ) + ) + suspend fun getBalance( + principal: Principal, + @PathVariable("currency") currency: String, + @PathVariable("wallet_type") walletType: String + ): BalanceResponse { + val owner = walletOwnerManager.findWalletOwner(principal.name) + if (owner != null) { + val wallet = walletManager.findWalletByOwnerAndCurrencyAndType(owner, walletType, Symbol(currency)) + return BalanceResponse(wallet?.balance()?.amount ?: BigDecimal.ZERO) + } + return BalanceResponse(BigDecimal.ZERO) + } +} \ No newline at end of file diff --git a/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/InquiryController.kt b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/InquiryController.kt new file mode 100644 index 000000000..1aff512a9 --- /dev/null +++ b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/InquiryController.kt @@ -0,0 +1,51 @@ +package co.nilin.opex.wallet.app.controller + +import co.nilin.opex.wallet.core.model.Amount +import co.nilin.opex.wallet.core.spi.WalletManager +import co.nilin.opex.wallet.core.spi.WalletOwnerManager +import io.swagger.annotations.ApiResponse +import io.swagger.annotations.Example +import io.swagger.annotations.ExampleProperty +import org.slf4j.LoggerFactory +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.RestController +import java.math.BigDecimal + +@RestController +class InquiryController( + val walletManager: WalletManager, val walletOwnerManager: WalletOwnerManager +) { + val logger = LoggerFactory.getLogger(InquiryController::class.java) + + data class BooleanResponse(val result: Boolean) + @GetMapping("{uuid}/wallet_type/{wallet_type}/can_withdraw/{amount}_{currency}") + @ApiResponse( + message = "OK", + code = 200, + examples = Example( + ExampleProperty( + value = "{ }", + mediaType = "application/json" + ) + ) + ) + suspend fun canFulfill( + @PathVariable("uuid") uuid: String, + @PathVariable("currency") currency: String, + @PathVariable("wallet_type") walletType: String, + @PathVariable("amount") amount: BigDecimal + ): BooleanResponse { + logger.info("canFullFill: {} {} {} {}", uuid, currency, walletType, amount) + val owner = walletOwnerManager.findWalletOwner(uuid) + if (owner != null) { + val wallet = walletManager.findWalletByOwnerAndCurrencyAndType(owner, walletType, Symbol(currency)) + if (wallet != null) { + return BooleanResponse( + walletManager.isWithdrawAllowed(wallet, amount) + && walletOwnerManager.isWithdrawAllowed(owner, Amount(wallet.currency(), amount)) ) + } + } + return BooleanResponse(false) + } +} \ No newline at end of file diff --git a/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/Symbol.kt b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/Symbol.kt new file mode 100644 index 000000000..cff978685 --- /dev/null +++ b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/Symbol.kt @@ -0,0 +1,17 @@ +package co.nilin.opex.wallet.app.controller + +import co.nilin.opex.wallet.core.model.Currency + +class Symbol(val symbol_: String): Currency { + override fun getSymbol(): String { + TODO("Not yet implemented") + } + + override fun getName(): String { + return symbol_ + } + + override fun getPrecision(): Int { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/TransferController.kt b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/TransferController.kt new file mode 100644 index 000000000..03f0d2a80 --- /dev/null +++ b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/TransferController.kt @@ -0,0 +1,69 @@ +package co.nilin.opex.wallet.app.controller + +import co.nilin.opex.wallet.core.inout.TransferCommand +import co.nilin.opex.wallet.core.inout.TransferResult +import co.nilin.opex.wallet.core.model.Amount +import co.nilin.opex.wallet.core.service.TransferService +import co.nilin.opex.wallet.core.spi.WalletManager +import co.nilin.opex.wallet.core.spi.WalletOwnerManager +import io.swagger.annotations.ApiResponse +import io.swagger.annotations.Example +import io.swagger.annotations.ExampleProperty +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RestController +import java.lang.IllegalArgumentException +import java.math.BigDecimal + +@RestController +class TransferController( + val transferService: TransferService, val walletManager: WalletManager, val walletOwnerManager: WalletOwnerManager +) { + @PostMapping("/transfer/{amount}_{symbol}/from/{senderUuid}_{senderWalletType}/to/{receiverUuid}_{receiverWalletType}") + @ApiResponse( + message = "OK", + code = 200, + examples = Example( + ExampleProperty( + value = "{ }", + mediaType = "application/json" + ) + ) + ) + suspend fun transfer( + @PathVariable("symbol") symbol: String, + @PathVariable("senderWalletType") senderWalletType: String, + @PathVariable("senderUuid") senderUuid: String, + @PathVariable("receiverWalletType") receiverWalletType: String, + @PathVariable("receiverUuid") receiverUuid: String, + @PathVariable("amount") amount: BigDecimal, + @PathVariable("description") description: String?, + @PathVariable("transferRef") transferRef: String? + ): TransferResult { + val sourceOwner = walletOwnerManager.findWalletOwner(senderUuid) ?: throw IllegalArgumentException() + val sourceWallet = + walletManager.findWalletByOwnerAndCurrencyAndType(sourceOwner, senderWalletType, Symbol(symbol)) + ?: throw IllegalArgumentException() + val receiverOwner = walletOwnerManager.findWalletOwner(receiverUuid) ?: walletOwnerManager.createWalletOwner( + senderUuid, + "noset", + "" + ) + val receiverWallet = walletManager.findWalletByOwnerAndCurrencyAndType( + receiverOwner, receiverWalletType, Symbol(symbol) + ) ?: walletManager.createWallet( + receiverOwner, + Amount(Symbol(symbol), BigDecimal.ZERO), + Symbol(symbol), + receiverWalletType + ) + return transferService.transfer( + TransferCommand( + sourceWallet, + receiverWallet, + Amount(sourceWallet.currency(), amount), + description, transferRef + ) + ) + } +} \ No newline at end of file diff --git a/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/WalletOwnerController.kt b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/WalletOwnerController.kt new file mode 100644 index 000000000..29d84605e --- /dev/null +++ b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/WalletOwnerController.kt @@ -0,0 +1,70 @@ +package co.nilin.opex.wallet.app.controller + +import co.nilin.opex.utility.error.data.OpexError +import co.nilin.opex.utility.error.data.OpexException +import co.nilin.opex.utility.error.data.throwError +import co.nilin.opex.wallet.core.spi.WalletManager +import co.nilin.opex.wallet.core.spi.WalletOwnerManager +import io.swagger.annotations.ApiResponse +import io.swagger.annotations.Example +import io.swagger.annotations.ExampleProperty +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RestController +import java.math.BigDecimal +import java.security.Principal + +@RestController +class WalletOwnerController( + val walletManager: WalletManager, + val walletOwnerManager: WalletOwnerManager +) { + + data class WalletData( + val asset: String, + val balance: BigDecimal, + val type: String + ) + + data class OwnerLimitsResponse( + val canTrade: Boolean, + val canWithdraw: Boolean, + val canDeposit: Boolean + ) + + @GetMapping("/owner/wallet/all") + @ApiResponse( + message = "OK", + code = 200, + examples = Example( + ExampleProperty( + value = "{ }", + mediaType = "application/json" + ) + ) + ) + suspend fun getAllWallets(principal: Principal): List { + val owner = walletOwnerManager.findWalletOwner(principal.name) + ?: throw OpexException(OpexError.WalletOwnerNotFound) + val wallets = walletManager.findWalletsByOwner(owner) + return wallets.map { + WalletData(it.currency().getSymbol(), it.balance().amount, it.type()) + } + } + + @GetMapping("/owner/limits") + @ApiResponse( + message = "OK", + code = 200, + examples = Example( + ExampleProperty( + value = "{ }", + mediaType = "application/json" + ) + ) + ) + suspend fun getWalletOwnerLimits(principal: Principal): OwnerLimitsResponse { + val owner = walletOwnerManager.findWalletOwner(principal.name) + ?: throw OpexException(OpexError.WalletOwnerNotFound) + return OwnerLimitsResponse(owner.isTradeAllowed(), owner.isWithdrawAllowed(), owner.isDepositAllowed()) + } +} \ No newline at end of file diff --git a/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/listener/WalletListenerImpl.kt b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/listener/WalletListenerImpl.kt new file mode 100644 index 000000000..083b9d9aa --- /dev/null +++ b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/listener/WalletListenerImpl.kt @@ -0,0 +1,24 @@ +package co.nilin.opex.wallet.app.listener + +import co.nilin.opex.wallet.core.model.Amount +import co.nilin.opex.wallet.core.model.Wallet +import co.nilin.opex.wallet.core.spi.WalletListener +import org.springframework.stereotype.Component +import java.math.BigDecimal + +@Component +class WalletListenerImpl: WalletListener { + override fun onDeposit( + me: Wallet, + sourceWallet: Wallet, + amount: Amount, + finalAmount: BigDecimal, + transaction: String + ) { + + } + + override fun onWithdraw(me: Wallet, destWallet: Wallet, amount: Amount, transaction: String) { + + } +} \ No newline at end of file diff --git a/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/service/UserRegistrationService.kt b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/service/UserRegistrationService.kt new file mode 100644 index 000000000..349ad7523 --- /dev/null +++ b/Wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/service/UserRegistrationService.kt @@ -0,0 +1,44 @@ +package co.nilin.opex.wallet.app.service + +import co.nilin.opex.auth.gateway.model.UserCreatedEvent +import co.nilin.opex.wallet.app.controller.Symbol +import co.nilin.opex.wallet.core.model.Amount +import co.nilin.opex.wallet.core.model.Wallet +import co.nilin.opex.wallet.core.spi.WalletManager +import co.nilin.opex.wallet.core.spi.WalletOwnerManager +import org.springframework.beans.factory.annotation.Value +import org.springframework.stereotype.Component +import org.springframework.transaction.annotation.Transactional +import java.math.BigDecimal + +@Component +class UserRegistrationService( + val walletOwnerManager: WalletOwnerManager, + val walletManager: WalletManager, + @Value("\${app.gift.symbol}") + val symbol: Symbol, + @Value("\${app.gift.amount}") + val amount: BigDecimal +) { + @Transactional + suspend fun registerNewUser(event: UserCreatedEvent): Wallet { + val owner = + walletOwnerManager.createWalletOwner(event.uuid, "${event.firstName} ${event.lastName}", "1") + + val btcSymbol = Symbol("btc") + //TODO REMOVE LATER + walletManager.createWallet( + owner, + Amount(btcSymbol, BigDecimal.ONE), + btcSymbol, + "main" + ) + + return walletManager.createWallet( + owner, + Amount(symbol, amount), + symbol, + "main" + ) + } +} \ No newline at end of file diff --git a/Wallet/wallet-app/src/main/resources/application-docker.yml b/Wallet/wallet-app/src/main/resources/application-docker.yml new file mode 100644 index 000000000..ead9d6314 --- /dev/null +++ b/Wallet/wallet-app/src/main/resources/application-docker.yml @@ -0,0 +1,22 @@ +server.port: 8091 +spring: + application: + name: opex-wallet + kafka: + bootstrap-servers: ${KAFKA_IP_PORT} + redis: + host: ${REDIS_HOST} + port: 6379 + r2dbc: + url: r2dbc:postgresql://${DB_IP_PORT}/opex_wallet + username: opex + password: hiopex + initialization-mode: always + cloud: + consul: + host: ${CONSUL_HOST} + port: 8500 + +app: + auth: + cert-url: lb://opex-auth/auth/realms/opex/protocol/openid-connect/certs \ No newline at end of file diff --git a/Wallet/wallet-app/src/main/resources/application.yml b/Wallet/wallet-app/src/main/resources/application.yml new file mode 100644 index 000000000..1f82b108a --- /dev/null +++ b/Wallet/wallet-app/src/main/resources/application.yml @@ -0,0 +1,39 @@ +server.port: 8091 +spring: + application: + name: opex-wallet + main: + allow-bean-definition-overriding: false + kafka: + bootstrap-servers: localhost:2181 + consumer: + group-id: wallet + redis: + host: 127.0.0.1 + port: 6379 + r2dbc: + url: r2dbc:postgresql://localhost/opex_wallet + username: opex + password: hiopex + initialization-mode: always + cloud: + bootstrap: + enabled: true + consul: + port: 8500 + discovery: + #healthCheckPath: ${management.context-path}/health + instance-id: ${spring.application.name}:${server.port} + healthCheckInterval: 20s + prefer-ip-address: true +app: + gift: + symbol: usdt + amount: 1000 +logging: + level: + org.apache.kafka: DEBUG + co.nilin: DEBUG + reactor.netty.http.client: DEBUG + +swagger.authUrl: https://api.opex.dev diff --git a/Wallet/wallet-core/.gitignore b/Wallet/wallet-core/.gitignore new file mode 100644 index 000000000..8e22e6c21 --- /dev/null +++ b/Wallet/wallet-core/.gitignore @@ -0,0 +1,35 @@ +HELP.md +target/ +.mvn +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +.DS_Store diff --git a/Wallet/wallet-core/mvnw b/Wallet/wallet-core/mvnw new file mode 100644 index 000000000..a16b5431b --- /dev/null +++ b/Wallet/wallet-core/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/Wallet/wallet-core/mvnw.cmd b/Wallet/wallet-core/mvnw.cmd new file mode 100644 index 000000000..c8d43372c --- /dev/null +++ b/Wallet/wallet-core/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/Wallet/wallet-core/pom.xml b/Wallet/wallet-core/pom.xml new file mode 100644 index 000000000..366ecc638 --- /dev/null +++ b/Wallet/wallet-core/pom.xml @@ -0,0 +1,75 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.4 + + + co.nilin.opex + wallet-core + 1.0-SNAPSHOT + wallet-core + Wallet managment of Opex + + + 1.8 + 1.4.31 + + + + + org.springframework.boot + spring-boot-starter + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + -Xjsr305=strict + + + spring + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + + + diff --git a/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/exc/CurrencyNotMatchedException.kt b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/exc/CurrencyNotMatchedException.kt new file mode 100644 index 000000000..80cee2fb6 --- /dev/null +++ b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/exc/CurrencyNotMatchedException.kt @@ -0,0 +1,3 @@ +package co.nilin.opex.wallet.core.exc + +class CurrencyNotMatchedException: Exception() \ No newline at end of file diff --git a/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/exc/DepositLimitExceededException.kt b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/exc/DepositLimitExceededException.kt new file mode 100644 index 000000000..382eb31ed --- /dev/null +++ b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/exc/DepositLimitExceededException.kt @@ -0,0 +1,3 @@ +package co.nilin.opex.wallet.core.exc + +class DepositLimitExceededException: Exception() \ No newline at end of file diff --git a/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/exc/NotEnoughBalanceException.kt b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/exc/NotEnoughBalanceException.kt new file mode 100644 index 000000000..9dccc85ac --- /dev/null +++ b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/exc/NotEnoughBalanceException.kt @@ -0,0 +1,3 @@ +package co.nilin.opex.wallet.core.exc + +class NotEnoughBalanceException: Exception() \ No newline at end of file diff --git a/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/exc/WithdrawLimitExceededException.kt b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/exc/WithdrawLimitExceededException.kt new file mode 100644 index 000000000..ab8fc7389 --- /dev/null +++ b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/exc/WithdrawLimitExceededException.kt @@ -0,0 +1,3 @@ +package co.nilin.opex.wallet.core.exc + +class WithdrawLimitExceededException: Exception() \ No newline at end of file diff --git a/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/inout/TransferCommand.kt b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/inout/TransferCommand.kt new file mode 100644 index 000000000..5f0891eaa --- /dev/null +++ b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/inout/TransferCommand.kt @@ -0,0 +1,6 @@ +package co.nilin.opex.wallet.core.inout + +import co.nilin.opex.wallet.core.model.Amount +import co.nilin.opex.wallet.core.model.Wallet + +data class TransferCommand(val sourceWallet: Wallet, val destWallet: Wallet, val amount: Amount, val description: String?, val transferRef: String?) diff --git a/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/inout/TransferResult.kt b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/inout/TransferResult.kt new file mode 100644 index 000000000..32988898d --- /dev/null +++ b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/inout/TransferResult.kt @@ -0,0 +1,6 @@ +package co.nilin.opex.wallet.core.inout + +import co.nilin.opex.wallet.core.model.Amount +import java.time.LocalDateTime + +data class TransferResult(val date: Long, val sourceBalanceBeforeAction: Amount, val sourceBalanceAfterAction: Amount, val amount: Amount) \ No newline at end of file diff --git a/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/model/Amount.kt b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/model/Amount.kt new file mode 100644 index 000000000..28f59e99e --- /dev/null +++ b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/model/Amount.kt @@ -0,0 +1,5 @@ +package co.nilin.opex.wallet.core.model + +import java.math.BigDecimal + +data class Amount(val currency: Currency, val amount: BigDecimal) \ No newline at end of file diff --git a/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/model/Currency.kt b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/model/Currency.kt new file mode 100644 index 000000000..98a97b9d4 --- /dev/null +++ b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/model/Currency.kt @@ -0,0 +1,7 @@ +package co.nilin.opex.wallet.core.model + +interface Currency { + fun getSymbol(): String + fun getName(): String + fun getPrecision(): Int +} \ No newline at end of file diff --git a/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/model/Transaction.kt b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/model/Transaction.kt new file mode 100644 index 000000000..551d563f2 --- /dev/null +++ b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/model/Transaction.kt @@ -0,0 +1,5 @@ +package co.nilin.opex.wallet.core.model + +import java.math.BigDecimal + +data class Transaction(val sourceWallet: Wallet, val destWallet: Wallet, val sourceAmount: BigDecimal, val destAmount: BigDecimal, val description: String?, val transferRef: String?) \ No newline at end of file diff --git a/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/model/Wallet.kt b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/model/Wallet.kt new file mode 100644 index 000000000..4f4210a94 --- /dev/null +++ b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/model/Wallet.kt @@ -0,0 +1,9 @@ +package co.nilin.opex.wallet.core.model + +interface Wallet { + fun id(): Long? + fun owner(): WalletOwner + fun balance(): Amount + fun currency(): Currency + fun type(): String + } \ No newline at end of file diff --git a/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/model/WalletOwner.kt b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/model/WalletOwner.kt new file mode 100644 index 000000000..f054b6f69 --- /dev/null +++ b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/model/WalletOwner.kt @@ -0,0 +1,11 @@ +package co.nilin.opex.wallet.core.model + +interface WalletOwner { + fun id(): Long? + fun uuid(): String + fun title(): String + fun level(): String + fun isTradeAllowed(): Boolean + fun isWithdrawAllowed(): Boolean + fun isDepositAllowed(): Boolean +} \ No newline at end of file diff --git a/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/service/TransferService.kt b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/service/TransferService.kt new file mode 100644 index 000000000..05ffec62b --- /dev/null +++ b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/service/TransferService.kt @@ -0,0 +1,75 @@ +package co.nilin.opex.wallet.core.service + +import co.nilin.opex.wallet.core.exc.CurrencyNotMatchedException +import co.nilin.opex.wallet.core.exc.DepositLimitExceededException +import co.nilin.opex.wallet.core.exc.NotEnoughBalanceException +import co.nilin.opex.wallet.core.exc.WithdrawLimitExceededException +import co.nilin.opex.wallet.core.inout.TransferCommand +import co.nilin.opex.wallet.core.inout.TransferResult +import co.nilin.opex.wallet.core.model.Amount +import co.nilin.opex.wallet.core.model.Transaction +import co.nilin.opex.wallet.core.spi.CurrencyRateService +import co.nilin.opex.wallet.core.spi.TransactionManager +import co.nilin.opex.wallet.core.spi.WalletListener +import co.nilin.opex.wallet.core.spi.WalletManager +import co.nilin.opex.wallet.core.spi.WalletOwnerManager +import org.springframework.stereotype.Service +import java.time.LocalDateTime +import java.util.Date + +@Service +class TransferService( + val currencyRateService: CurrencyRateService, + val walletManager: WalletManager, + val walletListener: WalletListener, + val walletOwnerManager: WalletOwnerManager, + val transactionManager: TransactionManager +) { + + suspend fun transfer(transferCommand: TransferCommand): TransferResult { + //pre transfer hook (dispatch pre transfer event) + val srcWallet = transferCommand.sourceWallet + val srcWalletOwner = srcWallet.owner() + val srcWalletBalance = srcWallet.balance() + if (srcWallet.currency() != transferCommand.amount.currency) + throw CurrencyNotMatchedException() + if (srcWalletBalance.amount < transferCommand.amount.amount) + throw NotEnoughBalanceException() + if (!walletOwnerManager.isWithdrawAllowed(srcWalletOwner, transferCommand.amount)) + throw WithdrawLimitExceededException() + if (!walletManager.isWithdrawAllowed(srcWallet, transferCommand.amount.amount)) + throw WithdrawLimitExceededException() + + val destWallet = transferCommand.destWallet + val destWalletOwner = destWallet.owner() + val balance = destWallet.balance() + //check wallet if can accept the value type + val amountToTransfer = currencyRateService.convert(transferCommand.amount, destWallet.currency()) + + if (!walletOwnerManager.isDepositAllowed(destWalletOwner, Amount(destWallet.currency(), amountToTransfer))) + throw DepositLimitExceededException() + if (!walletManager.isDepositAllowed(destWallet, amountToTransfer)) + throw DepositLimitExceededException() + + walletManager.decreaseBalance(srcWallet, transferCommand.amount.amount) + walletManager.increaseBalance(destWallet, amountToTransfer) + val tx = transactionManager.save( + Transaction( + srcWallet, + destWallet, + transferCommand.amount.amount, + amountToTransfer, + transferCommand.description, + transferCommand.transferRef + ) + ) + //get the result and add to return result type + walletListener.onDeposit(destWallet, srcWallet, transferCommand.amount, amountToTransfer, tx) + walletListener.onWithdraw(srcWallet, destWallet, transferCommand.amount, tx) + + //post transfer hook(dispatch post transfer event) + + //notify balance change + return TransferResult(Date().time, srcWalletBalance, srcWallet.balance(), balance) + } +} \ No newline at end of file diff --git a/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/spi/CurrencyRateService.kt b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/spi/CurrencyRateService.kt new file mode 100644 index 000000000..3123e1f4b --- /dev/null +++ b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/spi/CurrencyRateService.kt @@ -0,0 +1,9 @@ +package co.nilin.opex.wallet.core.spi + +import co.nilin.opex.wallet.core.model.Currency +import co.nilin.opex.wallet.core.model.Amount +import java.math.BigDecimal + +interface CurrencyRateService { + suspend fun convert(amount: Amount, targetCurrency: Currency): BigDecimal +} diff --git a/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/spi/TransactionManager.kt b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/spi/TransactionManager.kt new file mode 100644 index 000000000..222a26308 --- /dev/null +++ b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/spi/TransactionManager.kt @@ -0,0 +1,7 @@ +package co.nilin.opex.wallet.core.spi + +import co.nilin.opex.wallet.core.model.Transaction + +interface TransactionManager { + suspend fun save(transaction: Transaction): String +} \ No newline at end of file diff --git a/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/spi/WalletListener.kt b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/spi/WalletListener.kt new file mode 100644 index 000000000..da58cc6cc --- /dev/null +++ b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/spi/WalletListener.kt @@ -0,0 +1,10 @@ +package co.nilin.opex.wallet.core.spi + +import co.nilin.opex.wallet.core.model.Amount +import co.nilin.opex.wallet.core.model.Wallet +import java.math.BigDecimal + +interface WalletListener { + fun onDeposit(me: Wallet, sourceWallet: Wallet, amount: Amount, finalAmount: BigDecimal, transaction: String) + fun onWithdraw(me: Wallet, destWallet: Wallet, amount: Amount, transaction: String) +} \ No newline at end of file diff --git a/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/spi/WalletManager.kt b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/spi/WalletManager.kt new file mode 100644 index 000000000..9ebdbbce8 --- /dev/null +++ b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/spi/WalletManager.kt @@ -0,0 +1,23 @@ +package co.nilin.opex.wallet.core.spi + +import co.nilin.opex.wallet.core.model.Amount +import co.nilin.opex.wallet.core.model.Currency +import co.nilin.opex.wallet.core.model.Wallet +import co.nilin.opex.wallet.core.model.WalletOwner +import java.math.BigDecimal + +interface WalletManager { + suspend fun isDepositAllowed(wallet: Wallet, amount: BigDecimal): Boolean + suspend fun isWithdrawAllowed(wallet: Wallet, amount: BigDecimal): Boolean + suspend fun increaseBalance(wallet: Wallet, amount: BigDecimal) + suspend fun decreaseBalance(wallet: Wallet, amount: BigDecimal) + suspend fun findWalletByOwnerAndCurrencyAndType(owner: WalletOwner, walletType: String, currency: Currency): Wallet? + suspend fun findWalletsByOwnerAndType(owner: WalletOwner, walletType: String): List + suspend fun findWalletsByOwner(owner: WalletOwner): List + suspend fun createWallet( + owner: WalletOwner, + balance: Amount, + currency: Currency, + type: String + ): Wallet +} \ No newline at end of file diff --git a/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/spi/WalletOwnerManager.kt b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/spi/WalletOwnerManager.kt new file mode 100644 index 000000000..11913f576 --- /dev/null +++ b/Wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/spi/WalletOwnerManager.kt @@ -0,0 +1,11 @@ +package co.nilin.opex.wallet.core.spi + +import co.nilin.opex.wallet.core.model.Amount +import co.nilin.opex.wallet.core.model.WalletOwner + +interface WalletOwnerManager { + suspend fun isDepositAllowed(owner: WalletOwner, amount: Amount): Boolean + suspend fun isWithdrawAllowed(owner: WalletOwner, amount: Amount): Boolean + suspend fun findWalletOwner(uuid: String): WalletOwner? + suspend fun createWalletOwner(uuid: String, title: String, userLevel: String): WalletOwner +} diff --git a/Wallet/wallet-core/src/main/resources/application.properties b/Wallet/wallet-core/src/main/resources/application.properties new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/Wallet/wallet-core/src/main/resources/application.properties @@ -0,0 +1 @@ + diff --git a/Wallet/wallet-ports/wallet-eventlistener-kafka/.gitignore b/Wallet/wallet-ports/wallet-eventlistener-kafka/.gitignore new file mode 100644 index 000000000..f4e066ca5 --- /dev/null +++ b/Wallet/wallet-ports/wallet-eventlistener-kafka/.gitignore @@ -0,0 +1,36 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ +!/.mvn/ + +.DS_Store diff --git a/Wallet/wallet-ports/wallet-eventlistener-kafka/mvnw b/Wallet/wallet-ports/wallet-eventlistener-kafka/mvnw new file mode 100644 index 000000000..a16b5431b --- /dev/null +++ b/Wallet/wallet-ports/wallet-eventlistener-kafka/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/Wallet/wallet-ports/wallet-eventlistener-kafka/mvnw.cmd b/Wallet/wallet-ports/wallet-eventlistener-kafka/mvnw.cmd new file mode 100644 index 000000000..c8d43372c --- /dev/null +++ b/Wallet/wallet-ports/wallet-eventlistener-kafka/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/Wallet/wallet-ports/wallet-eventlistener-kafka/pom.xml b/Wallet/wallet-ports/wallet-eventlistener-kafka/pom.xml new file mode 100644 index 000000000..7b8cba33b --- /dev/null +++ b/Wallet/wallet-ports/wallet-eventlistener-kafka/pom.xml @@ -0,0 +1,102 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.4 + + + co.nilin.opex + wallet-eventlistener-kafka + 1.0-SNAPSHOT + wallet-eventlistener-kafka + Wallet kafka listener of Opex + + + 1.8 + 1.4.31 + ${version} + ${version} + + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-webflux + + + + org.springframework.kafka + spring-kafka + + + io.projectreactor.kotlin + reactor-kotlin-extensions + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + + + org.jetbrains.kotlinx + kotlinx-coroutines-core + + + org.springframework.kafka + spring-kafka-test + test + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + -Xjsr305=strict + + + spring + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + + + diff --git a/Wallet/wallet-ports/wallet-eventlistener-kafka/src/main/kotlin/co/nilin/opex/auth/gateway/model/AuthEvent.kt b/Wallet/wallet-ports/wallet-eventlistener-kafka/src/main/kotlin/co/nilin/opex/auth/gateway/model/AuthEvent.kt new file mode 100644 index 000000000..0ecf3780e --- /dev/null +++ b/Wallet/wallet-ports/wallet-eventlistener-kafka/src/main/kotlin/co/nilin/opex/auth/gateway/model/AuthEvent.kt @@ -0,0 +1,7 @@ +package co.nilin.opex.auth.gateway.model + +import java.time.LocalDateTime + +open class AuthEvent { + var eventDate: LocalDateTime = LocalDateTime.now() +} \ No newline at end of file diff --git a/Wallet/wallet-ports/wallet-eventlistener-kafka/src/main/kotlin/co/nilin/opex/auth/gateway/model/UserCreatedEvent.kt b/Wallet/wallet-ports/wallet-eventlistener-kafka/src/main/kotlin/co/nilin/opex/auth/gateway/model/UserCreatedEvent.kt new file mode 100644 index 000000000..b15485c56 --- /dev/null +++ b/Wallet/wallet-ports/wallet-eventlistener-kafka/src/main/kotlin/co/nilin/opex/auth/gateway/model/UserCreatedEvent.kt @@ -0,0 +1,22 @@ +package co.nilin.opex.auth.gateway.model + +class UserCreatedEvent: AuthEvent { + lateinit var uuid: String + lateinit var firstName: String + lateinit var lastName: String + lateinit var email: String + + + constructor(uuid: String, firstName: String, lastName: String, email: String) : super() { + this.uuid = uuid + this.firstName = firstName + this.lastName = lastName + this.email = email + } + + constructor() : super() + + override fun toString(): String { + return "UserCreatedEvent(uuid='$uuid', firstName='$firstName', lastName='$lastName', email='$email')" + } +} \ No newline at end of file diff --git a/Wallet/wallet-ports/wallet-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/wallet/kafka/config/WalletKafkaConfig.kt b/Wallet/wallet-ports/wallet-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/wallet/kafka/config/WalletKafkaConfig.kt new file mode 100644 index 000000000..f7216abd1 --- /dev/null +++ b/Wallet/wallet-ports/wallet-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/wallet/kafka/config/WalletKafkaConfig.kt @@ -0,0 +1,84 @@ +package co.nilin.opex.port.wallet.kafka.config + +import co.nilin.opex.auth.gateway.model.UserCreatedEvent +import co.nilin.opex.port.wallet.kafka.consumer.UserCreatedKafkaListener +import org.apache.kafka.clients.admin.NewTopic +import org.apache.kafka.clients.consumer.ConsumerConfig +import org.apache.kafka.clients.producer.ProducerConfig +import org.apache.kafka.common.serialization.StringDeserializer +import org.apache.kafka.common.serialization.StringSerializer +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.beans.factory.annotation.Value +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.context.support.GenericApplicationContext +import org.springframework.kafka.core.* +import org.springframework.kafka.listener.ConcurrentMessageListenerContainer +import org.springframework.kafka.listener.ContainerProperties +import org.springframework.kafka.support.serializer.JsonDeserializer +import org.springframework.kafka.support.serializer.JsonSerializer +import java.util.regex.Pattern + +@Configuration +class WalletKafkaConfig { + @Value("\${spring.kafka.bootstrap-servers}") + private val bootstrapServers: String? = null + + @Value("\${spring.kafka.consumer.group-id}") + private val groupId: String? = null + + @Bean("walletConsumerConfig") + fun consumerConfigs(): Map? { + val props: MutableMap = HashMap() + props[ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG] = bootstrapServers + props[ConsumerConfig.GROUP_ID_CONFIG] = groupId + props[ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG] = StringDeserializer::class.java + props[ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG] = JsonDeserializer::class.java + props[JsonDeserializer.TRUSTED_PACKAGES] = "co.nilin.opex.*" + return props + } + + @Bean("walletConsumerFactory") + fun consumerFactory(@Qualifier("walletConsumerConfig") consumerConfigs: Map): ConsumerFactory { + return DefaultKafkaConsumerFactory(consumerConfigs) + } + + @Bean("walletProducerConfig") + fun producerConfigs(): Map { + val props: MutableMap = HashMap() + props[ProducerConfig.BOOTSTRAP_SERVERS_CONFIG] = bootstrapServers + props[ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG] = StringSerializer::class.java + props[ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG] = JsonSerializer::class.java + return props + } + + @Bean("walletProducerFactory") + fun producerFactory(@Qualifier("walletProducerConfig") producerConfigs: Map): ProducerFactory { + return DefaultKafkaProducerFactory(producerConfigs) + } + + @Bean("walletKafkaTemplate") + fun kafkaTemplate(@Qualifier("walletProducerFactory") producerFactory: ProducerFactory): KafkaTemplate { + return KafkaTemplate(producerFactory) + } + + + @Autowired + @ConditionalOnBean(UserCreatedKafkaListener::class) + fun configureUserCreatedListener(listener: UserCreatedKafkaListener, @Qualifier("walletConsumerFactory") consumerFactory: ConsumerFactory) { + val containerProps = ContainerProperties(Pattern.compile("auth_user_created")) + containerProps.messageListener = listener + val container = ConcurrentMessageListenerContainer(consumerFactory, containerProps) + container.setBeanName("UserCreatedKafkaListenerContainer") + container.start() + } + + + @Autowired + fun createUserCreatedTopics(applicationContext: GenericApplicationContext) { + applicationContext.registerBean("topic_auth_user_created", NewTopic::class.java, "auth_user_created", 1, 1) + } + +} \ No newline at end of file diff --git a/Wallet/wallet-ports/wallet-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/wallet/kafka/consumer/UserCreatedKafkaListener.kt b/Wallet/wallet-ports/wallet-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/wallet/kafka/consumer/UserCreatedKafkaListener.kt new file mode 100644 index 000000000..4fa267011 --- /dev/null +++ b/Wallet/wallet-ports/wallet-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/wallet/kafka/consumer/UserCreatedKafkaListener.kt @@ -0,0 +1,29 @@ +package co.nilin.opex.port.wallet.kafka.consumer + + +import co.nilin.opex.auth.gateway.model.UserCreatedEvent +import co.nilin.opex.port.wallet.kafka.spi.UserCreatedEventListener +import org.apache.kafka.clients.consumer.ConsumerRecord +import org.springframework.kafka.listener.MessageListener +import org.springframework.stereotype.Component + +@Component +class UserCreatedKafkaListener: MessageListener { + val eventListeners = arrayListOf() + + override fun onMessage(data: ConsumerRecord) { + eventListeners.forEach{ + tl -> tl.onEvent(data.value(), data.partition(), data.offset(), data.timestamp()) + } + } + + fun addEventListener(tl: UserCreatedEventListener){ + eventListeners.add(tl) + } + + fun removeEventListener(tl: UserCreatedEventListener){ + eventListeners.removeIf { + item -> item.id() == tl.id() + } + } +} \ No newline at end of file diff --git a/Wallet/wallet-ports/wallet-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/wallet/kafka/spi/UserCreatedEventListener.kt b/Wallet/wallet-ports/wallet-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/wallet/kafka/spi/UserCreatedEventListener.kt new file mode 100644 index 000000000..35168cac8 --- /dev/null +++ b/Wallet/wallet-ports/wallet-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/wallet/kafka/spi/UserCreatedEventListener.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.port.wallet.kafka.spi + +import co.nilin.opex.auth.gateway.model.UserCreatedEvent + +interface UserCreatedEventListener { + fun id(): String + fun onEvent(event: UserCreatedEvent, partition: Int, offset: Long, timestamp: Long) +} \ No newline at end of file diff --git a/Wallet/wallet-ports/wallet-persister-postgres/.gitignore b/Wallet/wallet-ports/wallet-persister-postgres/.gitignore new file mode 100644 index 000000000..f4e066ca5 --- /dev/null +++ b/Wallet/wallet-ports/wallet-persister-postgres/.gitignore @@ -0,0 +1,36 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ +!/.mvn/ + +.DS_Store diff --git a/Wallet/wallet-ports/wallet-persister-postgres/mvnw b/Wallet/wallet-ports/wallet-persister-postgres/mvnw new file mode 100644 index 000000000..a16b5431b --- /dev/null +++ b/Wallet/wallet-ports/wallet-persister-postgres/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/Wallet/wallet-ports/wallet-persister-postgres/mvnw.cmd b/Wallet/wallet-ports/wallet-persister-postgres/mvnw.cmd new file mode 100644 index 000000000..c8d43372c --- /dev/null +++ b/Wallet/wallet-ports/wallet-persister-postgres/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/Wallet/wallet-ports/wallet-persister-postgres/pom.xml b/Wallet/wallet-ports/wallet-persister-postgres/pom.xml new file mode 100644 index 000000000..51d4de352 --- /dev/null +++ b/Wallet/wallet-ports/wallet-persister-postgres/pom.xml @@ -0,0 +1,105 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.4 + + + co.nilin.opex + wallet-persister-postgres + 1.0-SNAPSHOT + wallet-persister-postgres + Persist items of Opex wallet on Postgres + + + 1.8 + 1.4.31 + ${version} + + + + + co.nilin.opex + wallet-core + ${wallet.version} + provided + + + org.springframework.boot + spring-boot-starter-data-r2dbc + + + io.r2dbc + r2dbc-postgresql + runtime + + + org.postgresql + postgresql + runtime + + + + io.projectreactor.kotlin + reactor-kotlin-extensions + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + + + org.jetbrains.kotlinx + kotlinx-coroutines-core + + + io.projectreactor + reactor-test + test + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + -Xjsr305=strict + + + spring + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + + diff --git a/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/config/PostgresConfig.kt b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/config/PostgresConfig.kt new file mode 100644 index 000000000..2d8433420 --- /dev/null +++ b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/config/PostgresConfig.kt @@ -0,0 +1,107 @@ +package co.nilin.opex.port.wallet.postgres.config + +import org.springframework.context.annotation.Configuration +import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories +import org.springframework.r2dbc.core.DatabaseClient + +@Configuration +@EnableR2dbcRepositories(basePackages = ["co.nilin.opex"]) +class PostgresConfig(db: DatabaseClient) { + + init { + val initDb = db.sql { + """ + CREATE TABLE IF NOT EXISTS currency ( + name VARCHAR(25) PRIMARY KEY, + symbol VARCHAR(25) NOT NULL UNIQUE, + precision numeric NOT NULL + ); + + CREATE TABLE IF NOT EXISTS currency_rate ( + id SERIAL PRIMARY KEY, + source_currency VARCHAR(25) NOT NULL, + dest_currency VARCHAR(25) NOT NULL, + rate decimal + ); + + CREATE TABLE IF NOT EXISTS transaction ( + id SERIAL PRIMARY KEY, + source_wallet numeric NOT NULL, + dest_wallet numeric NOT NULL, + source_amount decimal NOT NULL, + dest_amount decimal NOT NULL, + description VARCHAR(100), + transfer_ref VARCHAR(25), + transaction_date DATE NOT NULL DEFAULT CURRENT_DATE + ); + + CREATE TABLE IF NOT EXISTS wallet_owner ( + id SERIAL PRIMARY KEY, + uuid VARCHAR(36) NOT NULL UNIQUE, + title VARCHAR(70) NOT NULL, + level VARCHAR(10) NOT NULL, + trade_allowed BOOLEAN NOT NULL DEFAULT true, + withdraw_allowed BOOLEAN NOT NULL DEFAULT true, + deposit_allowed BOOLEAN NOT NULL DEFAULT true + ); + + CREATE TABLE IF NOT EXISTS wallet ( + id SERIAL PRIMARY KEY, + owner numeric NOT NULL, + wallet_type VARCHAR(10), + currency VARCHAR(25) NOT NULL, + balance decimal + ); + + CREATE TABLE IF NOT EXISTS user_limits ( + id SERIAL PRIMARY KEY, + level VARCHAR(10), + owner numeric, + action VARCHAR(25) NOT NULL, + wallet_type VARCHAR(10) NOT NULL, + daily_total decimal, + daily_count numeric, + monthly_total decimal, + monthly_count numeric + ); + + CREATE TABLE IF NOT EXISTS wallet_limits ( + id SERIAL PRIMARY KEY, + level VARCHAR(10), + owner numeric, + action VARCHAR(25) NOT NULL, + currency VARCHAR(25) NOT NULL, + wallet_type VARCHAR(10) NOT NULL, + wallet_id numeric, + daily_total decimal, + daily_count numeric, + monthly_total decimal, + monthly_count numeric + ); + + CREATE TABLE IF NOT EXISTS wallet_config ( + name VARCHAR(20) PRIMARY KEY, + main_currency VARCHAR(25) NOT NULL + ); + + insert into wallet_owner(id, uuid, title, level) values(1, '1', 'system', 'basic') ON CONFLICT DO NOTHING; + insert into currency(name, symbol, precision) values('btc', 'btc', 0.000001) ON CONFLICT DO NOTHING;; + insert into currency(name, symbol, precision) values('usdt', 'usdt', 0.01) ON CONFLICT DO NOTHING;; + insert into currency(name, symbol, precision) values('nln', 'nln', 1) ON CONFLICT DO NOTHING;; + insert into currency_rate(id, source_currency, dest_currency, rate) values(1, 'btc', 'nln', 5500000) ON CONFLICT DO NOTHING; + insert into currency_rate(id, source_currency, dest_currency, rate) values(1, 'usdt', 'nln', 100) ON CONFLICT DO NOTHING; + insert into currency_rate(id, source_currency, dest_currency, rate) values(1, 'btc', 'usdt', 55000) ON CONFLICT DO NOTHING; + insert into wallet(id, owner, wallet_type, currency, balance) values(1, 1, 'main', 'btc', 10) ON CONFLICT DO NOTHING; + insert into wallet(id, owner, wallet_type, currency, balance) values(2, 1, 'exchange', 'btc', 0) ON CONFLICT DO NOTHING; + insert into wallet(id, owner, wallet_type, currency, balance) values(3, 1, 'main', 'usdt', 550000) ON CONFLICT DO NOTHING; + insert into wallet(id, owner, wallet_type, currency, balance) values(4, 1, 'exchange', 'usdt', 0) ON CONFLICT DO NOTHING; + insert into wallet(id, owner, wallet_type, currency, balance) values(5, 1, 'main', 'nln', 100000000) ON CONFLICT DO NOTHING; + insert into wallet(id, owner, wallet_type, currency, balance) values(6, 1, 'exchange', 'nln', 0) ON CONFLICT DO NOTHING; + insert into user_limits (id, level, owner, action, wallet_type, daily_total, daily_count, monthly_total, monthly_count)values(1, null, 1, 'withdraw', 'main', 1000, 100, 10000, 1000) ON CONFLICT DO NOTHING; + """ + } + initDb // initialize the database + .then() + .subscribe() // execute + } +} diff --git a/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/dao/CurrencyRateRepository.kt b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/dao/CurrencyRateRepository.kt new file mode 100644 index 000000000..ff1647e21 --- /dev/null +++ b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/dao/CurrencyRateRepository.kt @@ -0,0 +1,18 @@ +package co.nilin.opex.port.wallet.postgres.dao + +import co.nilin.opex.port.wallet.postgres.model.CurrencyModel +import co.nilin.opex.port.wallet.postgres.model.CurrencyRateModel +import kotlinx.coroutines.flow.Flow +import org.springframework.data.domain.Pageable +import org.springframework.data.r2dbc.repository.Query +import org.springframework.data.repository.query.Param +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository +import reactor.core.publisher.Mono + +@Repository +interface CurrencyRateRepository: ReactiveCrudRepository { + @Query("select * from currency_rate where source_currency = :sourceCurrency and dest_currency = :destCurrency") + fun findBySourceAndDest(@Param("source") sourceCurrency: String + , @Param("dest") destCurrency: String): Mono +} \ No newline at end of file diff --git a/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/dao/CurrencyRepository.kt b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/dao/CurrencyRepository.kt new file mode 100644 index 000000000..f541673c5 --- /dev/null +++ b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/dao/CurrencyRepository.kt @@ -0,0 +1,16 @@ +package co.nilin.opex.port.wallet.postgres.dao + +import co.nilin.opex.port.wallet.postgres.model.CurrencyModel +import co.nilin.opex.port.wallet.postgres.model.CurrencyRateModel +import kotlinx.coroutines.flow.Flow +import org.springframework.data.domain.Pageable +import org.springframework.data.r2dbc.repository.Query +import org.springframework.data.repository.query.Param +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository +import reactor.core.publisher.Mono + +@Repository +interface CurrencyRepository: ReactiveCrudRepository { + +} \ No newline at end of file diff --git a/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/dao/TransactionRepository.kt b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/dao/TransactionRepository.kt new file mode 100644 index 000000000..398a77157 --- /dev/null +++ b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/dao/TransactionRepository.kt @@ -0,0 +1,72 @@ +package co.nilin.opex.port.wallet.postgres.dao + +import co.nilin.opex.port.wallet.postgres.dto.TransactionStat +import co.nilin.opex.port.wallet.postgres.model.CurrencyModel +import co.nilin.opex.port.wallet.postgres.model.CurrencyRateModel +import co.nilin.opex.port.wallet.postgres.model.TransactionModel +import kotlinx.coroutines.flow.Flow +import org.springframework.core.ParameterizedTypeReference +import org.springframework.data.domain.Pageable +import org.springframework.data.r2dbc.repository.Query +import org.springframework.data.repository.query.Param +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository +import reactor.core.publisher.Mono +import java.time.LocalDateTime +import java.util.* + +@Repository +interface TransactionRepository: ReactiveCrudRepository { + @Query("SELECT count(1) cnt, COALESCE(sum(source_amount * crm.rate), 0) total" + + " FROM transaction tm " + + " join wallet wm on wm.id = tm.source_wallet " + + " join currency_rate crm on wm.currency = crm.source_currency " + + " WHERE wm.owner = :owner " + + " and wm.wallet_type = :walletType " + + " and crm.dest_currency = :currency " + + " and tm.transaction_date >= :startDate " + + " and tm.transaction_date <= :endDate") + fun calculateWithdrawStatisticsBasedOnCurrency(@Param("owner") owner: Long + ,@Param("walletType") walletType: String + ,@Param("startDate") startDate: LocalDateTime + ,@Param("endDate") endDate: LocalDateTime + ,@Param("currency") currency: String): Mono + @Query("SELECT count(1) cnt, COALESCE(sum(source_amount), 0) total " + + " FROM TransactionModel tm " + + " join WalletModel wm on wm.id = tm.sourceWallet " + + " WHERE wm.owner = :owner " + + " and wm.id = :id " + + " and tm.transaction_date >= :startDate " + + " and tm.transaction_date <= :endDate") + fun calculateWithdrawStatistics(@Param("owner") owner: Long + ,@Param("walletId") wallet: Long + ,@Param("startDate") startDate: LocalDateTime + ,@Param("endDate") endDate: LocalDateTime): Mono + + @Query("SELECT count(1) cnt, COALESCE(sum(dest_amount),0) total " + + " FROM transaction tm " + + " join wallet wm on wm.id = tm.dest_wallet " + + " join currency_rate crm on wm.currency = crm.source_currency " + + " WHERE wm.owner = :owner " + + " and wm.wallet_type = :walletType " + + " and crm.dest_currency = :currency " + + " and tm.transaction_date >= :startDate " + + " and tm.transaction_date <= :endDate") + fun calculateDepositStatisticsBasedOnCurrency(@Param("owner") owner: Long + ,@Param("walletType") walletType: String + ,@Param("startDate") startDate: LocalDateTime + ,@Param("endDate") endDate: LocalDateTime + ,@Param("currency") currency: String): Mono + @Query("SELECT count(1) cnt, COALESCE(sum(dest_amount * crm.rate), 0) total" + + " FROM transaction tm " + + " join wallet wm on wm.id = tm.dest_wallet " + + " WHERE wm.owner = :owner " + + " and wm.id = :walletId " + + " and tm.transaction_date >= :startDate " + + " and tm.transaction_date <= :endDate") + fun calculateDepositStatistics(@Param("owner") owner: Long + ,@Param("walletId") wallet: Long + ,@Param("startDate") startDate: LocalDateTime + ,@Param("endDate") endDate: LocalDateTime): Mono + +} \ No newline at end of file diff --git a/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/dao/UserLimitsRepository.kt b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/dao/UserLimitsRepository.kt new file mode 100644 index 000000000..1430f034f --- /dev/null +++ b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/dao/UserLimitsRepository.kt @@ -0,0 +1,26 @@ +package co.nilin.opex.port.wallet.postgres.dao + +import co.nilin.opex.port.wallet.postgres.model.UserLimitsModel +import co.nilin.opex.port.wallet.postgres.model.WalletLimitsModel +import kotlinx.coroutines.flow.Flow +import org.springframework.data.r2dbc.repository.Query +import org.springframework.data.repository.query.Param +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository +import reactor.core.publisher.Mono + +@Repository +interface UserLimitsRepository : ReactiveCrudRepository { + @Query("select * from user_limits where level = :level and action = :action and owner is null") + fun findByLevelAndAction( + @Param("level") level: String, + @Param("action") action: String + ): Flow + + @Query("select * from user_limits where owner = :owner and action = :action") + fun findByOwnerAndAction( + @Param("owner") owner: Long, + @Param("action") action: String + ): Flow + +} \ No newline at end of file diff --git a/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/dao/WalletConfigRepository.kt b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/dao/WalletConfigRepository.kt new file mode 100644 index 000000000..91bd67cc1 --- /dev/null +++ b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/dao/WalletConfigRepository.kt @@ -0,0 +1,12 @@ +package co.nilin.opex.port.wallet.postgres.dao + +import co.nilin.opex.port.wallet.postgres.model.WalletConfigModel +import co.nilin.opex.port.wallet.postgres.model.WalletModel +import org.springframework.data.r2dbc.repository.Query +import org.springframework.data.repository.query.Param +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository +import reactor.core.publisher.Mono + +@Repository +interface WalletConfigRepository : ReactiveCrudRepository \ No newline at end of file diff --git a/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/dao/WalletLimitsRepository.kt b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/dao/WalletLimitsRepository.kt new file mode 100644 index 000000000..48388eaf4 --- /dev/null +++ b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/dao/WalletLimitsRepository.kt @@ -0,0 +1,35 @@ +package co.nilin.opex.port.wallet.postgres.dao + +import co.nilin.opex.port.wallet.postgres.model.WalletLimitsModel +import org.springframework.data.r2dbc.repository.Query +import org.springframework.data.repository.query.Param +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository +import reactor.core.publisher.Mono + +@Repository +interface WalletLimitsRepository : ReactiveCrudRepository { + @Query("select * from wallet_limits where level = :level and currency = :currency and action = :action and wallet_type = :walletType") + fun findByLevelAndCurrencyAndActionAndWalletType( + @Param("level") level: String, + @Param("currency") currency: String, + @Param("action") action: String, + @Param("walletType") walletType: String + ): Mono + + @Query("select * from wallet_Limits where owner = :owner and currency = :currency and action = :action and wallet_type = :walletType") + fun findByOwnerAndCurrencyAndActionAndWalletType( + @Param("owner") owner: Long, + @Param("currency") currency: String, + @Param("action") action: String, + @Param("walletType") walletType: String + ): Mono + + @Query("select * from wallet_limits where owner = :owner and currency = :currency and action = :action and wallet_id = :wallet") + fun findByOwnerAndCurrencyAndWalletAndAction( + @Param("owner") owner: Long, + @Param("currency") currency: String, + @Param("wallet") wallet: Long, + @Param("action") action: String + ): Mono +} \ No newline at end of file diff --git a/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/dao/WalletOwnerRepository.kt b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/dao/WalletOwnerRepository.kt new file mode 100644 index 000000000..4504172b1 --- /dev/null +++ b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/dao/WalletOwnerRepository.kt @@ -0,0 +1,14 @@ +package co.nilin.opex.port.wallet.postgres.dao + +import co.nilin.opex.port.wallet.postgres.model.WalletOwnerModel +import org.springframework.data.r2dbc.repository.Query +import org.springframework.data.repository.query.Param +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository +import reactor.core.publisher.Mono + +@Repository +interface WalletOwnerRepository : ReactiveCrudRepository { + @Query("select * from wallet_owner where uuid = :uuid") + fun findByUuid(@Param("uuid") uuid: String): Mono +} \ No newline at end of file diff --git a/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/dao/WalletRepository.kt b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/dao/WalletRepository.kt new file mode 100644 index 000000000..2a3c27321 --- /dev/null +++ b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/dao/WalletRepository.kt @@ -0,0 +1,35 @@ +package co.nilin.opex.port.wallet.postgres.dao + +import co.nilin.opex.port.wallet.postgres.model.WalletModel +import org.springframework.data.r2dbc.repository.Modifying +import org.springframework.data.r2dbc.repository.Query +import org.springframework.data.repository.query.Param +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono +import java.math.BigDecimal + +@Repository +interface WalletRepository : ReactiveCrudRepository { + + @Query("select * from wallet where owner = :owner and wallet_type = :type and currency = :currency ") + fun findByOwnerAndTypeAndCurrency( + @Param("owner") owner: Long, + @Param("type") type: String, + @Param("currency") currency: String + ): Mono + + @Query("select * from wallet where owner = :owner and wallet_type = :type") + fun findByOwnerAndType( + @Param("owner") owner: Long, + @Param("type") type: String, + ): Flux + + @Query("select * from wallet where owner = :owner") + fun findByOwner(@Param("owner") owner: Long): Flux + + @Modifying + @Query("update wallet set balance = balance + :balance where id = :id") + fun updateBalance(id: Long, delta: BigDecimal): Mono +} \ No newline at end of file diff --git a/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/dto/SavedWallet.kt b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/dto/SavedWallet.kt new file mode 100644 index 000000000..ebc6ecf29 --- /dev/null +++ b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/dto/SavedWallet.kt @@ -0,0 +1,30 @@ +package co.nilin.opex.port.wallet.postgres.dto + +import co.nilin.opex.wallet.core.model.Amount +import co.nilin.opex.wallet.core.model.Currency +import co.nilin.opex.wallet.core.model.Wallet +import co.nilin.opex.wallet.core.model.WalletOwner + +class SavedWallet( + val id_: Long, val owner_: WalletOwner, val balance_: Amount, val currency_: Currency, val type_: String +) : Wallet { + override fun id(): Long { + return id_ + } + + override fun owner(): WalletOwner { + return owner_ + } + + override fun balance(): Amount { + return balance_ + } + + override fun currency(): Currency { + return currency_ + } + + override fun type(): String { + return type_ + } +} \ No newline at end of file diff --git a/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/dto/TransactionStat.kt b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/dto/TransactionStat.kt new file mode 100644 index 000000000..a7e0800dd --- /dev/null +++ b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/dto/TransactionStat.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.port.wallet.postgres.dto + +import java.math.BigDecimal + +class TransactionStat( + val cnt: Long?, + val total: BigDecimal? +) \ No newline at end of file diff --git a/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/impl/CurrencyRateServiceImpl.kt b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/impl/CurrencyRateServiceImpl.kt new file mode 100644 index 000000000..151ba759b --- /dev/null +++ b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/impl/CurrencyRateServiceImpl.kt @@ -0,0 +1,32 @@ +package co.nilin.opex.port.wallet.postgres.impl + +import co.nilin.opex.port.wallet.postgres.dao.CurrencyRateRepository +import co.nilin.opex.wallet.core.model.Amount +import co.nilin.opex.wallet.core.model.Currency +import co.nilin.opex.wallet.core.spi.CurrencyRateService +import kotlinx.coroutines.reactive.awaitFirstOrDefault +import kotlinx.coroutines.reactive.awaitFirstOrNull +import org.springframework.stereotype.Service +import java.math.BigDecimal + +@Service +class CurrencyRateServiceImpl(val currencyRateRepository: CurrencyRateRepository) : CurrencyRateService { + override suspend fun convert(amount: Amount, targetCurrency: Currency): BigDecimal { + if (amount.currency.getName() == targetCurrency.getName()) + return amount.amount + + var rate = currencyRateRepository.findBySourceAndDest( + amount.currency.getName(), targetCurrency.getName() + ) + .map { BigDecimal.valueOf(it!!.rate) } + .awaitFirstOrNull() + if (rate != null) { + rate = currencyRateRepository.findBySourceAndDest( + targetCurrency.getName(), amount.currency.getName() + ) + .map { BigDecimal.valueOf(it!!.rate) } + .awaitFirstOrDefault(BigDecimal.ZERO) + } + return amount.amount.multiply(rate) + } +} \ No newline at end of file diff --git a/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/impl/TransactionManagerImpl.kt b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/impl/TransactionManagerImpl.kt new file mode 100644 index 000000000..b1e94f141 --- /dev/null +++ b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/impl/TransactionManagerImpl.kt @@ -0,0 +1,27 @@ +package co.nilin.opex.port.wallet.postgres.impl + +import co.nilin.opex.port.wallet.postgres.dao.TransactionRepository +import co.nilin.opex.port.wallet.postgres.model.TransactionModel +import co.nilin.opex.wallet.core.model.Transaction +import co.nilin.opex.wallet.core.spi.TransactionManager +import kotlinx.coroutines.reactive.awaitSingle +import org.springframework.stereotype.Service +import java.time.LocalDateTime + +@Service +class TransactionManagerImpl(val transactionRepository: TransactionRepository) : TransactionManager { + override suspend fun save(transaction: Transaction): String { + return transactionRepository.save( + TransactionModel( + null, + transaction.sourceWallet.id()!!, + transaction.destWallet.id()!!, + transaction.sourceAmount, + transaction.destAmount, + transaction.description, + transaction.transferRef, + LocalDateTime.now() + ) + ).awaitSingle().id.toString() + } +} \ No newline at end of file diff --git a/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/impl/WalletManagerImpl.kt b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/impl/WalletManagerImpl.kt new file mode 100644 index 000000000..e0d1b8d79 --- /dev/null +++ b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/impl/WalletManagerImpl.kt @@ -0,0 +1,190 @@ +package co.nilin.opex.port.wallet.postgres.impl + +import co.nilin.opex.port.wallet.postgres.dao.* +import co.nilin.opex.port.wallet.postgres.dto.SavedWallet +import co.nilin.opex.port.wallet.postgres.model.WalletModel +import co.nilin.opex.wallet.core.model.Amount +import co.nilin.opex.wallet.core.model.Currency +import co.nilin.opex.wallet.core.model.Wallet +import co.nilin.opex.wallet.core.model.WalletOwner +import co.nilin.opex.wallet.core.spi.WalletManager +import kotlinx.coroutines.reactive.awaitFirst +import kotlinx.coroutines.reactive.awaitFirstOrNull +import kotlinx.coroutines.reactive.awaitSingle +import org.springframework.stereotype.Service +import java.math.BigDecimal +import java.time.LocalDateTime + +@Service +class WalletManagerImpl( + val walletLimitsRepository: WalletLimitsRepository, + val transactionRepository: TransactionRepository, + val walletRepository: WalletRepository, + val walletOwnerRepository: WalletOwnerRepository, + val currencyRepository: CurrencyRepository +) : WalletManager { + + override suspend fun isDepositAllowed(wallet: Wallet, amount: BigDecimal): Boolean { + var limit = walletLimitsRepository.findByOwnerAndCurrencyAndWalletAndAction( + wallet.owner().id()!!, wallet.currency().getName(), wallet.id()!!, "deposit" + ).awaitFirstOrNull() + if (limit == null) { + limit = walletLimitsRepository.findByOwnerAndCurrencyAndActionAndWalletType( + wallet.owner().id()!!, wallet.currency().getName(), wallet.type(), "deposit" + ).awaitFirstOrNull() + if (limit == null) { + limit = walletLimitsRepository.findByLevelAndCurrencyAndActionAndWalletType( + wallet.owner().level(), wallet.currency().getName(), wallet.type(), "deposit" + ).awaitFirstOrNull() + } + } + var evaluate = true + if (limit != null) { + if (limit.dailyCount != null || limit.dailyTotal != null) { + val ts = transactionRepository.calculateDepositStatistics( + wallet.owner().id()!!, wallet.id()!!, LocalDateTime.now().minusDays(1) + .withHour(0).withMinute(0).withSecond(0), LocalDateTime.now() + ).awaitFirstOrNull() + if (ts != null) { + evaluate = (limit.dailyCount != null && ts.cnt!! >= limit.dailyCount!!) + || (limit.dailyTotal != null && ts.total!! >= limit.dailyTotal) + } + } + + if (evaluate && (limit.monthlyCount != null || limit.monthlyTotal != null)) { + val ts = transactionRepository.calculateDepositStatistics( + wallet.owner().id()!!, wallet.id()!!, LocalDateTime.now().minusMonths(1) + .withDayOfMonth(1) + .withHour(0).withMinute(0).withSecond(0), LocalDateTime.now() + ).awaitFirstOrNull() + if (ts != null) { + evaluate = (limit.dailyCount != null && ts.cnt!! >= limit.dailyCount!!) + || (limit.dailyTotal != null && ts.total!! >= limit.dailyTotal) + } + } + } + return evaluate + } + + override suspend fun isWithdrawAllowed(wallet: Wallet, amount: BigDecimal): Boolean { + var evaluate = wallet.balance().amount >= amount + if (evaluate) { + var limit = walletLimitsRepository.findByOwnerAndCurrencyAndWalletAndAction( + wallet.owner().id()!!, wallet.currency().getName(), wallet.id()!!, "withdraw" + ).awaitFirstOrNull() + if (limit == null) { + limit = walletLimitsRepository.findByOwnerAndCurrencyAndActionAndWalletType( + wallet.owner().id()!!, wallet.currency().getName(), wallet.type(), "withdraw" + ).awaitFirstOrNull() + if (limit == null) { + limit = walletLimitsRepository.findByLevelAndCurrencyAndActionAndWalletType( + wallet.owner().level(), wallet.currency().getName(), wallet.type(), "withdraw" + ).awaitFirstOrNull() + } + } + + if (limit != null) { + if (limit.dailyCount != null || limit.dailyTotal != null) { + val ts = transactionRepository.calculateWithdrawStatistics( + wallet.owner().id()!!, wallet.id()!!, LocalDateTime.now().minusDays(1) + .withHour(0).withMinute(0).withSecond(0), LocalDateTime.now() + ).awaitFirstOrNull() + if (ts != null) { + evaluate = (limit.dailyCount != null && ts.cnt!! >= limit.dailyCount!!) + || (limit.dailyTotal != null && ts.total!! >= limit.dailyTotal) + } + } + + if (evaluate && (limit.monthlyCount != null || limit.monthlyTotal != null)) { + val ts = transactionRepository.calculateWithdrawStatistics( + wallet.owner().id()!!, wallet.id()!!, LocalDateTime.now().minusMonths(1) + .withDayOfMonth(1) + .withHour(0).withMinute(0).withSecond(0), LocalDateTime.now() + ).awaitFirstOrNull() + if (ts != null) { + evaluate = (limit.dailyCount != null && ts.cnt!! >= limit.dailyCount!!) + || (limit.dailyTotal != null && ts.total!! >= limit.dailyTotal) + } + } + } + } + return evaluate + } + + override suspend fun increaseBalance(wallet: Wallet, amount: BigDecimal) { + walletRepository.updateBalance(wallet.id()!!, amount).awaitFirst() + } + + override suspend fun decreaseBalance(wallet: Wallet, amount: BigDecimal) { + walletRepository.updateBalance(wallet.id()!!, -amount).awaitFirst() + } + + override suspend fun findWalletByOwnerAndCurrencyAndType( + owner: WalletOwner, + walletType: String, + currency: Currency + ): Wallet? { + val walletModel = walletRepository.findByOwnerAndTypeAndCurrency( + owner.id()!!, walletType, currency.getName() + ).awaitFirstOrNull() + if (walletModel == null) + return null + val existingCurrency = currencyRepository.findById(walletModel.currency).awaitFirst() + return SavedWallet( + walletModel.id!!, + walletOwnerRepository.findById(walletModel.owner).awaitFirst(), + Amount(existingCurrency, walletModel.balance), + existingCurrency, + walletModel.type + ) + } + + override suspend fun findWalletsByOwnerAndType(owner: WalletOwner, walletType: String): List { + val ownerModel = walletOwnerRepository.findById(owner.id()!!).awaitFirst() + return walletRepository.findByOwnerAndType(owner.id()!!, walletType) + .collectList() + .awaitSingle() + .map { + val currency = currencyRepository.findById(it.currency).awaitFirst() + SavedWallet( + it.id!!, + ownerModel, + Amount(currency, it.balance), + currency, + it.type + ) + } + } + + override suspend fun findWalletsByOwner(owner: WalletOwner): List { + val ownerModel = walletOwnerRepository.findById(owner.id()!!).awaitFirst() + return walletRepository.findByOwner(owner.id()!!) + .collectList() + .awaitSingle() + .map { + val currency = currencyRepository.findById(it.currency).awaitFirst() + SavedWallet( + it.id!!, + ownerModel, + Amount(currency, it.balance), + currency, + it.type + ) + } + } + + override suspend fun createWallet(owner: WalletOwner, balance: Amount, currency: Currency, type: String): Wallet { + val walletModel = walletRepository + .save(WalletModel(null, owner.id()!!, type, currency.getName(), balance.amount)) + .awaitFirst() + val wallet = SavedWallet( + walletModel.id!!, + owner, + Amount(currency, walletModel.balance), + currency, + walletModel.type + ) + return wallet + + } +} \ No newline at end of file diff --git a/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/impl/WalletOwnerManagerImpl.kt b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/impl/WalletOwnerManagerImpl.kt new file mode 100644 index 000000000..5cd57367d --- /dev/null +++ b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/impl/WalletOwnerManagerImpl.kt @@ -0,0 +1,149 @@ +package co.nilin.opex.port.wallet.postgres.impl + +import co.nilin.opex.port.wallet.postgres.dao.TransactionRepository +import co.nilin.opex.port.wallet.postgres.dao.UserLimitsRepository +import co.nilin.opex.port.wallet.postgres.dao.WalletConfigRepository +import co.nilin.opex.port.wallet.postgres.dao.WalletOwnerRepository +import co.nilin.opex.port.wallet.postgres.model.UserLimitsModel +import co.nilin.opex.port.wallet.postgres.model.WalletConfigModel +import co.nilin.opex.port.wallet.postgres.model.WalletOwnerModel +import co.nilin.opex.wallet.core.model.Amount +import co.nilin.opex.wallet.core.model.WalletOwner +import co.nilin.opex.wallet.core.spi.WalletOwnerManager +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEmpty +import kotlinx.coroutines.flow.reduce +import kotlinx.coroutines.reactive.awaitFirst +import kotlinx.coroutines.reactive.awaitFirstOrDefault +import kotlinx.coroutines.reactive.awaitFirstOrNull +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service +import java.time.LocalDateTime + +@Service +class WalletOwnerManagerImpl( + val limitsRepository: UserLimitsRepository, + val transactionRepository: TransactionRepository, + val walletConfigRepository: WalletConfigRepository, + val walletOwnerRepository: WalletOwnerRepository +) : WalletOwnerManager { + + val logger = LoggerFactory.getLogger(WalletOwnerManager::class.java) + + override suspend fun isDepositAllowed(owner: WalletOwner, amount: Amount): Boolean { + var evaluate: Boolean? = limitsRepository.findByOwnerAndAction( + owner.id()!!, + "deposit" + ).map { limit -> + evaluateLimit(limit, owner, true) + }.onEmpty { + emit(true) + } + .reduce { a, b -> + a && b + } + if (evaluate == null) { + evaluate = limitsRepository.findByLevelAndAction( + owner.level(), + "deposit" + ) + .map { limit -> + evaluateLimit(limit, owner, true) + }.onEmpty { + emit(true) + }.reduce { a, b -> + a && b + } + } + logger.info("isDepositAllowed: {} {}{} {}", owner.uuid(), amount.amount, amount.currency.getName(), evaluate) + return evaluate + } + + private suspend fun evaluateLimit( + limit: UserLimitsModel?, + owner: WalletOwner, + deposit: Boolean + ): Boolean { + var evaluate = true + if (limit != null) { + val mainCurrency = walletConfigRepository.findAll() + .map { t: WalletConfigModel -> t.mainCurrency } + .awaitFirstOrDefault("BTC") + if (limit.dailyCount != null || limit.dailyTotal != null) { + val ts = if (deposit) { + transactionRepository.calculateDepositStatisticsBasedOnCurrency( + owner.id()!!, limit.walletType, LocalDateTime.now().minusDays(1) + .withHour(0).withMinute(0).withSecond(0), LocalDateTime.now(), mainCurrency + ) + } else { + transactionRepository.calculateWithdrawStatisticsBasedOnCurrency( + owner.id()!!, limit.walletType, LocalDateTime.now().minusDays(1) + .withHour(0).withMinute(0).withSecond(0), LocalDateTime.now(), mainCurrency + ) + }.awaitFirstOrNull() + if (ts != null) { + evaluate = !((limit.dailyCount != null && ts.cnt!! >= limit.dailyCount) + || (limit.dailyTotal != null && ts.total!! >= limit.dailyTotal)) + } + } + if (evaluate) { + if (limit.monthlyCount != null || limit.monthlyTotal != null) { + val ts = if (deposit) { + transactionRepository.calculateDepositStatisticsBasedOnCurrency( + owner.id()!!, limit.walletType, LocalDateTime.now().minusMonths(1).withDayOfMonth(1) + .withHour(0).withMinute(0).withSecond(0), LocalDateTime.now(), mainCurrency + ) + } else { + transactionRepository.calculateWithdrawStatisticsBasedOnCurrency( + owner.id()!!, limit.walletType, LocalDateTime.now().minusMonths(1).withDayOfMonth(1) + .withHour(0).withMinute(0).withSecond(0), LocalDateTime.now(), mainCurrency + ) + }.awaitFirstOrNull() + if (ts != null) { + evaluate = !((limit.monthlyCount != null && ts.cnt!! >= limit.monthlyCount) + || (limit.monthlyTotal != null && ts.total!! >= limit.monthlyTotal)) + } + } + } + } + return evaluate + } + + + override suspend fun isWithdrawAllowed(owner: WalletOwner, amount: Amount): Boolean { + var evaluate: Boolean? = limitsRepository.findByOwnerAndAction( + owner.id()!!, + "withdraw" + ) + .map { limit -> + evaluateLimit(limit, owner, false) + }.onEmpty { + emit(true) + }.reduce { a, b -> + a && b + } + if (evaluate == null) { + evaluate = limitsRepository.findByLevelAndAction( + owner.level(), + "withdraw" + ) + .map { limit -> + evaluateLimit(limit, owner, false) + }.onEmpty { + emit(true) + }.reduce { a, b -> + a && b + } + } + logger.info("isWithdrawAllowed: {} {}{} {}", owner.uuid(), amount.amount, amount.currency.getName(), evaluate) + return evaluate + } + + override suspend fun findWalletOwner(uuid: String): WalletOwner? { + return walletOwnerRepository.findByUuid(uuid).awaitFirstOrNull() + } + + override suspend fun createWalletOwner(uuid: String, title: String, userLevel: String): WalletOwner { + return walletOwnerRepository.save(WalletOwnerModel(null, uuid, title, userLevel)).awaitFirst() + } +} \ No newline at end of file diff --git a/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/model/CurrencyModel.kt b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/model/CurrencyModel.kt new file mode 100644 index 000000000..4786dba55 --- /dev/null +++ b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/model/CurrencyModel.kt @@ -0,0 +1,27 @@ +package co.nilin.opex.port.wallet.postgres.model + + +import co.nilin.opex.wallet.core.model.Currency +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Column +import org.springframework.data.relational.core.mapping.Table + +@Table("currency") +class CurrencyModel( + @Id @Column("name") val name_: String, + @Column("symbol") val symbol_: String, + @Column("precision") val precision_: Int +): Currency { + override fun getSymbol(): String { + return symbol_ + } + + override fun getName(): String { + return name_ + } + + override fun getPrecision(): Int { + return precision_ + } +} + diff --git a/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/model/CurrencyRateModel.kt b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/model/CurrencyRateModel.kt new file mode 100644 index 000000000..bcaa7f5ef --- /dev/null +++ b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/model/CurrencyRateModel.kt @@ -0,0 +1,14 @@ +package co.nilin.opex.port.wallet.postgres.model + + +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Column +import org.springframework.data.relational.core.mapping.Table + +@Table("currency_rate") +class CurrencyRateModel( + @Id val id: Long?, + @Column("source_currency") val sourceCurrency: String, + @Column("dest_currency") val destCurrency: String, + @Column("rate") val rate: Double +) \ No newline at end of file diff --git a/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/model/TransactionModel.kt b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/model/TransactionModel.kt new file mode 100644 index 000000000..cc8d75c9c --- /dev/null +++ b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/model/TransactionModel.kt @@ -0,0 +1,19 @@ +package co.nilin.opex.port.wallet.postgres.model + +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Column +import org.springframework.data.relational.core.mapping.Table +import java.math.BigDecimal +import java.time.LocalDateTime + +@Table("transaction") +class TransactionModel( + @Id var id: Long?, + @Column("source_wallet") val sourceWallet: Long, + @Column("dest_wallet") val destWallet: Long, + @Column("source_amount") val sourceAmount: BigDecimal, + @Column("dest_amount") val destAmount: BigDecimal, + val description: String?, + @Column("transfer_ref") val transferRef: String?, + @Column("transaction_date") val txDate: LocalDateTime +) \ No newline at end of file diff --git a/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/model/UserLimitsModel.kt b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/model/UserLimitsModel.kt new file mode 100644 index 000000000..b37cb8169 --- /dev/null +++ b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/model/UserLimitsModel.kt @@ -0,0 +1,19 @@ +package co.nilin.opex.port.wallet.postgres.model + +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Column +import org.springframework.data.relational.core.mapping.Table +import java.math.BigDecimal + +@Table("user_limits") +class UserLimitsModel( + @Id val id: Long?, + val level: String?, + val owner: Long?, + val action: String, //withdraw or deposit + val walletType: String, + @Column("daily_total") val dailyTotal: BigDecimal?, + @Column("daily_count") val dailyCount: Int?, + @Column("monthly_total") val monthlyTotal: BigDecimal?, + @Column("monthly_count") val monthlyCount: Int? +) \ No newline at end of file diff --git a/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/model/WalletConfigModel.kt b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/model/WalletConfigModel.kt new file mode 100644 index 000000000..0652137b0 --- /dev/null +++ b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/model/WalletConfigModel.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.port.wallet.postgres.model + +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Column +import org.springframework.data.relational.core.mapping.Table + +@Table("wallet_config") +class WalletConfigModel(@Id val name: String, @Column("main_currency") val mainCurrency: String) \ No newline at end of file diff --git a/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/model/WalletLimitsModel.kt b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/model/WalletLimitsModel.kt new file mode 100644 index 000000000..4f5f59ab3 --- /dev/null +++ b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/model/WalletLimitsModel.kt @@ -0,0 +1,21 @@ +package co.nilin.opex.port.wallet.postgres.model + +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Column +import org.springframework.data.relational.core.mapping.Table +import java.math.BigDecimal + +@Table("wallet_limits") +class WalletLimitsModel( + @Id val id: Long?, + val level: String?, + val owner: Long?, + val action: String, //withdraw or deposit + val currency: String, + @Column("wallet_type") val walletType: String, + @Column("wallet_id") val walletId: Long?, + @Column("daily_total") val dailyTotal: BigDecimal?, + @Column("daily_count") val dailyCount: Int?, + @Column("monthly_total") val monthlyTotal: BigDecimal?, + @Column("monthly_count") val monthlyCount: Int? +) \ No newline at end of file diff --git a/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/model/WalletModel.kt b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/model/WalletModel.kt new file mode 100644 index 000000000..8c60c2f02 --- /dev/null +++ b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/model/WalletModel.kt @@ -0,0 +1,15 @@ +package co.nilin.opex.port.wallet.postgres.model + +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Column +import org.springframework.data.relational.core.mapping.Table +import java.math.BigDecimal + +@Table("wallet") +data class WalletModel( + @Id @Column("id") val id: Long?, + @Column("owner") val owner: Long, + @Column("wallet_type") val type: String, + @Column("currency") val currency: String, + @Column("balance") val balance: BigDecimal +) \ No newline at end of file diff --git a/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/model/WalletOwnerModel.kt b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/model/WalletOwnerModel.kt new file mode 100644 index 000000000..2c103ab7d --- /dev/null +++ b/Wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/port/wallet/postgres/model/WalletOwnerModel.kt @@ -0,0 +1,46 @@ +package co.nilin.opex.port.wallet.postgres.model + +import co.nilin.opex.wallet.core.model.WalletOwner +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Column +import org.springframework.data.relational.core.mapping.Table + +@Table("wallet_owner") +class WalletOwnerModel( + @Id @Column("id") var id_: Long?, + @Column("uuid") var uuid_: String, + @Column("title") var title_: String, + @Column("level") var level_: String, + @Column("trade_allowed") var isTradeAllowed_: Boolean = true, + @Column("withdraw_allowed") var isWithdrawAllowed_: Boolean = true, + @Column("deposit_allowed") var isDepositAllowed_: Boolean = true, +) : WalletOwner { + + override fun id(): Long? { + return id_ + } + + override fun uuid(): String { + return uuid_ + } + + override fun title(): String { + return title_ + } + + override fun level(): String { + return level_ + } + + override fun isTradeAllowed(): Boolean { + return isTradeAllowed_ + } + + override fun isWithdrawAllowed(): Boolean { + return isWithdrawAllowed_ + } + + override fun isDepositAllowed(): Boolean { + return isDepositAllowed_ + } +} \ No newline at end of file