├── .gitignore ├── build.gradle ├── deployment └── docker-compose.yml ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src ├── main ├── kotlin │ └── org │ │ └── jasoet │ │ └── vertx │ │ ├── VertxApplication.kt │ │ ├── config │ │ ├── PersistenceConfig.kt │ │ ├── RestTemplateConfig.kt │ │ ├── VertxConfig.kt │ │ └── properties │ │ │ └── AppProperties.kt │ │ ├── controller │ │ ├── Controller.kt │ │ └── MainController.kt │ │ ├── extension │ │ ├── Config.kt │ │ ├── DateTime.kt │ │ ├── Future.kt │ │ ├── Lang.kt │ │ ├── Mapper.kt │ │ ├── Resource.kt │ │ ├── Rx.kt │ │ ├── SnowFlake.kt │ │ ├── Spring.kt │ │ ├── Web.kt │ │ └── codec │ │ │ ├── Base32.kt │ │ │ ├── Base64.kt │ │ │ └── Digest.kt │ │ └── verticle │ │ └── MainVerticle.kt └── resources │ └── application.yaml └── test └── kotlin └── org └── jasoet └── vertx └── controller └── MainControllerTest.kt /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | 5 | ### STS ### 6 | .apt_generated 7 | .classpath 8 | .factorypath 9 | .project 10 | .settings 11 | .springBeans 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | nbproject/private/ 21 | build/ 22 | nbbuild/ 23 | dist/ 24 | nbdist/ 25 | .nb-gradle/ 26 | .vertx/ -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | kotlinVersion = '1.1.2' 4 | springBootVersion = '1.5.2.RELEASE' 5 | vertxVersion = '3.4.1' 6 | logbackVersion = '1.1.7' 7 | commonsLangVersion = '3.4' 8 | commonsCodecVersion = '1.10' 9 | jacksonVersion = '2.9.0.pr3' 10 | hibernateValidatorVersion = '5.3.3.Final' 11 | pebbleVersion = '2.3.0' 12 | jBCryptVersion = '0.4.1' 13 | jUnitVersion = '4.12' 14 | logstashLogbackEncoderVersion = '4.7' 15 | papertrailLogbackVersion = '1.0.0' 16 | } 17 | repositories { 18 | mavenCentral() 19 | } 20 | dependencies { 21 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") 22 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}") 23 | classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlinVersion}") 24 | } 25 | } 26 | 27 | apply plugin: 'kotlin' 28 | apply plugin: 'kotlin-spring' 29 | apply plugin: 'idea' 30 | apply plugin: 'application' 31 | apply plugin: 'org.springframework.boot' 32 | 33 | version = '1.0-SNAPSHOT' 34 | sourceCompatibility = 1.8 35 | 36 | group 'org.jasoet.vertx' 37 | 38 | mainClassName = 'org.jasoet.vertx.VertxApplication' 39 | 40 | compileKotlin { 41 | sourceCompatibility = JavaVersion.VERSION_1_8 42 | targetCompatibility = JavaVersion.VERSION_1_8 43 | 44 | kotlinOptions { 45 | jvmTarget = "1.8" 46 | apiVersion = "1.1" 47 | languageVersion = "1.1" 48 | } 49 | } 50 | 51 | compileTestKotlin { 52 | sourceCompatibility = JavaVersion.VERSION_1_8 53 | targetCompatibility = JavaVersion.VERSION_1_8 54 | 55 | kotlinOptions { 56 | jvmTarget = "1.8" 57 | apiVersion = "1.1" 58 | languageVersion = "1.1" 59 | } 60 | } 61 | 62 | repositories { 63 | mavenCentral() 64 | } 65 | 66 | 67 | dependencies { 68 | compile "io.vertx:vertx-core:$vertxVersion" 69 | compile "io.vertx:vertx-rx-java:$vertxVersion" 70 | compile "io.vertx:vertx-web:$vertxVersion" 71 | compile "io.vertx:vertx-web-templ-pebble:$vertxVersion" 72 | compile "io.vertx:vertx-config:$vertxVersion" 73 | compile "io.vertx:vertx-lang-kotlin:$vertxVersion" 74 | compile "io.vertx:vertx-hazelcast:$vertxVersion" 75 | compile "io.vertx:vertx-auth-common:$vertxVersion" 76 | compile "com.mitchellbosecke:pebble:${pebbleVersion}" 77 | 78 | compile "com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion" 79 | compile "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jacksonVersion" 80 | compile "com.fasterxml.jackson.module:jackson-module-parameter-names:$jacksonVersion" 81 | 82 | compile "org.apache.commons:commons-lang3:${commonsLangVersion}" 83 | compile "commons-codec:commons-codec:${commonsCodecVersion}" 84 | 85 | compile('org.springframework.boot:spring-boot-starter-data-jpa') 86 | compile('org.springframework.boot:spring-boot-starter-jdbc') 87 | compile('org.springframework:spring-web') 88 | compile("org.apache.httpcomponents:httpclient") 89 | 90 | compile("org.jetbrains.kotlin:kotlin-stdlib-jre8:${kotlinVersion}") 91 | compile "org.jetbrains.kotlin:kotlin-runtime:$kotlinVersion" 92 | compile "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion" 93 | compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" 94 | 95 | runtime('mysql:mysql-connector-java') 96 | testCompile('org.springframework.boot:spring-boot-starter-test') 97 | testCompile "io.vertx:vertx-unit:$vertxVersion" 98 | testCompile "org.jetbrains.kotlin:kotlin-test:$kotlinVersion" 99 | testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlinVersion" 100 | } 101 | 102 | task wrapper(type: Wrapper) { 103 | gradleVersion = '3.5' 104 | } 105 | -------------------------------------------------------------------------------- /deployment/docker-compose.yml: -------------------------------------------------------------------------------- 1 | db: 2 | image: mysql:5.7 3 | container_name: mysql_jasoet 4 | ports: 5 | - "3306:3306" 6 | environment: 7 | - MYSQL_ROOT_PASSWORD=localhost 8 | - MYSQL_USER=jasoet 9 | - MYSQL_PASSWORD=localhost 10 | - MYSQL_DATABASE=vertx 11 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KotlinID/Kotlin-Vertx-Spring-Example/e4645cc36e08b0c2802875691d71574da4b24a6f/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Apr 18 15:06:19 WIB 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save ( ) { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /src/main/kotlin/org/jasoet/vertx/VertxApplication.kt: -------------------------------------------------------------------------------- 1 | package org.jasoet.vertx 2 | 3 | import io.vertx.core.DeploymentOptions 4 | import io.vertx.core.Vertx 5 | import io.vertx.core.json.JsonObject 6 | import org.jasoet.vertx.extension.getBean 7 | import org.jasoet.vertx.extension.logger 8 | import org.jasoet.vertx.extension.springBootStart 9 | import org.jasoet.vertx.verticle.MainVerticle 10 | 11 | @org.springframework.boot.autoconfigure.SpringBootApplication 12 | class VertxApplication { 13 | companion object { 14 | val log = logger(VertxApplication::class) 15 | @JvmStatic 16 | fun main(args: Array) { 17 | log.info("Initialize Spring Application Context!") 18 | val applicationContext = springBootStart(*args) 19 | 20 | log.info("Retrieve Vertx and MainVerticle") 21 | val vertx = applicationContext.getBean() 22 | val mainVerticle = applicationContext.getBean() 23 | val config = applicationContext.getBean() 24 | 25 | log.info("Deploying Main Verticle") 26 | vertx.deployVerticle(mainVerticle, DeploymentOptions().apply { 27 | this.config = config 28 | }) 29 | } 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/main/kotlin/org/jasoet/vertx/config/PersistenceConfig.kt: -------------------------------------------------------------------------------- 1 | package org.jasoet.vertx.config 2 | 3 | import org.springframework.boot.autoconfigure.domain.EntityScan 4 | import org.springframework.context.annotation.Configuration 5 | import org.springframework.data.jpa.repository.config.EnableJpaAuditing 6 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories 7 | 8 | /** 9 | * [Documentation Here] 10 | * 11 | * @author Deny Prasetyo. 12 | */ 13 | 14 | @Configuration 15 | @EnableJpaRepositories() 16 | @EntityScan( 17 | basePackages = arrayOf( 18 | "org.jasoet.vertx.db" 19 | ) 20 | ) 21 | @EnableJpaAuditing 22 | class PersistenceConfig -------------------------------------------------------------------------------- /src/main/kotlin/org/jasoet/vertx/config/RestTemplateConfig.kt: -------------------------------------------------------------------------------- 1 | package org.jasoet.vertx.config 2 | 3 | import org.apache.http.client.HttpClient 4 | import org.apache.http.client.config.RequestConfig 5 | import org.apache.http.impl.client.HttpClientBuilder 6 | import org.apache.http.impl.conn.PoolingHttpClientConnectionManager 7 | import org.springframework.context.annotation.Bean 8 | import org.springframework.context.annotation.Configuration 9 | import org.springframework.http.client.ClientHttpRequestFactory 10 | import org.springframework.http.client.HttpComponentsClientHttpRequestFactory 11 | import org.springframework.web.client.RestTemplate 12 | 13 | /** 14 | * Bean definition of RestTemplate with PoolingHttpConnection 15 | * 16 | * @author Deny Prasetyo. 17 | */ 18 | 19 | @Configuration 20 | class RestTemplateConfig { 21 | companion object { 22 | fun defaultRestTemplate(): RestTemplate { 23 | return RestTemplateConfig().restTemplate() 24 | } 25 | } 26 | 27 | private val DEFAULT_MAX_TOTAL_CONNECTIONS = 100 28 | 29 | private val DEFAULT_MAX_CONNECTIONS_PER_ROUTE = 5 30 | 31 | private val DEFAULT_READ_TIMEOUT_MILLISECONDS = 60 * 1000 32 | 33 | @Bean 34 | fun httpClient(): HttpClient { 35 | val connectionManager = PoolingHttpClientConnectionManager() 36 | 37 | connectionManager.maxTotal = DEFAULT_MAX_TOTAL_CONNECTIONS 38 | connectionManager.defaultMaxPerRoute = DEFAULT_MAX_CONNECTIONS_PER_ROUTE 39 | val requestConfig = RequestConfig.custom().setConnectTimeout(DEFAULT_READ_TIMEOUT_MILLISECONDS).build() 40 | return HttpClientBuilder 41 | .create() 42 | .setConnectionManager(connectionManager) 43 | .setDefaultRequestConfig(requestConfig) 44 | .build() 45 | } 46 | 47 | @Bean 48 | fun httpRequestFactory(): ClientHttpRequestFactory { 49 | return HttpComponentsClientHttpRequestFactory(httpClient()) 50 | } 51 | 52 | @Bean 53 | fun restTemplate(): RestTemplate { 54 | return RestTemplate(httpRequestFactory()) 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/jasoet/vertx/config/VertxConfig.kt: -------------------------------------------------------------------------------- 1 | package org.jasoet.vertx.config 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper 4 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule 5 | import com.fasterxml.jackson.module.kotlin.registerKotlinModule 6 | import com.fasterxml.jackson.module.paramnames.ParameterNamesModule 7 | import com.hazelcast.config.Config 8 | import io.vertx.core.Vertx 9 | import io.vertx.core.VertxOptions 10 | import io.vertx.core.eventbus.EventBus 11 | import io.vertx.core.file.FileSystem 12 | import io.vertx.core.json.Json 13 | import io.vertx.core.json.JsonObject 14 | import io.vertx.core.spi.cluster.ClusterManager 15 | import io.vertx.ext.web.Router 16 | import io.vertx.ext.web.templ.PebbleTemplateEngine 17 | import io.vertx.ext.web.templ.TemplateEngine 18 | import io.vertx.kotlin.core.json.json 19 | import io.vertx.kotlin.core.json.obj 20 | import io.vertx.spi.cluster.hazelcast.HazelcastClusterManager 21 | import org.jasoet.vertx.config.properties.AppProperties 22 | import org.jasoet.vertx.extension.blockingSingle 23 | import org.jasoet.vertx.extension.env 24 | import org.jasoet.vertx.extension.logger 25 | import org.springframework.beans.factory.annotation.Autowired 26 | import org.springframework.boot.context.properties.EnableConfigurationProperties 27 | import org.springframework.context.annotation.Bean 28 | import org.springframework.context.annotation.Configuration 29 | import java.net.InetAddress 30 | 31 | /** 32 | * [Documentation Here] 33 | * 34 | * @author Deny Prasetyo. 35 | */ 36 | 37 | @Configuration 38 | @EnableConfigurationProperties(value = *arrayOf(AppProperties::class)) 39 | class VertxConfig() { 40 | 41 | init { 42 | Json.mapper.apply { 43 | registerKotlinModule() 44 | registerModule(ParameterNamesModule()) 45 | registerModule(JavaTimeModule()) 46 | } 47 | 48 | Json.prettyMapper.apply { 49 | registerKotlinModule() 50 | registerModule(ParameterNamesModule()) 51 | registerModule(JavaTimeModule()) 52 | } 53 | } 54 | 55 | private val log = logger(VertxConfig::class) 56 | @Autowired 57 | lateinit var appProperties: AppProperties 58 | 59 | 60 | @Bean 61 | fun hazelClusterManager(): ClusterManager { 62 | val hazelcastConfig = Config() 63 | return HazelcastClusterManager(hazelcastConfig) 64 | } 65 | 66 | @Bean 67 | fun vertx(): Vertx { 68 | val vertxOption = VertxOptions().apply { 69 | this.clusterManager = hazelClusterManager() 70 | try { 71 | val address = InetAddress.getByName(env("HOSTNAME", "localhost")).hostAddress 72 | this.clusterHost = address 73 | log.info("Cluster set to use clusterHost ${this.clusterHost}") 74 | } catch (e: Exception) { 75 | log.info("Hostname not Found, perhaps you run this app locally!") 76 | } 77 | } 78 | 79 | return blockingSingle { Vertx.clusteredVertx(vertxOption, it) }.value() 80 | } 81 | 82 | @Bean 83 | fun config(): JsonObject { 84 | return json { 85 | obj("HTTP_PORT" to appProperties.httpPort) 86 | } 87 | } 88 | 89 | @Bean 90 | fun router(): Router { 91 | return Router.router(vertx()) 92 | } 93 | 94 | @Bean 95 | fun eventBus(): EventBus { 96 | return vertx().eventBus() 97 | } 98 | 99 | @Bean 100 | fun objectMapper(): ObjectMapper { 101 | return Json.mapper 102 | } 103 | 104 | @Bean 105 | fun fileSystem(): FileSystem { 106 | return vertx().fileSystem() 107 | } 108 | 109 | @Bean 110 | fun pebbleTemplate(): TemplateEngine { 111 | return PebbleTemplateEngine.create(vertx()) 112 | } 113 | 114 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/jasoet/vertx/config/properties/AppProperties.kt: -------------------------------------------------------------------------------- 1 | package org.jasoet.vertx.config.properties 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties 4 | 5 | /** 6 | * [Documentation Here] 7 | * 8 | * @author Deny Prasetyo. 9 | */ 10 | 11 | @ConfigurationProperties(prefix = "app") 12 | data class AppProperties( 13 | var imageLocation: String = "", 14 | var baseUrl: String = "", 15 | var httpPort: Int = 8080 16 | ) 17 | 18 | -------------------------------------------------------------------------------- /src/main/kotlin/org/jasoet/vertx/controller/Controller.kt: -------------------------------------------------------------------------------- 1 | package org.jasoet.vertx.controller 2 | 3 | import io.vertx.ext.web.Router 4 | 5 | /** 6 | * [Documentation Here] 7 | * 8 | * @author Deny Prasetyo. 9 | */ 10 | 11 | 12 | abstract class Controller(val handlers: Router.() -> Unit) { 13 | abstract val router: Router 14 | fun create(): Router { 15 | return router.apply { 16 | handlers() 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/jasoet/vertx/controller/MainController.kt: -------------------------------------------------------------------------------- 1 | package org.jasoet.vertx.controller 2 | 3 | import io.netty.handler.codec.http.HttpResponseStatus 4 | import io.vertx.core.http.HttpMethod 5 | import io.vertx.ext.web.Router 6 | import org.jasoet.vertx.config.properties.AppProperties 7 | import org.jasoet.vertx.extension.DataInconsistentException 8 | import org.jasoet.vertx.extension.NotAllowedException 9 | import org.jasoet.vertx.extension.NullObjectException 10 | import org.jasoet.vertx.extension.OK 11 | import org.jasoet.vertx.extension.RegistrationException 12 | import org.jasoet.vertx.extension.endWithJson 13 | import org.jasoet.vertx.extension.first 14 | import org.jasoet.vertx.extension.header 15 | import org.jasoet.vertx.extension.logger 16 | import org.jasoet.vertx.extension.serveStatic 17 | import org.springframework.beans.factory.annotation.Autowired 18 | import org.springframework.stereotype.Component 19 | import java.io.FileNotFoundException 20 | 21 | /** 22 | * [Documentation Here] 23 | * 24 | * @author Deny Prasetyo. 25 | */ 26 | 27 | @Component 28 | class MainController @Autowired constructor(override val router: Router, 29 | val appProperties: AppProperties) : Controller({ 30 | 31 | val log = logger(MainController::class) 32 | 33 | route("/static/*").serveStatic() 34 | route().last().handler { it.fail(404) } 35 | 36 | get("/").handler { context -> 37 | context.OK(message = "Hello World!") 38 | } 39 | 40 | route().first().failureHandler { errorContext -> 41 | val e: Throwable? = errorContext.failure() 42 | if (e != null) { 43 | log.error(e.message, e) 44 | } 45 | val code = when (e) { 46 | is FileNotFoundException -> HttpResponseStatus.NOT_FOUND.code() 47 | is NullObjectException -> HttpResponseStatus.NOT_FOUND.code() 48 | is DataInconsistentException -> HttpResponseStatus.INTERNAL_SERVER_ERROR.code() 49 | is NotAllowedException -> HttpResponseStatus.METHOD_NOT_ALLOWED.code() 50 | is SecurityException -> HttpResponseStatus.UNAUTHORIZED.code() 51 | is RegistrationException -> HttpResponseStatus.BAD_REQUEST.code() 52 | else -> 53 | if (errorContext.statusCode() > 0) { 54 | errorContext.statusCode() 55 | } else { 56 | 500 57 | } 58 | } 59 | 60 | val acceptHeader = errorContext.header("Accept") ?: "" 61 | val contentTypeHeader = errorContext.header("Content-Type") ?: "" 62 | if (acceptHeader.matches(".*/json$".toRegex()) || contentTypeHeader.matches(".*/json$".toRegex())) { 63 | val result = mapOf( 64 | "success" to false, 65 | "message" to errorContext.failure().message 66 | ) 67 | errorContext.response().setStatusCode(code).endWithJson(result) 68 | } else { 69 | errorContext 70 | .reroute(HttpMethod.GET, "/static/html/$code.html") 71 | } 72 | } 73 | }) -------------------------------------------------------------------------------- /src/main/kotlin/org/jasoet/vertx/extension/Config.kt: -------------------------------------------------------------------------------- 1 | package org.jasoet.vertx.extension 2 | 3 | import io.vertx.config.ConfigRetriever 4 | import io.vertx.config.ConfigStoreOptions 5 | import io.vertx.core.Vertx 6 | import io.vertx.core.json.JsonObject 7 | import io.vertx.kotlin.config.ConfigRetrieverOptions 8 | import io.vertx.kotlin.config.ConfigStoreOptions 9 | import io.vertx.kotlin.core.json.json 10 | import io.vertx.kotlin.core.json.obj 11 | import rx.Observable 12 | 13 | /** 14 | * [Documentation Here] 15 | * 16 | * @author Deny Prasetyo 17 | */ 18 | fun propertiesConfig(path: String): ConfigStoreOptions { 19 | return ConfigStoreOptions( 20 | type = "file", 21 | format = "properties", 22 | config = json { 23 | obj("path" to path) 24 | } 25 | ) 26 | } 27 | 28 | fun jsonConfig(path: String): ConfigStoreOptions { 29 | return ConfigStoreOptions( 30 | type = "file", 31 | format = "json", 32 | config = json { 33 | obj("path" to path) 34 | } 35 | ) 36 | } 37 | 38 | fun envConfig(): ConfigStoreOptions { 39 | return ConfigStoreOptions( 40 | type = "env") 41 | } 42 | 43 | fun Vertx.retrieveConfig(vararg stores: ConfigStoreOptions): Observable { 44 | val options = ConfigRetrieverOptions( 45 | stores = stores.toList().plus(envConfig()) 46 | ) 47 | val retriever = ConfigRetriever.create(this, options) 48 | return observable { retriever.getConfig(it) } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /src/main/kotlin/org/jasoet/vertx/extension/DateTime.kt: -------------------------------------------------------------------------------- 1 | package org.jasoet.vertx.extension 2 | 3 | import java.time.Instant 4 | import java.time.LocalDate 5 | import java.time.LocalDateTime 6 | import java.time.Year 7 | import java.time.ZoneId 8 | import java.time.format.DateTimeFormatter 9 | import java.time.format.DateTimeParseException 10 | 11 | 12 | /** 13 | * [Documentation Here] 14 | * 15 | * @author Deny Prasetyo. 16 | */ 17 | 18 | fun Int.toYear(): Year { 19 | return Year.of(this) 20 | } 21 | 22 | fun LocalDateTime?.isoDateFormat(): String? { 23 | return this?.format(DateTimeFormatter.ISO_DATE) 24 | } 25 | 26 | fun LocalDateTime?.isoTimeFormat(): String? { 27 | return this?.format(DateTimeFormatter.ISO_TIME) 28 | } 29 | 30 | fun LocalDateTime?.isoDateTimeFormat(): String? { 31 | return this?.format(DateTimeFormatter.ISO_DATE_TIME) 32 | } 33 | 34 | fun Long.toLocalDateTime(): LocalDateTime { 35 | return LocalDateTime.ofInstant(Instant.ofEpochMilli(this), ZoneId.systemDefault()) 36 | } 37 | 38 | fun LocalDateTime.toMilliSeconds(): Long { 39 | return this.atZone(java.time.ZoneId.systemDefault()).toInstant().toEpochMilli() 40 | } 41 | 42 | @Throws(DateTimeParseException::class) 43 | fun String.toLocalDate(pattern: String): LocalDate { 44 | return LocalDate.parse(this, DateTimeFormatter.ofPattern(pattern)) 45 | } 46 | -------------------------------------------------------------------------------- /src/main/kotlin/org/jasoet/vertx/extension/Future.kt: -------------------------------------------------------------------------------- 1 | package org.jasoet.vertx.extension 2 | 3 | import io.vertx.core.AsyncResult 4 | import io.vertx.core.CompositeFuture 5 | import io.vertx.core.Future 6 | import io.vertx.core.Handler 7 | import io.vertx.core.Vertx 8 | 9 | /** 10 | * [Documentation Here] 11 | * 12 | * @author Deny Prasetyo. 13 | */ 14 | 15 | inline fun Vertx.async(crossinline operation: () -> T): Future { 16 | val future = Future.future() 17 | this.executeBlocking(io.vertx.core.Handler { f -> 18 | try { 19 | f.complete(operation()) 20 | } catch (e: Exception) { 21 | f.fail(e) 22 | } 23 | }, future.completer()) 24 | return future 25 | } 26 | 27 | inline fun futureTask(operation: (Handler>) -> Unit): Future { 28 | val future = Future.future() 29 | operation(future.completer()) 30 | return future 31 | } 32 | 33 | @Suppress("NOTHING_TO_INLINE") 34 | inline fun all(vararg futures: Future<*>): CompositeFuture { 35 | return CompositeFuture.all(futures.asList()) 36 | } 37 | 38 | @Suppress("NOTHING_TO_INLINE") 39 | inline fun any(vararg futures: Future<*>): CompositeFuture { 40 | return CompositeFuture.any(futures.asList()) 41 | } 42 | 43 | infix fun Future.handle(operation: (AsyncResult) -> Unit): Future { 44 | return this.setHandler(operation) 45 | } 46 | 47 | fun Future.handle(success: (T) -> Unit, failed: (Throwable) -> Unit): Future { 48 | return this.setHandler { 49 | if (it.succeeded()) { 50 | success(it.result()) 51 | } else { 52 | failed(it.cause()) 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/jasoet/vertx/extension/Lang.kt: -------------------------------------------------------------------------------- 1 | package org.jasoet.vertx.extension 2 | 3 | import io.vertx.core.logging.Logger 4 | import io.vertx.core.logging.LoggerFactory 5 | import kotlin.reflect.KClass 6 | 7 | 8 | /** 9 | * [Documentation Here] 10 | * 11 | * @author Deny Prasetyo. 12 | */ 13 | 14 | fun logger(clz: KClass<*>): Logger { 15 | return LoggerFactory.getLogger(clz.qualifiedName) 16 | } 17 | 18 | @Suppress("NOTHING_TO_INLINE") 19 | inline infix fun T?.orNotFound(message: String): T { 20 | return if (this == null) { 21 | throw NullObjectException(message) 22 | } else { 23 | this 24 | } 25 | } 26 | 27 | @Suppress("NOTHING_TO_INLINE") 28 | inline infix fun T?.orDataError(message: String): T { 29 | return if (this == null) { 30 | throw DataInconsistentException(message) 31 | } else { 32 | this 33 | } 34 | } 35 | 36 | class NullObjectException : RuntimeException { 37 | constructor(message: String, ex: Exception?) : super(message, ex) 38 | 39 | constructor(message: String) : super(message) 40 | 41 | constructor(ex: Exception) : super(ex) 42 | } 43 | 44 | class DataInconsistentException : RuntimeException { 45 | constructor(message: String, ex: Exception?) : super(message, ex) 46 | 47 | constructor(message: String) : super(message) 48 | 49 | constructor(ex: Exception) : super(ex) 50 | } 51 | 52 | class NotAllowedException : RuntimeException { 53 | constructor(message: String, ex: Exception?) : super(message, ex) 54 | 55 | constructor(message: String) : super(message) 56 | 57 | constructor(ex: Exception) : super(ex) 58 | } 59 | 60 | class RegistrationException : RuntimeException { 61 | constructor(message: String, ex: Exception?) : super(message, ex) 62 | 63 | constructor(message: String) : super(message) 64 | 65 | constructor(ex: Exception) : super(ex) 66 | } 67 | 68 | 69 | -------------------------------------------------------------------------------- /src/main/kotlin/org/jasoet/vertx/extension/Mapper.kt: -------------------------------------------------------------------------------- 1 | package org.jasoet.vertx.extension 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper 4 | import io.vertx.core.json.JsonObject 5 | 6 | /** 7 | * [Documentation Here] 8 | * 9 | * @author Deny Prasetyo. 10 | */ 11 | 12 | 13 | inline fun ObjectMapper.toValue(jsonString: String): T { 14 | return this.convertValue(jsonString, T::class.java) 15 | } 16 | 17 | inline fun ObjectMapper.toValue(json: JsonObject): T { 18 | return this.convertValue(json.map, T::class.java) 19 | } 20 | 21 | inline fun ObjectMapper.toJson(obj: T): JsonObject { 22 | val jsonString = this.writeValueAsString(obj) 23 | return JsonObject(jsonString) 24 | } 25 | -------------------------------------------------------------------------------- /src/main/kotlin/org/jasoet/vertx/extension/Resource.kt: -------------------------------------------------------------------------------- 1 | package org.jasoet.vertx.extension 2 | 3 | import io.vertx.core.buffer.Buffer 4 | import io.vertx.core.json.JsonObject 5 | import java.io.InputStreamReader 6 | import java.lang.IllegalArgumentException 7 | import java.nio.charset.StandardCharsets 8 | import kotlin.reflect.full.isSubclassOf 9 | 10 | /** 11 | * [Documentation Here] 12 | * 13 | * @author Deny Prasetyo. 14 | */ 15 | 16 | inline fun env(key: String, defaultValue: T? = null): T { 17 | val value: String? = System.getenv(key) 18 | return if (value != null) { 19 | if (T::class.isSubclassOf(String::class)) { 20 | value as T 21 | } else if (T::class.isSubclassOf(Int::class)) { 22 | value.toInt() as T 23 | } else if (T::class.isSubclassOf(Double::class)) { 24 | value.toDouble() as T 25 | } else if (T::class.isSubclassOf(Boolean::class)) { 26 | value.toBoolean() as T 27 | } else { 28 | throw IllegalArgumentException("${T::class.qualifiedName} Not Supported") 29 | } 30 | } else defaultValue ?: throw IllegalArgumentException("Illegal: $key not found and default value is null!") 31 | } 32 | 33 | inline fun applyEnv(key: String, defaultValue: T? = null, operation: (T) -> Unit) { 34 | val logger = logger(System::class) 35 | try { 36 | val value = env(key, defaultValue) 37 | operation(value) 38 | } catch (e: Exception) { 39 | logger.info("Exception occurred ${e.message}, Operation ignored!") 40 | } 41 | } 42 | 43 | fun String.resourceToBuffer(): Buffer { 44 | val inputStream = javaClass.getResourceAsStream(this) 45 | val byteArray = ByteArray(inputStream.available()) 46 | 47 | inputStream.use { 48 | it.read(byteArray) 49 | } 50 | return Buffer.buffer(byteArray) 51 | } 52 | 53 | fun String.toJsonObject(): JsonObject { 54 | val logger = logger(JsonObject::class) 55 | return try { 56 | val inputStream = InputStreamReader(javaClass.getResourceAsStream(this), StandardCharsets.UTF_8) 57 | val jsonString = inputStream.useLines { it.joinToString("") } 58 | logger.info("Load config from $this") 59 | JsonObject(jsonString) 60 | } catch (e: Exception) { 61 | logger.info("Config Cannot Loaded, Return Empty JsonObject. Cause: ${e.message}") 62 | JsonObject() 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/kotlin/org/jasoet/vertx/extension/Rx.kt: -------------------------------------------------------------------------------- 1 | package org.jasoet.vertx.extension 2 | 3 | import io.vertx.core.AsyncResult 4 | import io.vertx.core.Handler 5 | import io.vertx.rx.java.ObservableFuture 6 | import io.vertx.rx.java.SingleOnSubscribeAdapter 7 | import rx.Observable 8 | import rx.Single 9 | import rx.singles.BlockingSingle 10 | 11 | /** 12 | * [Documentation Here] 13 | * 14 | * @author Deny Prasetyo. 15 | */ 16 | 17 | inline fun blockingSingle(crossinline operation: (Handler>) -> Unit): BlockingSingle { 18 | return single(operation).toBlocking() 19 | } 20 | 21 | inline fun observable(operation: (Handler>) -> Unit): Observable { 22 | val future = ObservableFuture() 23 | operation(future.toHandler()) 24 | return future 25 | } 26 | 27 | inline fun single(crossinline operation: (Handler>) -> Unit): Single { 28 | return Single.create(SingleOnSubscribeAdapter { fut -> operation(fut) }) 29 | } 30 | 31 | inline fun observableCall(crossinline operation: () -> T): Observable { 32 | return Observable.fromCallable { 33 | operation() 34 | } 35 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/jasoet/vertx/extension/SnowFlake.kt: -------------------------------------------------------------------------------- 1 | package org.jasoet.vertx.extension 2 | 3 | import java.time.LocalDateTime 4 | import java.util.concurrent.atomic.AtomicInteger 5 | 6 | /** 7 | * [Documentation Here] 8 | * 9 | * @author Deny Prasetyo. 10 | */ 11 | 12 | class SnowFlake(val machineId: Int) { 13 | 14 | private val ATOMIC_INCREMENT = AtomicInteger(0) 15 | private val EPOCH = 1420045200000L 16 | private val MAX_MACHINE_ID = 64 17 | private val ALPHA_NUMERIC_BASE = 36 18 | private val TIME_STAMP_SHIFT = 22 19 | private val MACHINE_ID_SHIFT = 16 20 | 21 | init { 22 | if (machineId >= MAX_MACHINE_ID || machineId < 0) { 23 | throw IllegalArgumentException("Machine Number must between 0 - ${MAX_MACHINE_ID - 1}") 24 | } 25 | } 26 | 27 | fun parse(id: Long): SnowFlakeId { 28 | val ts = (id shr TIME_STAMP_SHIFT) + EPOCH 29 | val max = MAX_MACHINE_ID - 1L 30 | val machineId = (id shr MACHINE_ID_SHIFT) and max 31 | val i = id and max 32 | return SnowFlakeId(ts.toLocalDateTime(), machineId.toInt(), i.toInt()) 33 | } 34 | 35 | fun parse(alpha: String): SnowFlakeId { 36 | val id = java.lang.Long.parseLong(alpha.toLowerCase(), ALPHA_NUMERIC_BASE) 37 | return parse(id) 38 | } 39 | 40 | fun nextId(): Long { 41 | synchronized(this) { 42 | val currentTs = System.currentTimeMillis() 43 | val ts = currentTs - EPOCH 44 | val maxIncrement = 16384 45 | val max = maxIncrement - 2 46 | 47 | if (ATOMIC_INCREMENT.get() >= max) { 48 | ATOMIC_INCREMENT.set(0) 49 | } 50 | val i = ATOMIC_INCREMENT.incrementAndGet() 51 | return (ts shl TIME_STAMP_SHIFT) or (this.machineId shl MACHINE_ID_SHIFT).toLong() or i.toLong() 52 | } 53 | } 54 | 55 | fun nextAlpha(): String { 56 | val id = nextId() 57 | return java.lang.Long.toString(id, ALPHA_NUMERIC_BASE) 58 | } 59 | 60 | 61 | } 62 | 63 | data class SnowFlakeId( 64 | val timestamp: LocalDateTime, 65 | val machineId: Int, 66 | val increment: Int 67 | ) -------------------------------------------------------------------------------- /src/main/kotlin/org/jasoet/vertx/extension/Spring.kt: -------------------------------------------------------------------------------- 1 | package org.jasoet.vertx.extension 2 | 3 | import org.springframework.beans.factory.BeanFactory 4 | import org.springframework.boot.SpringApplication 5 | import org.springframework.context.ConfigurableApplicationContext 6 | 7 | /** 8 | * [Documentation Here] 9 | * 10 | * @author Deny Prasetyo 11 | */ 12 | 13 | 14 | inline fun BeanFactory.getBean(): T { 15 | return this.getBean(T::class.java) 16 | } 17 | 18 | inline fun springBootStart(vararg args: String): ConfigurableApplicationContext { 19 | return SpringApplication.run(T::class.java, *args) 20 | } 21 | -------------------------------------------------------------------------------- /src/main/kotlin/org/jasoet/vertx/extension/Web.kt: -------------------------------------------------------------------------------- 1 | package org.jasoet.vertx.extension 2 | 3 | import io.netty.handler.codec.http.HttpResponseStatus 4 | import io.vertx.core.Handler 5 | import io.vertx.core.http.HttpServerResponse 6 | import io.vertx.core.json.Json 7 | import io.vertx.core.json.JsonArray 8 | import io.vertx.core.json.JsonObject 9 | import io.vertx.ext.web.Route 10 | import io.vertx.ext.web.RoutingContext 11 | import io.vertx.ext.web.templ.TemplateEngine 12 | 13 | /** 14 | * [Documentation Here] 15 | * 16 | * @author Deny Prasetyo. 17 | */ 18 | 19 | fun Route.first(): Route { 20 | return this.order(-100) 21 | } 22 | 23 | @Suppress("NOTHING_TO_INLINE") 24 | inline fun Route.render(engine: TemplateEngine, template: String): Route { 25 | return this.handler { context -> 26 | context.render(engine, template) 27 | } 28 | } 29 | 30 | @Suppress("NOTHING_TO_INLINE") 31 | inline fun Route.reroute(destination: String): Route { 32 | return this.handler { context -> 33 | context.reroute(destination) 34 | } 35 | } 36 | 37 | @Suppress("NOTHING_TO_INLINE") 38 | inline fun Route.serveStatic(): Route { 39 | return this.handler(io.vertx.ext.web.handler.StaticHandler.create()) 40 | } 41 | 42 | 43 | @Suppress("NOTHING_TO_INLINE") 44 | inline fun Route.serveStatic(webRoot: String): Route { 45 | return this.handler(io.vertx.ext.web.handler.StaticHandler.create().apply { 46 | setWebRoot(webRoot) 47 | }) 48 | } 49 | 50 | /** 51 | * Extension to the HTTP response to output JSON objects. 52 | */ 53 | fun HttpServerResponse.endWithJson(obj: Any) { 54 | this.putHeader("Content-Type", "application/json; charset=utf-8") 55 | .end(Json.encodePrettily(obj)) 56 | } 57 | 58 | @Suppress("NOTHING_TO_INLINE") 59 | inline fun RoutingContext.header(key: String): String? { 60 | return this.request().headers().get(key) 61 | } 62 | 63 | @Suppress("NOTHING_TO_INLINE") 64 | inline fun RoutingContext.param(key: String): String? { 65 | return this.request().getParam(key) 66 | } 67 | 68 | @Suppress("NOTHING_TO_INLINE") 69 | inline fun RoutingContext.json(obj: Any) { 70 | val response = this.response() 71 | response.putHeader("Content-Type", "application/json; charset=utf-8") 72 | .end(Json.encode(obj)) 73 | } 74 | 75 | @Suppress("NOTHING_TO_INLINE") 76 | inline fun RoutingContext.json(headers: Map = emptyMap(), message: Any) { 77 | this.response().apply { 78 | headers.entries.fold(this) { response, entries -> 79 | response.putHeader(entries.key, entries.value) 80 | } 81 | putHeader("Content-Type", "application/json; charset=utf-8") 82 | end(Json.encode(message)) 83 | } 84 | } 85 | 86 | @Suppress("NOTHING_TO_INLINE") 87 | inline fun RoutingContext.jsonBody(): JsonObject? { 88 | val result: JsonObject? = try { 89 | this.bodyAsJson 90 | } catch (e: Exception) { 91 | null 92 | } 93 | return result 94 | } 95 | 96 | @Suppress("NOTHING_TO_INLINE") 97 | inline fun RoutingContext.jsonArrayBody(): JsonArray? { 98 | val result: JsonArray? = try { 99 | this.bodyAsJsonArray 100 | } catch (e: Exception) { 101 | null 102 | } 103 | return result 104 | } 105 | 106 | @Suppress("NOTHING_TO_INLINE") 107 | inline fun RoutingContext.OK(message: String = "", headers: Map = emptyMap()) { 108 | this.response().let { 109 | it.statusCode = HttpResponseStatus.OK.code() 110 | headers.entries.fold(it) { response, entries -> 111 | response.putHeader(entries.key, entries.value) 112 | } 113 | it.end(message) 114 | } 115 | } 116 | 117 | @Suppress("NOTHING_TO_INLINE") 118 | inline fun RoutingContext.prettyJson(obj: Any) { 119 | val response = this.response() 120 | response.putHeader("Content-Type", "application/json; charset=utf-8") 121 | .end(Json.encodePrettily(obj)) 122 | } 123 | 124 | inline fun handler(crossinline handle: (RoutingContext) -> Unit): Handler { 125 | return Handler { handle(it) } 126 | } 127 | 128 | @Suppress("NOTHING_TO_INLINE") 129 | inline fun RoutingContext.render(engine: TemplateEngine, templateName: String) { 130 | engine.render(this, templateName, { 131 | if (it.succeeded()) { 132 | this.response().end(it.result()) 133 | } else { 134 | this.fail(it.cause()) 135 | } 136 | }) 137 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/jasoet/vertx/extension/codec/Base32.kt: -------------------------------------------------------------------------------- 1 | package org.jasoet.vertx.extension.codec 2 | 3 | import org.apache.commons.codec.binary.Base32 4 | import org.apache.commons.codec.binary.StringUtils 5 | import org.apache.commons.lang3.SerializationUtils 6 | import java.io.Serializable 7 | 8 | 9 | /** 10 | * Documentation 11 | * 12 | * @author Deny Prasetyo. 13 | */ 14 | 15 | private val base32Codec = Base32() 16 | 17 | fun String.base32Encode(): String { 18 | return base32Codec.encodeToString(this.getBytesUtf8()) 19 | } 20 | 21 | fun String.base32EncodeToByteArray(): ByteArray { 22 | return base32Codec.encode(this.getBytesUtf8()) 23 | } 24 | 25 | 26 | fun T.base32Encode(): String { 27 | return base32Codec.encodeToString(this.serializeToByteArray()) 28 | } 29 | 30 | fun T.base32EncodeToByteArray(): ByteArray { 31 | return base32Codec.encode(this.serializeToByteArray()) 32 | } 33 | 34 | fun ByteArray.base32EncodeToString(): String { 35 | return base32Codec.encodeAsString(this) 36 | } 37 | 38 | fun ByteArray.base32Encode(): ByteArray { 39 | return base32Codec.encode(this) 40 | } 41 | 42 | @Throws(DecodeBase32Exception::class) 43 | fun String.base32Decode(): String { 44 | return try { 45 | StringUtils.newStringUtf8(base32Codec.decode(this)) 46 | } catch (e: Exception) { 47 | throw DecodeBase32Exception("Exception when Decode ${this.abbreviate()} ", e) 48 | } 49 | 50 | } 51 | 52 | @Throws(DecodeBase32Exception::class) 53 | fun String.base32DecodeToObject(): T { 54 | return try { 55 | SerializationUtils.deserialize(base32Codec.decode(this)) 56 | } catch (e: Exception) { 57 | throw DecodeBase32Exception("Exception when Decode ${this.abbreviate()} ", e) 58 | } 59 | } 60 | 61 | @Throws(DecodeBase32Exception::class) 62 | fun ByteArray.base32DecodeToObject(): T { 63 | return try { 64 | SerializationUtils.deserialize(base32Codec.decode(this)) 65 | } catch (e: Exception) { 66 | throw DecodeBase32Exception("Exception when Decode ${this.size} bytes ", e) 67 | } 68 | } 69 | 70 | 71 | class DecodeBase32Exception(message: String, cause: Throwable) : Exception(message, cause) -------------------------------------------------------------------------------- /src/main/kotlin/org/jasoet/vertx/extension/codec/Base64.kt: -------------------------------------------------------------------------------- 1 | package org.jasoet.vertx.extension.codec 2 | 3 | import org.apache.commons.codec.binary.Base64 4 | import org.apache.commons.lang3.SerializationUtils 5 | import org.apache.commons.lang3.StringUtils 6 | import java.io.Serializable 7 | import org.apache.commons.codec.binary.StringUtils as CodecStringUtils 8 | 9 | 10 | /** 11 | * Documentation 12 | * 13 | * @author Deny Prasetyo. 14 | */ 15 | 16 | private val base64Codec = Base64() 17 | 18 | fun String.abbreviate(width: Int = 42): String { 19 | return StringUtils.abbreviate(this, width) 20 | } 21 | 22 | fun String.getBytesUtf8(): ByteArray { 23 | return this.getBytesUtf8() 24 | } 25 | 26 | fun T.serializeToByteArray(): ByteArray { 27 | return SerializationUtils.serialize(this) 28 | } 29 | 30 | fun String.base64Encode(): String { 31 | return base64Codec.encodeToString(this.getBytesUtf8()) 32 | } 33 | 34 | fun String.base64EncodeToByteArray(): ByteArray { 35 | return base64Codec.encode(this.getBytesUtf8()) 36 | } 37 | 38 | fun T.base64Encode(): String { 39 | return base64Codec.encodeToString(this.serializeToByteArray()) 40 | } 41 | 42 | fun T.base64EncodeToByteArray(): ByteArray { 43 | return base64Codec.encode(this.serializeToByteArray()) 44 | } 45 | 46 | fun ByteArray.base64EncodeToString(): String { 47 | return base64Codec.encodeAsString(this) 48 | } 49 | 50 | fun ByteArray.base64Encode(): ByteArray { 51 | return base64Codec.encode(this) 52 | } 53 | 54 | @Throws(DecodeBase64Exception::class) 55 | fun String.base64Decode(): String { 56 | return try { 57 | org.apache.commons.codec.binary.StringUtils.newStringUtf8(base64Codec.decode(this)) 58 | } catch (e: Exception) { 59 | throw DecodeBase64Exception("Exception when Decode ${this.abbreviate()}", e) 60 | } 61 | } 62 | 63 | @Throws(DecodeBase64Exception::class) 64 | fun String.base64DecodeToObject(): T { 65 | return try { 66 | SerializationUtils.deserialize(base64Codec.decode(this)) 67 | } catch (e: Exception) { 68 | throw DecodeBase64Exception("Exception when Decode ${this.abbreviate()}", e) 69 | } 70 | } 71 | 72 | @Throws(DecodeBase64Exception::class) 73 | fun ByteArray.base64DecodeToObject(): T { 74 | return try { 75 | SerializationUtils.deserialize(base64Codec.decode(this)) 76 | } catch (e: Exception) { 77 | throw DecodeBase64Exception("Exception when Decode ${this.size} bytes ", e) 78 | } 79 | } 80 | 81 | 82 | class DecodeBase64Exception(message: String, cause: Throwable) : Exception(message, cause) -------------------------------------------------------------------------------- /src/main/kotlin/org/jasoet/vertx/extension/codec/Digest.kt: -------------------------------------------------------------------------------- 1 | package org.jasoet.vertx.extension.codec 2 | 3 | import org.apache.commons.codec.digest.DigestUtils 4 | import java.io.InputStream 5 | 6 | /** 7 | * [Documentation Here] 8 | * 9 | * @author Deny Prasetyo 10 | */ 11 | 12 | /** 13 | * Calculate SHA1 Digest as Hexadecimal 14 | * Do not close [InputStream] after use nor reset 15 | * please handle it yourself 16 | * 17 | * @return Hexadecimal Digest from [InputStream] as String 18 | */ 19 | fun InputStream.sha1HexDigest(): String { 20 | return DigestUtils.sha1Hex(this) 21 | } 22 | 23 | /** 24 | * Calculate SHA1 Digest as Hexadecimal 25 | * 26 | * @return Hexadecimal Digest from [ByteArray] as String 27 | */ 28 | fun ByteArray.sha1HexDigest(): String { 29 | return DigestUtils.sha1Hex(this) 30 | } 31 | -------------------------------------------------------------------------------- /src/main/kotlin/org/jasoet/vertx/verticle/MainVerticle.kt: -------------------------------------------------------------------------------- 1 | package org.jasoet.vertx.verticle 2 | 3 | import io.vertx.core.AbstractVerticle 4 | import io.vertx.core.Future 5 | import io.vertx.core.http.HttpServer 6 | import org.jasoet.vertx.controller.MainController 7 | import org.jasoet.vertx.extension.logger 8 | import org.jasoet.vertx.extension.observable 9 | import org.springframework.beans.factory.annotation.Autowired 10 | import org.springframework.stereotype.Component 11 | 12 | /** 13 | * [Documentation Here] 14 | * 15 | * @author Deny Prasetyo. 16 | */ 17 | 18 | @Component 19 | class MainVerticle @Autowired constructor(val mainController: MainController) : AbstractVerticle() { 20 | private val log = logger(MainVerticle::class) 21 | private val config by lazy { config() } 22 | 23 | override fun start(startFuture: Future) { 24 | log.info("Initialize Main Verticle...") 25 | 26 | log.info("Initialize Router...") 27 | val router = mainController.create() 28 | 29 | log.info("Starting HttpServer...") 30 | observable { 31 | vertx.createHttpServer() 32 | .requestHandler { router.accept(it) } 33 | .listen(config.getInteger("HTTP_PORT"), it) 34 | }.subscribe( 35 | { 36 | log.info("HttpServer started in port ${config.getInteger("HTTP_PORT")}") 37 | log.info("Main Verticle Deployed!") 38 | startFuture.complete() 39 | }, 40 | { 41 | log.error("Failed to start HttpServer. [${it.message}]", it) 42 | log.error("Main Verticle Failed to Deploy!") 43 | startFuture.fail(it) 44 | } 45 | ) 46 | } 47 | } -------------------------------------------------------------------------------- /src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | spring: 2 | datasource: 3 | driver-class-name: com.mysql.jdbc.Driver 4 | url: jdbc:mysql://localhost:3306/vertx?autoReconnect=true&useSSL=false 5 | username: jasoet 6 | password: localhost 7 | validation-query: SELECT 1; 8 | test-on-borrow: true 9 | validation-interval: 30000 10 | jpa: 11 | database: MYSQL 12 | hibernate: 13 | ddl-auto: update 14 | naming: 15 | strategy: org.hibernate.cfg.ImprovedNamingStrategy 16 | show-sql: false 17 | -------------------------------------------------------------------------------- /src/test/kotlin/org/jasoet/vertx/controller/MainControllerTest.kt: -------------------------------------------------------------------------------- 1 | package org.jasoet.vertx.controller 2 | 3 | import io.vertx.core.DeploymentOptions 4 | import io.vertx.core.Vertx 5 | import io.vertx.core.json.JsonObject 6 | import io.vertx.ext.unit.TestContext 7 | import io.vertx.ext.unit.junit.VertxUnitRunner 8 | import org.jasoet.vertx.VertxApplication 9 | import org.jasoet.vertx.extension.getBean 10 | import org.jasoet.vertx.extension.logger 11 | import org.jasoet.vertx.extension.springBootStart 12 | import org.jasoet.vertx.verticle.MainVerticle 13 | import org.junit.After 14 | import org.junit.Before 15 | import org.junit.Test 16 | import org.junit.runner.RunWith 17 | import org.springframework.context.ConfigurableApplicationContext 18 | import java.net.ServerSocket 19 | 20 | /** 21 | * [Documentation Here] 22 | * 23 | * @author Deny Prasetyo 24 | */ 25 | 26 | @RunWith(VertxUnitRunner::class) 27 | class MainControllerTest { 28 | val log = logger(MainControllerTest::class) 29 | lateinit var sharedVertx: Vertx 30 | lateinit var sharedContext: ConfigurableApplicationContext 31 | var port: Int = 0 32 | 33 | @Before 34 | fun setUp(context: TestContext) { 35 | val socket = ServerSocket(0) 36 | port = socket.localPort 37 | socket.close() 38 | log.info("Get Unused Port $port") 39 | 40 | log.info("Initialize Spring Application Context!") 41 | sharedContext = springBootStart() 42 | 43 | log.info("Retrieve Vertx and MainVerticle") 44 | sharedVertx = sharedContext.getBean() 45 | val mainVerticle = sharedContext.getBean() 46 | 47 | val config = sharedContext.getBean() 48 | config.put("HTTP_PORT", port) 49 | 50 | log.info("Deploying Main Verticle") 51 | sharedVertx.deployVerticle(mainVerticle, DeploymentOptions().apply { 52 | this.config = config 53 | }) 54 | } 55 | 56 | @Test 57 | fun testSimpleEndpoint(context: TestContext) { 58 | val async = context.async() 59 | log.info("Request Get to localhost:$port/") 60 | sharedVertx.createHttpClient().getNow(port, "localhost", "/") { response -> 61 | response.handler { body -> 62 | val bodyAsString = body.toString() 63 | log.info("Received Body [$bodyAsString]") 64 | context.assertTrue(bodyAsString.contains("Hello", ignoreCase = true)) 65 | async.complete() 66 | } 67 | } 68 | } 69 | 70 | @After 71 | fun tearDown(context: TestContext) { 72 | sharedVertx.close(context.asyncAssertSuccess()) 73 | sharedContext.close() 74 | } 75 | } --------------------------------------------------------------------------------