├── .gitignore ├── README.md ├── employee-app-starter ├── .gitignore ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src │ ├── main │ └── java │ │ └── com │ │ └── learnwebclient │ │ ├── constants │ │ └── EmployeeConstants.java │ │ ├── dto │ │ └── Employee.java │ │ └── service │ │ └── EmployeeRestClient.java │ └── test │ └── java │ └── com │ └── learnwebclient │ └── service │ └── EmployeeRestClientTest.java ├── employee-app ├── .gitignore ├── build.gradle ├── build1.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src │ ├── main │ └── java │ │ └── com │ │ └── learnwebclient │ │ ├── constants │ │ └── EmployeeConstants.java │ │ ├── dto │ │ └── Employee.java │ │ ├── exception │ │ ├── ClientDataException.java │ │ └── EmployeeServiceException.java │ │ └── service │ │ └── EmployeeRestClient.java │ └── test │ └── java │ └── com │ └── learnwebclient │ └── service │ └── EmployeeRestClientTest.java ├── employee-service-executable └── employee-service.jar └── employee-service ├── .gitignore ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── main ├── java │ └── com │ │ └── employeeservice │ │ ├── EmployeeRestfulServiceApplication.java │ │ ├── config │ │ └── SwaggerConfig.java │ │ ├── constants │ │ └── EmployeeConstants.java │ │ ├── controller │ │ └── EmployeeController.java │ │ ├── entity │ │ └── Employee.java │ │ ├── exception │ │ └── GlobalExceptionHandler.java │ │ ├── intialize │ │ └── EmployeeDataInitializer.java │ │ └── repository │ │ └── EmployeeRepository.java └── resources │ └── application.yml └── test ├── java └── com │ └── employeeservice │ └── controller │ └── EmployeeControllerTestIT.java └── resources └── data └── sql ├── employeeSetUp.sql └── tearDown.sql /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # spring-webclient 2 | This repository explores the WebClient API to perform Rest API calls 3 | 4 | ## Launch the Employee RestFul Service in your local 5 | 6 | - Navigate to the **employee-service-executable** directory. 7 | 8 | - Run the below command 9 | ``` 10 | java -jar employee-service.jar 11 | ``` 12 | 13 | - The complete swagger spec is available in tthe below link. 14 | 15 | http://localhost:8081/employeeservice/swagger-ui.html#/ 16 | -------------------------------------------------------------------------------- /employee-app-starter/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/** 6 | !**/src/test/** 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | 17 | ### IntelliJ IDEA ### 18 | .idea 19 | *.iws 20 | *.iml 21 | *.ipr 22 | out/ 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /nbdist/ 28 | /dist/ 29 | /out/ 30 | /.nb-gradle/ 31 | 32 | ### VS Code ### 33 | .vscode/ -------------------------------------------------------------------------------- /employee-app-starter/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | } 4 | apply plugin: 'java' 5 | 6 | group 'com.learnwebclient' 7 | version '1.0-SNAPSHOT' 8 | 9 | sourceCompatibility = '1.8' 10 | 11 | repositories { 12 | mavenCentral() 13 | } 14 | 15 | test{ 16 | useJUnitPlatform() 17 | } 18 | 19 | dependencies { 20 | //webclient-dependencies 21 | implementation 'org.springframework:spring-webflux:5.1.8.RELEASE' 22 | implementation 'io.projectreactor.netty:reactor-netty:0.8.9.RELEASE' 23 | 24 | //lombok 25 | compileOnly 'org.projectlombok:lombok:1.18.8' 26 | annotationProcessor 'org.projectlombok:lombok:1.18.8' 27 | implementation 'org.slf4j:slf4j-api:1.7.26' 28 | implementation 'org.slf4j:slf4j-simple:1.7.26' 29 | 30 | //jackson 31 | implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.9' 32 | implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.9.9' 33 | implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.9' 34 | implementation 'com.fasterxml.jackson.module:jackson-module-parameter-names:2.9.9' 35 | 36 | //junit5-dependencies 37 | testImplementation("org.junit.jupiter:junit-jupiter-engine:5.5.1") 38 | } 39 | 40 | -------------------------------------------------------------------------------- /employee-app-starter/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-with-dilip/spring-webclient/84bff2bbe333920b045b7f23ab2a9f8e5f35ed86/employee-app-starter/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /employee-app-starter/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.8-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /employee-app-starter/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 | -------------------------------------------------------------------------------- /employee-app-starter/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 | -------------------------------------------------------------------------------- /employee-app-starter/src/main/java/com/learnwebclient/constants/EmployeeConstants.java: -------------------------------------------------------------------------------- 1 | package com.learnwebclient.constants; 2 | 3 | public class EmployeeConstants { 4 | } 5 | -------------------------------------------------------------------------------- /employee-app-starter/src/main/java/com/learnwebclient/dto/Employee.java: -------------------------------------------------------------------------------- 1 | package com.learnwebclient.dto; 2 | 3 | public class Employee { 4 | } 5 | -------------------------------------------------------------------------------- /employee-app-starter/src/main/java/com/learnwebclient/service/EmployeeRestClient.java: -------------------------------------------------------------------------------- 1 | package com.learnwebclient.service; 2 | 3 | public class EmployeeRestClient { 4 | } 5 | -------------------------------------------------------------------------------- /employee-app-starter/src/test/java/com/learnwebclient/service/EmployeeRestClientTest.java: -------------------------------------------------------------------------------- 1 | package com.learnwebclient.service; 2 | 3 | public class EmployeeRestClientTest { 4 | } 5 | -------------------------------------------------------------------------------- /employee-app/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/** 6 | !**/src/test/** 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | 17 | ### IntelliJ IDEA ### 18 | .idea 19 | *.iws 20 | *.iml 21 | *.ipr 22 | out/ 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /nbdist/ 28 | /dist/ 29 | /out/ 30 | /.nb-gradle/ 31 | 32 | ### VS Code ### 33 | .vscode/ -------------------------------------------------------------------------------- /employee-app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | } 4 | apply plugin: 'java' 5 | 6 | group 'com.learnwebclient' 7 | version '1.0-SNAPSHOT' 8 | 9 | sourceCompatibility = '1.8' 10 | 11 | repositories { 12 | mavenCentral() 13 | } 14 | 15 | test{ 16 | useJUnitPlatform() 17 | } 18 | 19 | dependencies { 20 | //webclient-dependencies 21 | implementation 'org.springframework:spring-webflux:5.1.8.RELEASE' 22 | implementation 'io.projectreactor.netty:reactor-netty:0.8.9.RELEASE' 23 | implementation 'io.projectreactor.addons:reactor-extra:3.2.2.RELEASE' 24 | 25 | 26 | //lombok 27 | compileOnly 'org.projectlombok:lombok:1.18.8' 28 | annotationProcessor 'org.projectlombok:lombok:1.18.8' 29 | implementation 'org.slf4j:slf4j-api:1.7.26' 30 | implementation 'org.slf4j:slf4j-simple:1.7.26' 31 | 32 | //jackson 33 | implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.9' 34 | implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.9.9' 35 | implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.9' 36 | implementation 'com.fasterxml.jackson.module:jackson-module-parameter-names:2.9.9' 37 | 38 | //junit5-dependencies 39 | testImplementation("org.junit.jupiter:junit-jupiter-engine:5.5.1") 40 | } 41 | 42 | -------------------------------------------------------------------------------- /employee-app/build1.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | } 4 | apply plugin: 'java' 5 | 6 | group 'com.learnwebclient' 7 | version '1.0-SNAPSHOT' 8 | 9 | sourceCompatibility = '1.8' 10 | 11 | repositories { 12 | mavenCentral() 13 | } 14 | 15 | test{ 16 | useJUnitPlatform() 17 | } 18 | 19 | dependencies { 20 | //webclient-dependencies 21 | implementation 'org.springframework:spring-webflux:5.1.8.RELEASE' 22 | implementation 'io.projectreactor.netty:reactor-netty:0.8.9.RELEASE' 23 | implementation 'io.projectreactor.addons:reactor-extra:3.2.2.RELEASE' 24 | 25 | 26 | //lombok 27 | compileOnly 'org.projectlombok:lombok:1.18.8' 28 | annotationProcessor 'org.projectlombok:lombok:1.18.8' 29 | implementation 'org.slf4j:slf4j-api:1.7.26' 30 | implementation 'org.slf4j:slf4j-simple:1.7.26' 31 | 32 | //jackson 33 | implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.9' 34 | implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.9.9' 35 | implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.9' 36 | implementation 'com.fasterxml.jackson.module:jackson-module-parameter-names:2.9.9' 37 | 38 | //junit5-dependencies 39 | testImplementation("org.junit.jupiter:junit-jupiter-engine:5.5.1") 40 | } 41 | 42 | -------------------------------------------------------------------------------- /employee-app/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-with-dilip/spring-webclient/84bff2bbe333920b045b7f23ab2a9f8e5f35ed86/employee-app/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /employee-app/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Sep 29 11:21:05 CDT 2019 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-4.8-all.zip 7 | -------------------------------------------------------------------------------- /employee-app/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 | -------------------------------------------------------------------------------- /employee-app/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 | -------------------------------------------------------------------------------- /employee-app/src/main/java/com/learnwebclient/constants/EmployeeConstants.java: -------------------------------------------------------------------------------- 1 | package com.learnwebclient.constants; 2 | 3 | public class EmployeeConstants { 4 | 5 | public static final String GET_ALL_EMPLOYEES_V1 ="/v1/allEmployees"; 6 | public static final String EMPLOYEE_BY_ID_V1 ="/v1/employee/{id}"; 7 | public static final String GET_EMPLOYEE_BY_NAME_V1 ="/v1/employeeName"; 8 | public static final String ADD_EMPLOYEE_V1 ="/v1/employee"; 9 | public static final String ERROR_EMPLOYEE_V1 ="/v1/employee/error"; 10 | 11 | } 12 | -------------------------------------------------------------------------------- /employee-app/src/main/java/com/learnwebclient/dto/Employee.java: -------------------------------------------------------------------------------- 1 | package com.learnwebclient.dto; 2 | 3 | 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @AllArgsConstructor 10 | @NoArgsConstructor 11 | public class Employee { 12 | 13 | private Long id; 14 | 15 | private String firstName; 16 | 17 | private String lastName; 18 | 19 | private Integer age; 20 | 21 | private String gender; 22 | 23 | private String role; 24 | } 25 | 26 | 27 | -------------------------------------------------------------------------------- /employee-app/src/main/java/com/learnwebclient/exception/ClientDataException.java: -------------------------------------------------------------------------------- 1 | package com.learnwebclient.exception; 2 | 3 | public class ClientDataException extends RuntimeException { 4 | public ClientDataException(String message) { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /employee-app/src/main/java/com/learnwebclient/exception/EmployeeServiceException.java: -------------------------------------------------------------------------------- 1 | package com.learnwebclient.exception; 2 | 3 | public class EmployeeServiceException extends RuntimeException { 4 | public EmployeeServiceException(String message) { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /employee-app/src/main/java/com/learnwebclient/service/EmployeeRestClient.java: -------------------------------------------------------------------------------- 1 | package com.learnwebclient.service; 2 | 3 | import com.learnwebclient.dto.Employee; 4 | import com.learnwebclient.exception.ClientDataException; 5 | import com.learnwebclient.exception.EmployeeServiceException; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.web.reactive.function.client.ClientResponse; 9 | import org.springframework.web.reactive.function.client.WebClient; 10 | import org.springframework.web.reactive.function.client.WebClientResponseException; 11 | import org.springframework.web.util.UriComponentsBuilder; 12 | import reactor.core.publisher.Mono; 13 | import reactor.retry.Retry; 14 | 15 | import java.time.Duration; 16 | import java.util.List; 17 | 18 | import static com.learnwebclient.constants.EmployeeConstants.*; 19 | 20 | @Slf4j 21 | public class EmployeeRestClient { 22 | 23 | private WebClient webClient; 24 | 25 | public EmployeeRestClient(WebClient webClient) { 26 | this.webClient = webClient; 27 | } 28 | 29 | public static Retry fixedRetry = Retry.anyOf(WebClientResponseException .class) 30 | .fixedBackoff(Duration.ofSeconds(2)) 31 | .retryMax(3) 32 | .doOnRetry((exception) -> { 33 | log.info("The exception is : " + exception); 34 | 35 | }); 36 | 37 | 38 | public static Retry fixedRetry5xx = Retry.anyOf(EmployeeServiceException .class) 39 | .fixedBackoff(Duration.ofSeconds(2)) 40 | .retryMax(3) 41 | .doOnRetry((exception) -> { 42 | log.info("The exception is : " + exception); 43 | 44 | }); 45 | 46 | public Mono handle4xxErrorResponse(ClientResponse clientResponse) { 47 | Mono errorResponse = clientResponse.bodyToMono(String.class); 48 | return errorResponse.flatMap((message) -> { 49 | log.error("ErrorResponse Code is " + clientResponse.rawStatusCode() + " and the exception message is : " + message); 50 | throw new ClientDataException(message); 51 | }); 52 | } 53 | 54 | public Mono handle5xxErrorResponse(ClientResponse clientResponse) { 55 | Mono errorResponse = clientResponse.bodyToMono(String.class); 56 | return errorResponse.flatMap((message) -> { 57 | log.error("ErrorResponse Code is " + clientResponse.rawStatusCode() + " and the exception message is : " + message); 58 | throw new EmployeeServiceException(message); 59 | }); 60 | } 61 | 62 | public List retrieveAllEmployees() { 63 | try { 64 | return webClient.get().uri(GET_ALL_EMPLOYEES_V1) 65 | .retrieve() 66 | .bodyToFlux(Employee.class) 67 | .collectList() 68 | .block(); 69 | } catch (WebClientResponseException ex) { 70 | log.error("Error Response code is : {} and the message is {}", ex.getRawStatusCode(), ex.getResponseBodyAsString()); 71 | log.error("WebClientResponseException in retrieveAllEmployees", ex); 72 | throw ex; 73 | } catch (Exception ex) { 74 | log.error("Exception in retrieveAllEmployees ", ex); 75 | throw ex; 76 | } 77 | } 78 | 79 | 80 | public Employee retrieveEmployeeById(int employeeId) { 81 | 82 | try { 83 | return webClient.get().uri(EMPLOYEE_BY_ID_V1, employeeId) 84 | .retrieve() 85 | .bodyToMono(Employee.class) 86 | .block(); 87 | } catch (WebClientResponseException ex) { 88 | log.error("Error Response code is : {} and the message is {}", ex.getRawStatusCode(), ex.getResponseBodyAsString()); 89 | log.error("WebClientResponseException in retrieveEmployeeById", ex); 90 | throw ex; 91 | } catch (Exception ex) { 92 | log.error("Exception in retrieveEmployeeById ", ex); 93 | throw ex; 94 | } 95 | } 96 | 97 | public Employee retrieveEmployeeById_Custom_Error_Handling(int employeeId) { 98 | 99 | return webClient.get().uri(EMPLOYEE_BY_ID_V1, employeeId) 100 | .retrieve() 101 | .onStatus(HttpStatus::is4xxClientError, clientResponse -> handle4xxErrorResponse(clientResponse)) 102 | .onStatus(HttpStatus::is5xxServerError, clientResponse -> handle5xxErrorResponse(clientResponse)) 103 | .bodyToMono(Employee.class) 104 | .block(); 105 | } 106 | 107 | public Employee retrieveEmployeeById_WithRetry(int employeeId) { 108 | 109 | try { 110 | return webClient.get().uri(EMPLOYEE_BY_ID_V1, employeeId) 111 | .retrieve() 112 | .bodyToMono(Employee.class) 113 | .retryWhen(fixedRetry) 114 | .block(); 115 | } catch (WebClientResponseException ex) { 116 | log.error("Error Response code is : {} and the message is {}", ex.getRawStatusCode(), ex.getResponseBodyAsString()); 117 | log.error("WebClientResponseException in retrieveEmployeeById", ex); 118 | throw ex; 119 | } catch (Exception ex) { 120 | log.error("Exception in retrieveEmployeeById ", ex); 121 | throw new EmployeeServiceException(ex.getMessage()); 122 | } 123 | } 124 | 125 | public List retrieveEmployeeByName(String employeeName) { 126 | 127 | String uri = UriComponentsBuilder.fromUriString(GET_EMPLOYEE_BY_NAME_V1) 128 | .queryParam("employee_name", employeeName) 129 | .build().toUriString(); 130 | try { 131 | return webClient.get().uri(uri) 132 | .retrieve() 133 | .bodyToFlux(Employee.class) 134 | .collectList() 135 | .block(); 136 | } catch (WebClientResponseException ex) { 137 | log.error("Error Response code is : {} and the message is {}", ex.getRawStatusCode(), ex.getResponseBodyAsString()); 138 | log.error("WebClientResponseException in retrieveEmployeeByName", ex); 139 | throw ex; 140 | } catch (Exception ex) { 141 | log.error("Exception in retrieveEmployeeByName ", ex); 142 | throw ex; 143 | } 144 | } 145 | 146 | public Employee addNewEmployee(Employee employee) { 147 | try { 148 | return webClient.post().uri(ADD_EMPLOYEE_V1) 149 | .syncBody(employee) 150 | .retrieve() 151 | .bodyToMono(Employee.class) 152 | .block(); 153 | } catch (WebClientResponseException ex) { 154 | log.error("Error Response code is : {} and the message is {}", ex.getRawStatusCode(), ex.getResponseBodyAsString()); 155 | log.error("WebClientResponseException in addNewEmployee", ex); 156 | throw ex; 157 | } catch (Exception ex) { 158 | log.error("Exception in addNewEmployee ", ex); 159 | throw ex; 160 | } 161 | } 162 | 163 | public Employee addNewEmployee_custom_Error_Handling(Employee employee) { 164 | return webClient.post().uri(ADD_EMPLOYEE_V1) 165 | .syncBody(employee) 166 | .retrieve() 167 | .onStatus(HttpStatus::is4xxClientError, clientResponse -> handle4xxErrorResponse(clientResponse)) 168 | .onStatus(HttpStatus::is5xxServerError, clientResponse -> handle5xxErrorResponse(clientResponse)) 169 | .bodyToMono(Employee.class) 170 | .block(); 171 | } 172 | 173 | public Employee updateEmployee(int id, Employee employee) { 174 | 175 | try { 176 | return webClient.put().uri(EMPLOYEE_BY_ID_V1, id) 177 | .syncBody(employee) 178 | .retrieve() 179 | .bodyToMono(Employee.class) 180 | .block(); 181 | } catch (WebClientResponseException ex) { 182 | log.error("Error Response code is : {} and the message is {}", ex.getRawStatusCode(), ex.getResponseBodyAsString()); 183 | log.error("WebClientResponseException in updateEmployee", ex); 184 | throw ex; 185 | } catch (Exception ex) { 186 | log.error("Exception in updateEmployee ", ex); 187 | throw ex; 188 | } 189 | } 190 | 191 | public String deleteEmployeeById(int id) { 192 | try { 193 | return webClient.delete().uri(EMPLOYEE_BY_ID_V1, id) 194 | .retrieve() 195 | .bodyToMono(String.class) 196 | .block(); 197 | } catch (WebClientResponseException ex) { 198 | log.error("Error Response code is : {} and the message is {}", ex.getRawStatusCode(), ex.getResponseBodyAsString()); 199 | log.error("WebClientResponseException in updateEmployee", ex); 200 | throw ex; 201 | } catch (Exception ex) { 202 | log.error("Exception in updateEmployee ", ex); 203 | throw ex; 204 | } 205 | 206 | } 207 | 208 | public String errorEndpoint(){ 209 | 210 | return webClient.get().uri(ERROR_EMPLOYEE_V1) 211 | .retrieve() 212 | .onStatus(HttpStatus::is4xxClientError, clientResponse -> handle4xxErrorResponse(clientResponse)) 213 | .onStatus(HttpStatus::is5xxServerError, clientResponse -> handle5xxErrorResponse(clientResponse)) 214 | .bodyToMono(String.class) 215 | .block(); 216 | } 217 | 218 | public String errorEndpoint_fixedRetry(){ 219 | 220 | return webClient.get().uri(ERROR_EMPLOYEE_V1) 221 | .retrieve() 222 | .onStatus(HttpStatus::is4xxClientError, clientResponse -> handle4xxErrorResponse(clientResponse)) 223 | .onStatus(HttpStatus::is5xxServerError, clientResponse -> handle5xxErrorResponse(clientResponse)) 224 | .bodyToMono(String.class) 225 | .retryWhen(fixedRetry5xx) 226 | .block(); 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /employee-app/src/test/java/com/learnwebclient/service/EmployeeRestClientTest.java: -------------------------------------------------------------------------------- 1 | package com.learnwebclient.service; 2 | 3 | import com.learnwebclient.dto.Employee; 4 | import com.learnwebclient.exception.ClientDataException; 5 | import com.learnwebclient.exception.EmployeeServiceException; 6 | import org.junit.jupiter.api.Assertions; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.Test; 9 | import org.springframework.web.reactive.function.client.WebClient; 10 | import org.springframework.web.reactive.function.client.WebClientResponseException; 11 | import reactor.retry.RetryExhaustedException; 12 | 13 | import java.util.List; 14 | 15 | import static org.junit.jupiter.api.Assertions.assertEquals; 16 | import static org.junit.jupiter.api.Assertions.assertTrue; 17 | 18 | public class EmployeeRestClientTest { 19 | 20 | private String baseUrl = "http://localhost:8081/employeeservice"; 21 | WebClient webClient = WebClient.create(baseUrl); 22 | EmployeeRestClient employeeRestClient = new EmployeeRestClient(webClient);; 23 | 24 | @Test 25 | void retrieveAllEmployees(){ 26 | List employeeList = employeeRestClient.retrieveAllEmployees(); 27 | assertTrue(employeeList.size()>0); 28 | } 29 | 30 | @Test 31 | void retrieveEmployeeById(){ 32 | int employeeId = 1; 33 | Employee employee = employeeRestClient.retrieveEmployeeById(employeeId); 34 | assertEquals("Chris", employee.getFirstName()); 35 | } 36 | 37 | @Test 38 | void retrieveEmployeeById_NotFound(){ 39 | int employeeId = 100; 40 | Assertions.assertThrows(WebClientResponseException.class, () -> employeeRestClient.retrieveEmployeeById(employeeId)); 41 | 42 | } 43 | 44 | @Test 45 | void retrieveEmployeeById_WithRetry(){ 46 | int employeeId = 100; 47 | Assertions.assertThrows(EmployeeServiceException.class, () -> employeeRestClient.retrieveEmployeeById_WithRetry(employeeId)); 48 | 49 | } 50 | 51 | @Test 52 | void retrieveEmployeeById_Custom_Error_Handling_Client_Data_Exception(){ 53 | 54 | int employeeId = 100; 55 | Assertions.assertThrows(ClientDataException.class, () -> employeeRestClient.retrieveEmployeeById_Custom_Error_Handling(employeeId)); 56 | } 57 | 58 | 59 | @Test 60 | void retrieveEmployeeByName(){ 61 | List employees =employeeRestClient.retrieveEmployeeByName("Sandler"); 62 | assertEquals(1, employees.size()); 63 | } 64 | 65 | @Test 66 | void retrieveEmployeeByName_NotFound(){ 67 | Assertions.assertThrows(WebClientResponseException.class, () -> employeeRestClient.retrieveEmployeeByName("ABC")); 68 | } 69 | 70 | @Test 71 | void addNewEmployee(){ 72 | Employee employee = new Employee(null,"Iron", "Man", 54, "male", "Architect"); 73 | 74 | Employee employee1 = employeeRestClient.addNewEmployee(employee); 75 | System.out.println("employee1 : " + employee1); 76 | assertTrue(employee1.getId()!=null); 77 | 78 | } 79 | 80 | @Test 81 | void addNewEmployee_BadRequest(){ 82 | Employee employee = new Employee(null,null, "Man", 54, "male", "Architect"); 83 | 84 | String expectedErrorMessage = "Please pass all the input fields : [firstName]"; 85 | Assertions.assertThrows(WebClientResponseException.class, () -> employeeRestClient.addNewEmployee(employee), expectedErrorMessage); 86 | 87 | } 88 | 89 | @Test 90 | void addNewEmployee_custom_Error_Handling(){ 91 | Employee employee = new Employee(null,null, "Man", 54, "male", "Architect"); 92 | Assertions.assertThrows(ClientDataException.class, () -> employeeRestClient.addNewEmployee_custom_Error_Handling(employee)); 93 | 94 | } 95 | 96 | @Test 97 | void updateEmployee(){ 98 | 99 | int employeeId = 2; 100 | Employee employee = new Employee(null,"Chris1", null, null, null, null); 101 | Employee updatedEmployee = employeeRestClient.updateEmployee(employeeId, employee); 102 | assertEquals("Chris1", updatedEmployee.getFirstName()); 103 | 104 | } 105 | 106 | @Test 107 | void updateEmployee_NotFound(){ 108 | 109 | int employeeId = 100; 110 | Employee employee = new Employee(null,"Chris1", null, null, null, null); 111 | Assertions.assertThrows(WebClientResponseException.class,() -> employeeRestClient.updateEmployee(employeeId, employee)); 112 | 113 | } 114 | 115 | @Test 116 | void deleteEmployee(){ 117 | 118 | Employee employee = new Employee(null,"Iron", "Man", 54, "male", "Architect"); 119 | Employee employee1 = employeeRestClient.addNewEmployee(employee); 120 | String message = employeeRestClient.deleteEmployeeById(employee1.getId().intValue()); 121 | String expectedMessage = "Employee deleted successfully."; 122 | assertEquals(expectedMessage,message); 123 | } 124 | 125 | @Test 126 | void deleteEmployee_NotFound(){ 127 | 128 | Assertions.assertThrows(WebClientResponseException.class,() -> employeeRestClient.deleteEmployeeById(100)); 129 | } 130 | 131 | @Test 132 | void errorEndpoint(){ 133 | 134 | Assertions.assertThrows(EmployeeServiceException.class,() -> employeeRestClient.errorEndpoint()); 135 | } 136 | 137 | @Test 138 | void errorEndpoint_withRetry(){ 139 | 140 | Assertions.assertThrows(RetryExhaustedException.class,() -> employeeRestClient.errorEndpoint_fixedRetry()); 141 | } 142 | 143 | 144 | } 145 | -------------------------------------------------------------------------------- /employee-service-executable/employee-service.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-with-dilip/spring-webclient/84bff2bbe333920b045b7f23ab2a9f8e5f35ed86/employee-service-executable/employee-service.jar -------------------------------------------------------------------------------- /employee-service/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/** 6 | !**/src/test/** 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | 17 | ### IntelliJ IDEA ### 18 | .idea 19 | *.iws 20 | *.iml 21 | *.ipr 22 | out/ 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | 31 | ### VS Code ### 32 | .vscode/ -------------------------------------------------------------------------------- /employee-service/README.md: -------------------------------------------------------------------------------- 1 | # Employee RestFul Service 2 | 3 | ## Swagger Link 4 | 5 | The below link will launch the swagger of the movies-restful-web-service. 6 | 7 | http://localhost:8081/employeeservice/swagger-ui.html#/ -------------------------------------------------------------------------------- /employee-service/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.springframework.boot' version '2.1.6.RELEASE' 3 | id 'java' 4 | } 5 | 6 | apply plugin: 'io.spring.dependency-management' 7 | 8 | group = 'com.movies' 9 | version = '0.0.1-SNAPSHOT' 10 | configurations { 11 | compileOnly { 12 | extendsFrom annotationProcessor 13 | } 14 | } 15 | 16 | repositories { 17 | mavenCentral() 18 | } 19 | 20 | test { 21 | useJUnitPlatform() // enables Junit5 22 | } 23 | 24 | dependencies { 25 | implementation 'org.springframework.boot:spring-boot-starter-actuator' 26 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 27 | implementation 'org.springframework.boot:spring-boot-starter-web' 28 | implementation 'org.springframework.boot:spring-boot-starter-webflux' 29 | implementation 'io.projectreactor.addons:reactor-extra' 30 | 31 | //lombok 32 | compileOnly 'org.projectlombok:lombok' 33 | annotationProcessor 'org.projectlombok:lombok' 34 | 35 | //h2 36 | runtimeOnly 'com.h2database:h2' 37 | 38 | //swagger 39 | implementation "io.springfox:springfox-swagger2:2.9.2" 40 | implementation 'io.springfox:springfox-swagger-ui:2.9.2' 41 | 42 | //Testing 43 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 44 | 45 | //junit5-dependencies 46 | testImplementation('org.junit.jupiter:junit-jupiter:5.5.1') 47 | } -------------------------------------------------------------------------------- /employee-service/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-with-dilip/spring-webclient/84bff2bbe333920b045b7f23ab2a9f8e5f35ed86/employee-service/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /employee-service/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Aug 16 07:56:00 CDT 2019 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-5.4.1-bin.zip 7 | -------------------------------------------------------------------------------- /employee-service/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 | -------------------------------------------------------------------------------- /employee-service/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 | -------------------------------------------------------------------------------- /employee-service/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'employee-service' 2 | 3 | -------------------------------------------------------------------------------- /employee-service/src/main/java/com/employeeservice/EmployeeRestfulServiceApplication.java: -------------------------------------------------------------------------------- 1 | package com.employeeservice; 2 | 3 | 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import springfox.documentation.swagger2.annotations.EnableSwagger2; 7 | 8 | @SpringBootApplication 9 | @EnableSwagger2 10 | public class EmployeeRestfulServiceApplication { 11 | public static void main(String[] args) { 12 | SpringApplication.run(EmployeeRestfulServiceApplication.class, args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /employee-service/src/main/java/com/employeeservice/config/SwaggerConfig.java: -------------------------------------------------------------------------------- 1 | package com.employeeservice.config; 2 | 3 | import springfox.documentation.service.Contact; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import springfox.documentation.builders.RequestHandlerSelectors; 7 | import springfox.documentation.service.ApiInfo; 8 | import springfox.documentation.service.StringVendorExtension; 9 | import springfox.documentation.spi.DocumentationType; 10 | import springfox.documentation.spring.web.plugins.Docket; 11 | import springfox.documentation.swagger2.annotations.EnableSwagger2; 12 | 13 | import java.util.Arrays; 14 | 15 | @Configuration 16 | @EnableSwagger2 17 | public class SwaggerConfig { 18 | 19 | @Bean 20 | public Docket moviesAPI(){ 21 | 22 | return new Docket(DocumentationType.SWAGGER_2) 23 | .select() 24 | .apis(RequestHandlerSelectors.basePackage("com.employeeservice.controller")) 25 | //.paths(regex("/rest.*")) 26 | .build() 27 | .apiInfo(apiInfo()); 28 | } 29 | 30 | 31 | private ApiInfo apiInfo() { 32 | 33 | Contact contact = new Contact("Dilip Sundarraj Youtube Channel","https://www.youtube.com/codewithdilip",""); 34 | 35 | StringVendorExtension listVendorExtension = new StringVendorExtension("Code With Dilip", "Online Instructor"); 36 | ApiInfo apiInfo = new ApiInfo("Employee RestFul Service API", 37 | "Employee RestFul Service", 38 | "1.0", 39 | "", 40 | (springfox.documentation.service.Contact) contact, 41 | "Employee RestFul Service- Source Code" 42 | ,"https://github.com/code-with-dilip/spring-webclient/tree/master/employee-service", 43 | Arrays.asList(listVendorExtension)); 44 | return apiInfo; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /employee-service/src/main/java/com/employeeservice/constants/EmployeeConstants.java: -------------------------------------------------------------------------------- 1 | package com.employeeservice.constants; 2 | 3 | public class EmployeeConstants { 4 | public static final String GET_ALL_MOVIES_V1 = "/v1/allEmployees"; 5 | public static final String EMPLOYEE_BY_ID_PATH_PARAM_V1 = "/v1/employee/{id}"; 6 | public static final String EMPLOYEE_BY_NAME_QUERY_PARAM_V1 ="/v1/employeeName" ; 7 | public static final String ADD_EMPLOYEE_V1 = "/v1/employee"; 8 | public static final String ERROR_ENDPOINT = "/v1/employee/error"; 9 | public static final String ERROR_MESSAGE = "Please pass all the input fields : "; 10 | 11 | public static final String DELETE_MESSAGE = "Employee deleted successfully."; 12 | } 13 | -------------------------------------------------------------------------------- /employee-service/src/main/java/com/employeeservice/controller/EmployeeController.java: -------------------------------------------------------------------------------- 1 | package com.employeeservice.controller; 2 | 3 | import com.employeeservice.constants.EmployeeConstants; 4 | import com.employeeservice.entity.Employee; 5 | import com.employeeservice.repository.EmployeeRepository; 6 | import io.swagger.annotations.ApiOperation; 7 | import io.swagger.annotations.ApiResponse; 8 | import io.swagger.annotations.ApiResponses; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.http.HttpStatus; 12 | import org.springframework.http.ResponseEntity; 13 | import org.springframework.util.CollectionUtils; 14 | import org.springframework.util.StringUtils; 15 | import org.springframework.web.bind.annotation.*; 16 | import org.springframework.web.server.ResponseStatusException; 17 | 18 | import javax.validation.Valid; 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | import java.util.Optional; 22 | import java.util.function.Function; 23 | import java.util.function.Supplier; 24 | 25 | import static com.employeeservice.constants.EmployeeConstants.*; 26 | 27 | @RestController 28 | @Slf4j 29 | public class EmployeeController { 30 | 31 | @Autowired 32 | EmployeeRepository employeeRepository; 33 | 34 | Function notFoundId = (id) -> { 35 | return new ResponseStatusException(HttpStatus.NOT_FOUND, "No Employee Available with the given Id - "+ id); 36 | }; 37 | 38 | Function notFoundName = (name) -> { 39 | throw new ResponseStatusException(HttpStatus.NOT_FOUND, "No Employee Available with the given name - "+ name); 40 | }; 41 | 42 | Supplier serverError = () -> { 43 | throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "RunTimeException from Employee Service"); 44 | }; 45 | 46 | 47 | @GetMapping(GET_ALL_MOVIES_V1) 48 | @ApiOperation("Retrieves all the Employees") 49 | @ApiResponses( 50 | value = { 51 | @ApiResponse(code = 200, message = "SuccessFul Retrieval of Employees") 52 | } 53 | ) 54 | public List allEmployees() { 55 | List employees = new ArrayList<>(); 56 | log.info("Recieved request for retrieving all Employees"); 57 | employeeRepository.findAll() 58 | .forEach(employees::add); 59 | return employees; 60 | } 61 | 62 | @ApiOperation("Retrieve an Employee using the Employee id.") 63 | @ApiResponses( 64 | value = { 65 | @ApiResponse(code = 200, message = "Returns the Employee for the id."), 66 | @ApiResponse(code = 404, message = "No Employee found for the id that's passed."), 67 | } 68 | ) 69 | @GetMapping(EMPLOYEE_BY_ID_PATH_PARAM_V1) 70 | public ResponseEntity employeeById(@PathVariable Long id) { 71 | 72 | Optional employeeOptional = employeeRepository.findById(id); 73 | if (employeeOptional.isPresent()) { 74 | log.info("Response is {}.", employeeOptional.get()); 75 | return ResponseEntity.status(HttpStatus.OK).body(employeeOptional.get()); 76 | 77 | } else { 78 | log.info("No Employee available with the given Employee Id - {}", id); 79 | throw notFoundId.apply(id); 80 | } 81 | } 82 | 83 | @ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "video not found") 84 | public class EmployeeNotFoundException extends RuntimeException { 85 | } 86 | 87 | 88 | @ApiOperation("Returns the Employees using the employee name passed as part of the request.") 89 | @ApiResponses( 90 | value = { 91 | @ApiResponse(code = 200, message = "Returns the Employees using the FirstName or LastName of the employee."), 92 | @ApiResponse(code = 404, message = "No Employee found for the name thats passed."), 93 | } 94 | ) 95 | @GetMapping(EMPLOYEE_BY_NAME_QUERY_PARAM_V1) 96 | public ResponseEntity movieByName(@RequestParam("employee_name") String name) { 97 | 98 | log.info("Received the request to search by Employee name - {} .", name); 99 | 100 | List employees = employeeRepository.findByEmployeeName(name); 101 | if (CollectionUtils.isEmpty(employees)) { 102 | log.info("No Employee available for the given Employee name - {}.", name); 103 | throw notFoundName.apply(name); 104 | } else { 105 | log.info("Response is : {}", employees); 106 | return ResponseEntity.status(HttpStatus.OK).body(employees); 107 | 108 | } 109 | } 110 | 111 | @ApiOperation("Adds a new Employee.") 112 | @ApiResponses( 113 | value = { 114 | @ApiResponse(code = 201, message = "Employee Successfully added to the InMemory DB.") 115 | } 116 | ) 117 | @PostMapping(ADD_EMPLOYEE_V1) 118 | public ResponseEntity createMovie(@Valid @RequestBody Employee employee) { 119 | 120 | log.info("Received the request to add a new Employee in the service {} ", employee); 121 | Employee addedEmployee = employeeRepository.save(employee); 122 | log.info("Employee SuccessFully added to the DB. New Employee Details are {} .", employee); 123 | return ResponseEntity.status(HttpStatus.CREATED).body(addedEmployee); 124 | 125 | } 126 | 127 | @ApiOperation("Updates the Employee details.") 128 | @ApiResponses( 129 | value = { 130 | @ApiResponse(code = 200, message = "Employee details are successfully updated to the DB."), 131 | @ApiResponse(code = 404, message = "No Employee found for the id that's passed."), 132 | } 133 | ) 134 | @PutMapping(EMPLOYEE_BY_ID_PATH_PARAM_V1) 135 | public ResponseEntity updateEmployee(@PathVariable Long id, @RequestBody Employee updateEmployee) { 136 | log.info("Received the request to update the employee. Employee Id is {} and the updated Employee Details are {} ", id, updateEmployee); 137 | 138 | Optional employeeOptional = employeeRepository.findById(id); 139 | if (employeeOptional.isPresent()) { 140 | Employee employeeToUpdate = employeeOptional.get(); 141 | createEmployeeEntity(employeeToUpdate, updateEmployee); 142 | employeeRepository.save(employeeToUpdate); 143 | return ResponseEntity.status(HttpStatus.OK).body(employeeToUpdate); 144 | 145 | } else { 146 | log.info("No Employee available for the given Movie Id - {}.", id); 147 | throw notFoundId.apply(id); 148 | 149 | } 150 | 151 | 152 | } 153 | 154 | public void createEmployeeEntity(Employee employeeToUpdate, Employee updateEmployee) { 155 | if (checkEmptyNullString(updateEmployee.getFirstName()) && !updateEmployee.getFirstName().equals(employeeToUpdate.getFirstName())) { 156 | employeeToUpdate.setFirstName(updateEmployee.getFirstName()); 157 | } 158 | if (checkEmptyNullString(updateEmployee.getLastName()) && !updateEmployee.getLastName().equals(employeeToUpdate.getLastName())) { 159 | employeeToUpdate.setLastName(updateEmployee.getLastName()); 160 | } 161 | if(updateEmployee!=null && updateEmployee.getAge()!=employeeToUpdate.getAge()){ 162 | employeeToUpdate.setAge(updateEmployee.getAge()); 163 | } 164 | if (checkEmptyNullString(updateEmployee.getGender()) && !updateEmployee.getGender().equals(employeeToUpdate.getGender())) { 165 | employeeToUpdate.setGender(updateEmployee.getGender()); 166 | } 167 | if (checkEmptyNullString(updateEmployee.getRole()) && !updateEmployee.getRole().equals(employeeToUpdate.getRole())) { 168 | employeeToUpdate.setRole(updateEmployee.getRole()); 169 | } 170 | } 171 | 172 | @ApiOperation("Removes the Employee details.") 173 | @ApiResponses( 174 | value = { 175 | @ApiResponse(code = 200, message = "Employee details are successfully deleted from the DB."), 176 | @ApiResponse(code = 404, message = "No Employee found for the year that's passed."), 177 | } 178 | ) 179 | @DeleteMapping(EMPLOYEE_BY_ID_PATH_PARAM_V1) 180 | public ResponseEntity deleteMovie(@PathVariable Long id) { 181 | 182 | log.info("Received the request to delete a Employee and the id is {} .", id); 183 | Optional movieToUpdateOptional = employeeRepository.findById(id); 184 | if (movieToUpdateOptional.isPresent()) { 185 | employeeRepository.deleteById(id); 186 | log.info("Employee Successfully deleted from the DB"); 187 | return ResponseEntity.status(HttpStatus.OK).body(EmployeeConstants.DELETE_MESSAGE); 188 | } else { 189 | log.info("No Employee available for the given Movie Id - {}.", id); 190 | throw notFoundId.apply(id); 191 | } 192 | 193 | 194 | } 195 | 196 | @GetMapping(ERROR_ENDPOINT) 197 | public ResponseEntity errorEndpoint() { 198 | 199 | throw serverError.get(); 200 | } 201 | 202 | private boolean checkEmptyNullString(String input) { 203 | return !StringUtils.isEmpty(input) && !StringUtils.isEmpty(input.trim()); 204 | } 205 | 206 | } 207 | -------------------------------------------------------------------------------- /employee-service/src/main/java/com/employeeservice/entity/Employee.java: -------------------------------------------------------------------------------- 1 | package com.employeeservice.entity; 2 | 3 | import io.swagger.annotations.ApiModelProperty; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.persistence.Column; 9 | import javax.persistence.Entity; 10 | import javax.persistence.GeneratedValue; 11 | import javax.persistence.Id; 12 | import javax.validation.constraints.NotBlank; 13 | 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | @Data 17 | @Entity 18 | public class Employee { 19 | 20 | @GeneratedValue 21 | @Id 22 | @ApiModelProperty("Represents the ID which is unique to an Employee.") 23 | private Long id; 24 | 25 | @NotBlank 26 | @Column(name = "firstname") 27 | @ApiModelProperty("Represents the First Name of the Employee.") 28 | private String firstName; 29 | 30 | @NotBlank 31 | @Column(name = "lastname") 32 | @ApiModelProperty("Represents the Last Name of the Employee.") 33 | private String lastName; 34 | 35 | @ApiModelProperty("Represents the Age of the Employee.") 36 | private Integer age; 37 | 38 | @NotBlank 39 | @ApiModelProperty("Represents the gender of the Employee.") 40 | private String gender; 41 | 42 | @NotBlank 43 | @ApiModelProperty("Represents the role of the Employee.") 44 | private String role; 45 | 46 | } 47 | -------------------------------------------------------------------------------- /employee-service/src/main/java/com/employeeservice/exception/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.employeeservice.exception; 2 | 3 | import com.employeeservice.constants.EmployeeConstants; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.validation.FieldError; 8 | import org.springframework.web.bind.MethodArgumentNotValidException; 9 | import org.springframework.web.bind.annotation.ControllerAdvice; 10 | import org.springframework.web.bind.annotation.ExceptionHandler; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.stream.Collectors; 15 | 16 | @ControllerAdvice 17 | @Slf4j 18 | public class GlobalExceptionHandler { 19 | 20 | @ExceptionHandler(value = MethodArgumentNotValidException.class) 21 | public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) { 22 | 23 | List fieldError = ex.getBindingResult().getFieldErrors(); 24 | List errorFields = new ArrayList<>(); 25 | for (FieldError error : fieldError) { 26 | errorFields.add(error.getField()); 27 | } 28 | String errorMsg = EmployeeConstants.ERROR_MESSAGE + errorFields.stream().sorted().collect(Collectors.toList()).toString(); 29 | log.info("ErrorMessage : " + errorMsg); 30 | 31 | return new ResponseEntity<>(errorMsg, HttpStatus.BAD_REQUEST); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /employee-service/src/main/java/com/employeeservice/intialize/EmployeeDataInitializer.java: -------------------------------------------------------------------------------- 1 | package com.employeeservice.intialize; 2 | 3 | import com.employeeservice.entity.Employee; 4 | import com.employeeservice.repository.EmployeeRepository; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.CommandLineRunner; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.util.Arrays; 11 | import java.util.List; 12 | 13 | @Slf4j 14 | @Component 15 | public class EmployeeDataInitializer implements CommandLineRunner { 16 | 17 | @Autowired 18 | EmployeeRepository employeeRepository; 19 | 20 | @Override 21 | public void run(String... args) throws Exception { 22 | 23 | Employee employee1 = new Employee(null, "Chris", "Evans", 50, "male", "Lead Engineer"); 24 | Employee employee2 = new Employee(null, "Adam", "Sandler", 50, "male", "Senior Engineer"); 25 | Employee employee3 = new Employee(null, "Jenny", "Richards", 32, "female", "Senior Engineer"); 26 | Employee employee4 = new Employee(null, "Amy", "Adams", 44, "female", "Manager"); 27 | List employeeList = Arrays.asList(employee1,employee2,employee3,employee4); 28 | log.info("********* Employee RestFul Service Initial Data Starts *********"); 29 | employeeRepository.saveAll(employeeList); 30 | employeeRepository.findAll().forEach((employee-> log.info(""+employee))); 31 | log.info("********* Employee RestFul Service Initial Data Ends *********"); 32 | 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /employee-service/src/main/java/com/employeeservice/repository/EmployeeRepository.java: -------------------------------------------------------------------------------- 1 | package com.employeeservice.repository; 2 | 3 | import com.employeeservice.entity.Employee; 4 | import org.springframework.data.jpa.repository.Query; 5 | import org.springframework.data.repository.CrudRepository; 6 | 7 | import java.util.List; 8 | 9 | public interface EmployeeRepository extends CrudRepository { 10 | 11 | @Query("select m from Employee m where m.firstName like %?1% or m.lastName like %?1%") 12 | List findByEmployeeName(String name); 13 | } 14 | -------------------------------------------------------------------------------- /employee-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: 3 | active: dev 4 | server: 5 | servlet: 6 | context-path: /employeeservice 7 | port: 8081 8 | --- 9 | spring: 10 | profiles: dev 11 | datasource: 12 | url: jdbc:h2:mem:testdb 13 | driver-class-name: org.h2.Driver 14 | data-username: sa 15 | jpa: 16 | database: h2 17 | database-platform: org.hibernate.dialect.H2Dialect 18 | h2: 19 | console: 20 | enabled: true 21 | -------------------------------------------------------------------------------- /employee-service/src/test/java/com/employeeservice/controller/EmployeeControllerTestIT.java: -------------------------------------------------------------------------------- 1 | package com.employeeservice.controller; 2 | 3 | 4 | import com.employeeservice.constants.EmployeeConstants; 5 | import com.employeeservice.entity.Employee; 6 | import com.fasterxml.jackson.core.JsonProcessingException; 7 | import org.junit.jupiter.api.Test; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.beans.factory.annotation.Value; 10 | import org.springframework.boot.test.autoconfigure.web.client.AutoConfigureWebClient; 11 | import org.springframework.boot.test.context.SpringBootTest; 12 | import org.springframework.test.annotation.DirtiesContext; 13 | import org.springframework.test.context.ActiveProfiles; 14 | import org.springframework.test.context.jdbc.Sql; 15 | import org.springframework.test.context.jdbc.SqlGroup; 16 | import org.springframework.test.web.reactive.server.WebTestClient; 17 | import reactor.core.publisher.Mono; 18 | 19 | import java.util.List; 20 | import java.util.stream.Collectors; 21 | 22 | import static com.employeeservice.constants.EmployeeConstants.*; 23 | import static org.junit.jupiter.api.Assertions.assertEquals; 24 | 25 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 26 | @AutoConfigureWebClient 27 | @DirtiesContext 28 | @ActiveProfiles("test") 29 | @SqlGroup({ 30 | @Sql(scripts = "/data/sql/employeeSetUp.sql"), 31 | @Sql(scripts = {"/data/sql/tearDown.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) 32 | }) 33 | public class EmployeeControllerTestIT { 34 | 35 | 36 | @Autowired 37 | WebTestClient webTestClient; 38 | 39 | @Autowired 40 | EmployeeController employeeController; 41 | 42 | @Value("${server.servlet.context-path}") 43 | private String contextPath; 44 | 45 | @Test 46 | void getAllItems() { 47 | 48 | List employeeList = webTestClient.get() 49 | .uri(contextPath.concat(GET_ALL_MOVIES_V1)) 50 | .exchange() 51 | .expectStatus().isOk() 52 | .returnResult(Employee.class) 53 | .getResponseBody() 54 | .toStream().collect(Collectors.toList()); 55 | 56 | assertEquals(2, employeeList.size()); 57 | 58 | } 59 | 60 | @Test 61 | void employeeById() { 62 | 63 | Employee movie = webTestClient.get().uri(contextPath.concat(EMPLOYEE_BY_ID_PATH_PARAM_V1), 1001) 64 | .exchange() 65 | .expectStatus().isOk() 66 | .returnResult(Employee.class) 67 | .getResponseBody() 68 | .blockLast(); 69 | 70 | assertEquals("Christian", movie.getFirstName()); 71 | 72 | } 73 | 74 | @Test 75 | void employeeById_NotFound() { 76 | 77 | webTestClient.get().uri(contextPath.concat(EMPLOYEE_BY_ID_PATH_PARAM_V1), 123) 78 | .exchange() 79 | .expectStatus().isNotFound(); 80 | } 81 | 82 | @Test 83 | void employeeByFirstName() { 84 | 85 | List movies = webTestClient.get().uri(uriBuilder -> uriBuilder.path(contextPath.concat(EMPLOYEE_BY_NAME_QUERY_PARAM_V1)) 86 | .queryParam("employee_name", "Adam") 87 | .build()) 88 | .exchange() 89 | .expectStatus().isOk() 90 | .returnResult(Employee.class) 91 | .getResponseBody() 92 | .toStream().collect(Collectors.toList()); 93 | 94 | assertEquals(1, movies.size()); 95 | } 96 | 97 | @Test 98 | void employeeByLastName() { 99 | 100 | List movies = webTestClient.get().uri(uriBuilder -> uriBuilder.path(contextPath.concat(EMPLOYEE_BY_NAME_QUERY_PARAM_V1)) 101 | .queryParam("employee_name", "Sandler") 102 | .build()) 103 | .exchange() 104 | .expectStatus().isOk() 105 | .returnResult(Employee.class) 106 | .getResponseBody() 107 | .toStream().collect(Collectors.toList()); 108 | 109 | assertEquals(1, movies.size()); 110 | } 111 | 112 | @Test 113 | void employeeByLastName_NotFound() { 114 | 115 | webTestClient.get().uri(uriBuilder -> uriBuilder.path(contextPath.concat(EMPLOYEE_BY_NAME_QUERY_PARAM_V1)) 116 | .queryParam("employee_name", "ABC") 117 | .build()) 118 | .exchange() 119 | .expectStatus().isNotFound(); 120 | } 121 | 122 | @Test 123 | void createMovie() { 124 | 125 | //given 126 | Employee employee = new Employee(null, "Chris", "Evans", 50, "male", "Lead Engineer"); 127 | 128 | //when 129 | webTestClient.post().uri(contextPath.concat(ADD_EMPLOYEE_V1)) 130 | .body(Mono.just(employee), Employee.class) 131 | .exchange() 132 | .expectStatus().isCreated() 133 | .expectBody() 134 | .jsonPath("$.id").isNotEmpty(); 135 | } 136 | 137 | @Test 138 | void createMovie_Validating_Input_Data() throws JsonProcessingException { 139 | 140 | //given 141 | Employee newMovie = new Employee(null, "", null, null,"female", "Manager" ); 142 | String expectedErrorMessage = "Please pass all the input fields : [firstName, lastName]"; 143 | 144 | //when 145 | webTestClient.post().uri(contextPath.concat(ADD_EMPLOYEE_V1)) 146 | .body(Mono.just(newMovie), Employee.class) 147 | .exchange() 148 | .expectStatus().is4xxClientError() 149 | .expectBody(String.class) 150 | .isEqualTo(expectedErrorMessage); 151 | } 152 | 153 | @Test 154 | void createEmployeeEntity(){ 155 | Employee employeeToUpdate = new Employee(null, "Chris", "Evans", 50, "male", "Lead Engineer"); 156 | Employee updateEmployee = new Employee(null, "Chris1", "Evans1", 51, "male1", "Lead Engineer1"); 157 | 158 | employeeController.createEmployeeEntity(employeeToUpdate,updateEmployee); 159 | 160 | assertEquals(employeeToUpdate.getFirstName(), updateEmployee.getFirstName()); 161 | assertEquals(employeeToUpdate.getLastName(), updateEmployee.getLastName()); 162 | assertEquals(employeeToUpdate.getAge(), updateEmployee.getAge()); 163 | assertEquals(employeeToUpdate.getRole(), updateEmployee.getRole()); 164 | } 165 | 166 | @Test 167 | void updateEmployee() { 168 | 169 | //given 170 | String firstMName = "Chris1"; 171 | String lastMName = "Evans1"; 172 | Integer age = 51; 173 | String gender = "male1"; 174 | String role = "Lead Engineer1"; 175 | Employee updateEmployee = new Employee(null, firstMName, lastMName, age, gender, role); 176 | 177 | //when 178 | webTestClient.put().uri(contextPath.concat(EMPLOYEE_BY_ID_PATH_PARAM_V1), 1000) 179 | .syncBody(updateEmployee) 180 | .exchange() 181 | .expectStatus().isOk() 182 | .expectBody() 183 | .jsonPath("$.firstName").isEqualTo(firstMName) 184 | .jsonPath("$.lastName").isEqualTo(lastMName) 185 | .jsonPath("$.age").isEqualTo(age) 186 | .jsonPath("$.gender").isEqualTo(gender) 187 | .jsonPath("$.role").isEqualTo(role); 188 | 189 | 190 | webTestClient.get().uri(contextPath.concat(EMPLOYEE_BY_ID_PATH_PARAM_V1), 1000) 191 | .exchange() 192 | .expectStatus().isOk() 193 | .expectBody() 194 | .jsonPath("$.firstName").isEqualTo(firstMName) 195 | .jsonPath("$.lastName").isEqualTo(lastMName) 196 | .jsonPath("$.age").isEqualTo(age) 197 | .jsonPath("$.gender").isEqualTo(gender) 198 | .jsonPath("$.role").isEqualTo(role); 199 | } 200 | 201 | @Test 202 | void deleteMovie() { 203 | 204 | webTestClient.delete().uri(contextPath.concat(EMPLOYEE_BY_ID_PATH_PARAM_V1), 1000) 205 | .exchange() 206 | .expectStatus().isOk() 207 | .expectBody(String.class) 208 | .isEqualTo(DELETE_MESSAGE); 209 | 210 | } 211 | 212 | @Test 213 | void deleteMovie_invalidMovieId() { 214 | 215 | webTestClient.delete().uri(contextPath.concat(EMPLOYEE_BY_ID_PATH_PARAM_V1), 2000) 216 | .exchange() 217 | .expectStatus().isNotFound(); 218 | } 219 | 220 | @Test 221 | void errorEndpoint() { 222 | 223 | webTestClient.get().uri(contextPath+ERROR_ENDPOINT) 224 | .exchange() 225 | .expectStatus().is5xxServerError(); 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /employee-service/src/test/resources/data/sql/employeeSetUp.sql: -------------------------------------------------------------------------------- 1 | insert into employee(id, firstname, lastname, age, gender, role) values (1000,'Adam', 'Sandler', 42, 'male','Engineer'); 2 | insert into employee(id, firstname, lastname,age, gender, role) values (1001,'Christian', 'Bale', 53, 'male','Senior Engineer'); -------------------------------------------------------------------------------- /employee-service/src/test/resources/data/sql/tearDown.sql: -------------------------------------------------------------------------------- 1 | delete from employee; --------------------------------------------------------------------------------