diff --git a/.github/workflows/dependency-check.yml b/.github/workflows/dependency-check.yml index 9ed592d0a..f6a2dbc75 100644 --- a/.github/workflows/dependency-check.yml +++ b/.github/workflows/dependency-check.yml @@ -16,11 +16,11 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up JDK 11 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: distribution: zulu - java-version: '11' + java-version: '21' - uses: actions/cache@v4 with: diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 71571f25f..691afceba 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -17,11 +17,11 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up JDK 11 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: distribution: zulu - java-version: '11' + java-version: '21' - uses: actions/cache@v4 with: path: | diff --git a/.java-version b/.java-version index b4de39476..aabe6ec39 100644 --- a/.java-version +++ b/.java-version @@ -1 +1 @@ -11 +21 diff --git a/build.gradle b/build.gradle index 03c56f755..6749c81fa 100644 --- a/build.gradle +++ b/build.gradle @@ -1,28 +1,27 @@ plugins { - id 'org.springframework.boot' version '2.6.3' - id 'io.spring.dependency-management' version '1.0.11.RELEASE' + id 'org.springframework.boot' version '3.5.0' + id 'io.spring.dependency-management' version '1.1.7' id 'java' - id "com.netflix.dgs.codegen" version "5.0.6" - id "com.diffplug.spotless" version "6.2.1" + id "com.netflix.dgs.codegen" version "7.0.3" + id "com.diffplug.spotless" version "6.25.0" id 'jacoco' } version = '0.0.1-SNAPSHOT' -sourceCompatibility = '11' -targetCompatibility = '11' +sourceCompatibility = '21' +targetCompatibility = '21' spotless { java { target project.fileTree(project.rootDir) { - include '**/*.java' - exclude 'build/generated/**/*.*', 'build/generated-examples/**/*.*' + include 'src/**/*.java' } googleJavaFormat() } } jacoco { - toolVersion = "0.8.7" + toolVersion = "0.8.12" } jacocoTestReport { @@ -45,6 +44,8 @@ jacocoTestCoverageVerification { } } +ext['byte-buddy.version'] = '1.15.4' + repositories { mavenCentral() } @@ -53,6 +54,15 @@ configurations { compileOnly { extendsFrom annotationProcessor } + configureEach { + resolutionStrategy { + force 'com.graphql-java:java-dataloader:5.0.3' + force 'org.apache.httpcomponents.client5:httpclient5:5.4.4' + } + } + // Phase 3c classpath verification: confirmed no javax.validation:validation-api + // or other Jakarta-conflicting jars on runtimeClasspath or testRuntimeClasspath. + // All validation dependencies resolve to jakarta.validation (Spring Boot 3.x BOM). } dependencies { @@ -60,33 +70,34 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-hateoas' implementation 'org.springframework.boot:spring-boot-starter-security' - implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.2' - implementation 'com.netflix.graphql.dgs:graphql-dgs-spring-boot-starter:4.9.21' + implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3' + implementation 'com.netflix.graphql.dgs:graphql-dgs-spring-graphql-starter:10.6.0' implementation 'org.flywaydb:flyway-core' - implementation 'io.jsonwebtoken:jjwt-api:0.11.2' - runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.2', - 'io.jsonwebtoken:jjwt-jackson:0.11.2' - implementation 'joda-time:joda-time:2.10.13' - implementation 'org.xerial:sqlite-jdbc:3.36.0.3' + implementation 'io.jsonwebtoken:jjwt-api:0.12.6' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6', + 'io.jsonwebtoken:jjwt-jackson:0.12.6' + implementation 'org.xerial:sqlite-jdbc:3.46.0.0' + implementation 'org.apache.httpcomponents.client5:httpclient5' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' - testImplementation 'io.rest-assured:rest-assured:4.5.1' - testImplementation 'io.rest-assured:json-path:4.5.1' - testImplementation 'io.rest-assured:xml-path:4.5.1' - testImplementation 'io.rest-assured:spring-mock-mvc:4.5.1' + testImplementation 'io.rest-assured:rest-assured:5.4.0' + testImplementation 'io.rest-assured:json-path:5.4.0' + testImplementation 'io.rest-assured:xml-path:5.4.0' + testImplementation 'io.rest-assured:spring-mock-mvc:5.4.0' testImplementation 'org.springframework.security:spring-security-test' testImplementation 'org.springframework.boot:spring-boot-starter-test' - testImplementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter-test:2.2.2' - testImplementation 'org.mockito:mockito-inline:4.0.0' + testImplementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter-test:3.0.3' + testImplementation 'org.mockito:mockito-core:5.14.2' + testImplementation 'org.mockito:mockito-junit-jupiter:5.14.2' // Selenium E2E testing - testImplementation 'org.seleniumhq.selenium:selenium-java:4.15.0' - testImplementation 'io.github.bonigarcia:webdrivermanager:5.6.2' - testImplementation 'org.testng:testng:7.8.0' - testImplementation 'com.aventstack:extentreports:5.1.1' - testImplementation 'org.apache.httpcomponents.client5:httpclient5:5.2.1' + testImplementation 'org.seleniumhq.selenium:selenium-java:4.21.0' + testImplementation 'io.github.bonigarcia:webdrivermanager:5.8.0' + testImplementation 'org.testng:testng:7.10.2' + testImplementation 'com.aventstack:extentreports:5.1.2' + testImplementation 'org.apache.httpcomponents.client5:httpclient5:5.4.4' } tasks.named('test') { diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c02..a4b76b953 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 41dfb8790..df97d72b8 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 4f906e0c8..f5feea6d6 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,69 +15,104 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +122,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,88 +133,120 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index ac1b06f93..9b42019c7 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,8 +13,10 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +27,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,13 +43,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -56,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -75,13 +78,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/src/main/java/io/spring/JacksonCustomizations.java b/src/main/java/io/spring/JacksonCustomizations.java index 86fab0abe..c46560c71 100644 --- a/src/main/java/io/spring/JacksonCustomizations.java +++ b/src/main/java/io/spring/JacksonCustomizations.java @@ -6,8 +6,9 @@ import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.std.StdSerializer; import java.io.IOException; -import org.joda.time.DateTime; -import org.joda.time.format.ISODateTimeFormat; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -21,23 +22,26 @@ public Module realWorldModules() { public static class RealWorldModules extends SimpleModule { public RealWorldModules() { - addSerializer(DateTime.class, new DateTimeSerializer()); + addSerializer(Instant.class, new InstantSerializer()); } } - public static class DateTimeSerializer extends StdSerializer { + public static class InstantSerializer extends StdSerializer { - protected DateTimeSerializer() { - super(DateTime.class); + private static final DateTimeFormatter FORMATTER = + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").withZone(ZoneOffset.UTC); + + protected InstantSerializer() { + super(Instant.class); } @Override - public void serialize(DateTime value, JsonGenerator gen, SerializerProvider provider) + public void serialize(Instant value, JsonGenerator gen, SerializerProvider provider) throws IOException { if (value == null) { gen.writeNull(); } else { - gen.writeString(ISODateTimeFormat.dateTime().withZoneUTC().print(value)); + gen.writeString(FORMATTER.format(value)); } } } diff --git a/src/main/java/io/spring/api/ArticleApi.java b/src/main/java/io/spring/api/ArticleApi.java index c80afa31d..74b8234c4 100644 --- a/src/main/java/io/spring/api/ArticleApi.java +++ b/src/main/java/io/spring/api/ArticleApi.java @@ -10,9 +10,9 @@ import io.spring.core.article.ArticleRepository; import io.spring.core.service.AuthorizationService; import io.spring.core.user.User; +import jakarta.validation.Valid; import java.util.HashMap; import java.util.Map; -import javax.validation.Valid; import lombok.AllArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; diff --git a/src/main/java/io/spring/api/ArticlesApi.java b/src/main/java/io/spring/api/ArticlesApi.java index 50584bd6d..3f96607e9 100644 --- a/src/main/java/io/spring/api/ArticlesApi.java +++ b/src/main/java/io/spring/api/ArticlesApi.java @@ -6,8 +6,8 @@ import io.spring.application.article.NewArticleParam; import io.spring.core.article.Article; import io.spring.core.user.User; +import jakarta.validation.Valid; import java.util.HashMap; -import javax.validation.Valid; import lombok.AllArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; diff --git a/src/main/java/io/spring/api/CommentsApi.java b/src/main/java/io/spring/api/CommentsApi.java index c5f7e77e9..ff37833ec 100644 --- a/src/main/java/io/spring/api/CommentsApi.java +++ b/src/main/java/io/spring/api/CommentsApi.java @@ -11,11 +11,11 @@ import io.spring.core.comment.CommentRepository; import io.spring.core.service.AuthorizationService; import io.spring.core.user.User; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; import java.util.HashMap; import java.util.List; import java.util.Map; -import javax.validation.Valid; -import javax.validation.constraints.NotBlank; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/src/main/java/io/spring/api/CurrentUserApi.java b/src/main/java/io/spring/api/CurrentUserApi.java index e096aec0b..a2ce43eb4 100644 --- a/src/main/java/io/spring/api/CurrentUserApi.java +++ b/src/main/java/io/spring/api/CurrentUserApi.java @@ -7,9 +7,9 @@ import io.spring.application.user.UpdateUserParam; import io.spring.application.user.UserService; import io.spring.core.user.User; +import jakarta.validation.Valid; import java.util.HashMap; import java.util.Map; -import javax.validation.Valid; import lombok.AllArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; diff --git a/src/main/java/io/spring/api/UsersApi.java b/src/main/java/io/spring/api/UsersApi.java index d91321e82..71e39d4ab 100644 --- a/src/main/java/io/spring/api/UsersApi.java +++ b/src/main/java/io/spring/api/UsersApi.java @@ -12,12 +12,12 @@ import io.spring.core.service.JwtService; import io.spring.core.user.User; import io.spring.core.user.UserRepository; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; import java.util.HashMap; import java.util.Map; import java.util.Optional; -import javax.validation.Valid; -import javax.validation.constraints.Email; -import javax.validation.constraints.NotBlank; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/src/main/java/io/spring/api/exception/CustomizeExceptionHandler.java b/src/main/java/io/spring/api/exception/CustomizeExceptionHandler.java index ade3ff4ff..3e7c5c908 100644 --- a/src/main/java/io/spring/api/exception/CustomizeExceptionHandler.java +++ b/src/main/java/io/spring/api/exception/CustomizeExceptionHandler.java @@ -2,15 +2,15 @@ import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.stream.Collectors; -import javax.validation.ConstraintViolation; -import javax.validation.ConstraintViolationException; import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; @@ -63,7 +63,7 @@ public ResponseEntity handleInvalidAuthentication( protected ResponseEntity handleMethodArgumentNotValid( MethodArgumentNotValidException e, HttpHeaders headers, - HttpStatus status, + HttpStatusCode status, WebRequest request) { List errorResources = e.getBindingResult().getFieldErrors().stream() diff --git a/src/main/java/io/spring/api/security/JwtTokenFilter.java b/src/main/java/io/spring/api/security/JwtTokenFilter.java index 1b5c50146..b03e9d143 100644 --- a/src/main/java/io/spring/api/security/JwtTokenFilter.java +++ b/src/main/java/io/spring/api/security/JwtTokenFilter.java @@ -2,13 +2,13 @@ import io.spring.core.service.JwtService; import io.spring.core.user.UserRepository; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Collections; import java.util.Optional; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; diff --git a/src/main/java/io/spring/api/security/WebSecurityConfig.java b/src/main/java/io/spring/api/security/WebSecurityConfig.java index 3786959ef..a46032cd0 100644 --- a/src/main/java/io/spring/api/security/WebSecurityConfig.java +++ b/src/main/java/io/spring/api/security/WebSecurityConfig.java @@ -8,10 +8,10 @@ import org.springframework.http.HttpStatus; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.HttpStatusEntryPoint; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.cors.CorsConfiguration; @@ -20,7 +20,7 @@ @Configuration @EnableWebSecurity -public class WebSecurityConfig extends WebSecurityConfigurerAdapter { +public class WebSecurityConfig { @Bean public JwtTokenFilter jwtTokenFilter() { @@ -32,36 +32,32 @@ public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } - @Override - protected void configure(HttpSecurity http) throws Exception { - - http.csrf() - .disable() - .cors() - .and() - .exceptionHandling() - .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)) - .and() - .sessionManagement() - .sessionCreationPolicy(SessionCreationPolicy.STATELESS) - .and() - .authorizeRequests() - .antMatchers(HttpMethod.OPTIONS) - .permitAll() - .antMatchers("/graphiql") - .permitAll() - .antMatchers("/graphql") - .permitAll() - .antMatchers(HttpMethod.GET, "/articles/feed") - .authenticated() - .antMatchers(HttpMethod.POST, "/users", "/users/login") - .permitAll() - .antMatchers(HttpMethod.GET, "/articles/**", "/profiles/**", "/tags") - .permitAll() - .anyRequest() - .authenticated(); + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http.csrf(c -> c.disable()) + .cors(cors -> cors.configurationSource(corsConfigurationSource())) + .exceptionHandling( + e -> e.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))) + .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests( + auth -> + auth.requestMatchers(HttpMethod.OPTIONS) + .permitAll() + .requestMatchers("/graphiql") + .permitAll() + .requestMatchers("/graphql") + .permitAll() + .requestMatchers(HttpMethod.GET, "/articles/feed") + .authenticated() + .requestMatchers(HttpMethod.POST, "/users", "/users/login") + .permitAll() + .requestMatchers(HttpMethod.GET, "/articles/**", "/profiles/**", "/tags") + .permitAll() + .anyRequest() + .authenticated()); http.addFilterBefore(jwtTokenFilter(), UsernamePasswordAuthenticationFilter.class); + return http.build(); } @Bean @@ -69,12 +65,7 @@ public CorsConfigurationSource corsConfigurationSource() { final CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(asList("*")); configuration.setAllowedMethods(asList("HEAD", "GET", "POST", "PUT", "DELETE", "PATCH")); - // setAllowCredentials(true) is important, otherwise: - // The value of the 'Access-Control-Allow-Origin' header in the response must not be the - // wildcard '*' when the request's credentials mode is 'include'. configuration.setAllowCredentials(false); - // setAllowedHeaders is important! Without it, OPTIONS preflight request - // will fail with 403 Invalid CORS request configuration.setAllowedHeaders(asList("Authorization", "Cache-Control", "Content-Type")); final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); diff --git a/src/main/java/io/spring/application/ArticleQueryService.java b/src/main/java/io/spring/application/ArticleQueryService.java index 959e8c638..9e4f41bdc 100644 --- a/src/main/java/io/spring/application/ArticleQueryService.java +++ b/src/main/java/io/spring/application/ArticleQueryService.java @@ -9,6 +9,7 @@ import io.spring.infrastructure.mybatis.readservice.ArticleFavoritesReadService; import io.spring.infrastructure.mybatis.readservice.ArticleReadService; import io.spring.infrastructure.mybatis.readservice.UserRelationshipQueryService; +import java.time.Instant; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -17,7 +18,6 @@ import java.util.Optional; import java.util.Set; import lombok.AllArgsConstructor; -import org.joda.time.DateTime; import org.springframework.stereotype.Service; @Service @@ -55,7 +55,7 @@ public CursorPager findRecentArticlesWithCursor( String tag, String author, String favoritedBy, - CursorPageParameter page, + CursorPageParameter page, User currentUser) { List articleIds = articleReadService.findArticlesWithCursor(tag, author, favoritedBy, page); @@ -78,7 +78,7 @@ public CursorPager findRecentArticlesWithCursor( } public CursorPager findUserFeedWithCursor( - User user, CursorPageParameter page) { + User user, CursorPageParameter page) { List followdUsers = userRelationshipQueryService.followedUsers(user.getId()); if (followdUsers.size() == 0) { return new CursorPager<>(new ArrayList<>(), page.getDirection(), false); diff --git a/src/main/java/io/spring/application/CommentQueryService.java b/src/main/java/io/spring/application/CommentQueryService.java index da1677f4c..974ffabc8 100644 --- a/src/main/java/io/spring/application/CommentQueryService.java +++ b/src/main/java/io/spring/application/CommentQueryService.java @@ -4,6 +4,7 @@ import io.spring.core.user.User; import io.spring.infrastructure.mybatis.readservice.CommentReadService; import io.spring.infrastructure.mybatis.readservice.UserRelationshipQueryService; +import java.time.Instant; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -11,7 +12,6 @@ import java.util.Set; import java.util.stream.Collectors; import lombok.AllArgsConstructor; -import org.joda.time.DateTime; import org.springframework.stereotype.Service; @Service @@ -54,7 +54,7 @@ public List findByArticleId(String articleId, User user) { } public CursorPager findByArticleIdWithCursor( - String articleId, User user, CursorPageParameter page) { + String articleId, User user, CursorPageParameter page) { List comments = commentReadService.findByArticleIdWithCursor(articleId, page); if (comments.isEmpty()) { return new CursorPager<>(new ArrayList<>(), page.getDirection(), false); diff --git a/src/main/java/io/spring/application/DateTimeCursor.java b/src/main/java/io/spring/application/DateTimeCursor.java index cfcc86bc8..cf090af19 100644 --- a/src/main/java/io/spring/application/DateTimeCursor.java +++ b/src/main/java/io/spring/application/DateTimeCursor.java @@ -1,23 +1,22 @@ package io.spring.application; -import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; +import java.time.Instant; -public class DateTimeCursor extends PageCursor { +public class DateTimeCursor extends PageCursor { - public DateTimeCursor(DateTime data) { + public DateTimeCursor(Instant data) { super(data); } @Override public String toString() { - return String.valueOf(getData().getMillis()); + return String.valueOf(getData().toEpochMilli()); } - public static DateTime parse(String cursor) { + public static Instant parse(String cursor) { if (cursor == null) { return null; } - return new DateTime().withMillis(Long.parseLong(cursor)).withZone(DateTimeZone.UTC); + return Instant.ofEpochMilli(Long.parseLong(cursor)); } } diff --git a/src/main/java/io/spring/application/article/ArticleCommandService.java b/src/main/java/io/spring/application/article/ArticleCommandService.java index 861bc22b2..2d88441b7 100644 --- a/src/main/java/io/spring/application/article/ArticleCommandService.java +++ b/src/main/java/io/spring/application/article/ArticleCommandService.java @@ -3,7 +3,7 @@ import io.spring.core.article.Article; import io.spring.core.article.ArticleRepository; import io.spring.core.user.User; -import javax.validation.Valid; +import jakarta.validation.Valid; import lombok.AllArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; diff --git a/src/main/java/io/spring/application/article/DuplicatedArticleConstraint.java b/src/main/java/io/spring/application/article/DuplicatedArticleConstraint.java index 0eb1eda84..a18d47cac 100644 --- a/src/main/java/io/spring/application/article/DuplicatedArticleConstraint.java +++ b/src/main/java/io/spring/application/article/DuplicatedArticleConstraint.java @@ -1,12 +1,12 @@ package io.spring.application.article; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import javax.validation.Constraint; -import javax.validation.Payload; @Documented @Constraint(validatedBy = DuplicatedArticleValidator.class) diff --git a/src/main/java/io/spring/application/article/DuplicatedArticleValidator.java b/src/main/java/io/spring/application/article/DuplicatedArticleValidator.java index 0e3828e1e..e04853f26 100644 --- a/src/main/java/io/spring/application/article/DuplicatedArticleValidator.java +++ b/src/main/java/io/spring/application/article/DuplicatedArticleValidator.java @@ -2,8 +2,8 @@ import io.spring.application.ArticleQueryService; import io.spring.core.article.Article; -import javax.validation.ConstraintValidator; -import javax.validation.ConstraintValidatorContext; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; import org.springframework.beans.factory.annotation.Autowired; class DuplicatedArticleValidator diff --git a/src/main/java/io/spring/application/article/NewArticleParam.java b/src/main/java/io/spring/application/article/NewArticleParam.java index 344d76c33..35586645d 100644 --- a/src/main/java/io/spring/application/article/NewArticleParam.java +++ b/src/main/java/io/spring/application/article/NewArticleParam.java @@ -1,8 +1,8 @@ package io.spring.application.article; import com.fasterxml.jackson.annotation.JsonRootName; +import jakarta.validation.constraints.NotBlank; import java.util.List; -import javax.validation.constraints.NotBlank; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/io/spring/application/data/ArticleData.java b/src/main/java/io/spring/application/data/ArticleData.java index 3d3c947e2..43fd08a72 100644 --- a/src/main/java/io/spring/application/data/ArticleData.java +++ b/src/main/java/io/spring/application/data/ArticleData.java @@ -2,11 +2,11 @@ import com.fasterxml.jackson.annotation.JsonProperty; import io.spring.application.DateTimeCursor; +import java.time.Instant; import java.util.List; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; -import org.joda.time.DateTime; @Data @NoArgsConstructor @@ -19,8 +19,8 @@ public class ArticleData implements io.spring.application.Node { private String body; private boolean favorited; private int favoritesCount; - private DateTime createdAt; - private DateTime updatedAt; + private Instant createdAt; + private Instant updatedAt; private List tagList; @JsonProperty("author") diff --git a/src/main/java/io/spring/application/data/CommentData.java b/src/main/java/io/spring/application/data/CommentData.java index 1e28d94bd..96919485f 100644 --- a/src/main/java/io/spring/application/data/CommentData.java +++ b/src/main/java/io/spring/application/data/CommentData.java @@ -4,10 +4,10 @@ import com.fasterxml.jackson.annotation.JsonProperty; import io.spring.application.DateTimeCursor; import io.spring.application.Node; +import java.time.Instant; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; -import org.joda.time.DateTime; @Data @NoArgsConstructor @@ -16,8 +16,8 @@ public class CommentData implements Node { private String id; private String body; @JsonIgnore private String articleId; - private DateTime createdAt; - private DateTime updatedAt; + private Instant createdAt; + private Instant updatedAt; @JsonProperty("author") private ProfileData profileData; diff --git a/src/main/java/io/spring/application/user/DuplicatedEmailConstraint.java b/src/main/java/io/spring/application/user/DuplicatedEmailConstraint.java index e41eb009e..8e0f3d53f 100644 --- a/src/main/java/io/spring/application/user/DuplicatedEmailConstraint.java +++ b/src/main/java/io/spring/application/user/DuplicatedEmailConstraint.java @@ -1,9 +1,9 @@ package io.spring.application.user; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import javax.validation.Constraint; -import javax.validation.Payload; @Constraint(validatedBy = DuplicatedEmailValidator.class) @Retention(RetentionPolicy.RUNTIME) diff --git a/src/main/java/io/spring/application/user/DuplicatedEmailValidator.java b/src/main/java/io/spring/application/user/DuplicatedEmailValidator.java index e30711465..f7543976e 100644 --- a/src/main/java/io/spring/application/user/DuplicatedEmailValidator.java +++ b/src/main/java/io/spring/application/user/DuplicatedEmailValidator.java @@ -1,8 +1,8 @@ package io.spring.application.user; import io.spring.core.user.UserRepository; -import javax.validation.ConstraintValidator; -import javax.validation.ConstraintValidatorContext; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; import org.springframework.beans.factory.annotation.Autowired; public class DuplicatedEmailValidator diff --git a/src/main/java/io/spring/application/user/DuplicatedUsernameConstraint.java b/src/main/java/io/spring/application/user/DuplicatedUsernameConstraint.java index 4f365b789..0e5498804 100644 --- a/src/main/java/io/spring/application/user/DuplicatedUsernameConstraint.java +++ b/src/main/java/io/spring/application/user/DuplicatedUsernameConstraint.java @@ -1,9 +1,9 @@ package io.spring.application.user; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import javax.validation.Constraint; -import javax.validation.Payload; @Constraint(validatedBy = DuplicatedUsernameValidator.class) @Retention(RetentionPolicy.RUNTIME) diff --git a/src/main/java/io/spring/application/user/DuplicatedUsernameValidator.java b/src/main/java/io/spring/application/user/DuplicatedUsernameValidator.java index ae1fd21aa..4b803bf85 100644 --- a/src/main/java/io/spring/application/user/DuplicatedUsernameValidator.java +++ b/src/main/java/io/spring/application/user/DuplicatedUsernameValidator.java @@ -1,8 +1,8 @@ package io.spring.application.user; import io.spring.core.user.UserRepository; -import javax.validation.ConstraintValidator; -import javax.validation.ConstraintValidatorContext; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; import org.springframework.beans.factory.annotation.Autowired; class DuplicatedUsernameValidator diff --git a/src/main/java/io/spring/application/user/RegisterParam.java b/src/main/java/io/spring/application/user/RegisterParam.java index 3ba1234d3..d629268c6 100644 --- a/src/main/java/io/spring/application/user/RegisterParam.java +++ b/src/main/java/io/spring/application/user/RegisterParam.java @@ -1,8 +1,8 @@ package io.spring.application.user; import com.fasterxml.jackson.annotation.JsonRootName; -import javax.validation.constraints.Email; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/src/main/java/io/spring/application/user/UpdateUserParam.java b/src/main/java/io/spring/application/user/UpdateUserParam.java index 54cd77471..f0263a0bf 100644 --- a/src/main/java/io/spring/application/user/UpdateUserParam.java +++ b/src/main/java/io/spring/application/user/UpdateUserParam.java @@ -1,7 +1,7 @@ package io.spring.application.user; import com.fasterxml.jackson.annotation.JsonRootName; -import javax.validation.constraints.Email; +import jakarta.validation.constraints.Email; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/io/spring/application/user/UserService.java b/src/main/java/io/spring/application/user/UserService.java index 48c6735b8..7772b32aa 100644 --- a/src/main/java/io/spring/application/user/UserService.java +++ b/src/main/java/io/spring/application/user/UserService.java @@ -2,12 +2,12 @@ import io.spring.core.user.User; import io.spring.core.user.UserRepository; +import jakarta.validation.Constraint; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import jakarta.validation.Valid; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import javax.validation.Constraint; -import javax.validation.ConstraintValidator; -import javax.validation.ConstraintValidatorContext; -import javax.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.crypto.password.PasswordEncoder; diff --git a/src/main/java/io/spring/core/article/Article.java b/src/main/java/io/spring/core/article/Article.java index f23c2c6d5..e909fb212 100644 --- a/src/main/java/io/spring/core/article/Article.java +++ b/src/main/java/io/spring/core/article/Article.java @@ -3,13 +3,13 @@ import static java.util.stream.Collectors.toList; import io.spring.Util; +import java.time.Instant; import java.util.HashSet; import java.util.List; import java.util.UUID; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; -import org.joda.time.DateTime; @Getter @NoArgsConstructor @@ -22,12 +22,12 @@ public class Article { private String description; private String body; private List tags; - private DateTime createdAt; - private DateTime updatedAt; + private Instant createdAt; + private Instant updatedAt; public Article( String title, String description, String body, List tagList, String userId) { - this(title, description, body, tagList, userId, new DateTime()); + this(title, description, body, tagList, userId, Instant.now()); } public Article( @@ -36,7 +36,7 @@ public Article( String body, List tagList, String userId, - DateTime createdAt) { + Instant createdAt) { this.id = UUID.randomUUID().toString(); this.slug = toSlug(title); this.title = title; @@ -52,15 +52,15 @@ public void update(String title, String description, String body) { if (!Util.isEmpty(title)) { this.title = title; this.slug = toSlug(title); - this.updatedAt = new DateTime(); + this.updatedAt = Instant.now(); } if (!Util.isEmpty(description)) { this.description = description; - this.updatedAt = new DateTime(); + this.updatedAt = Instant.now(); } if (!Util.isEmpty(body)) { this.body = body; - this.updatedAt = new DateTime(); + this.updatedAt = Instant.now(); } } diff --git a/src/main/java/io/spring/core/comment/Comment.java b/src/main/java/io/spring/core/comment/Comment.java index 5b9fbe7d8..9da47d05e 100644 --- a/src/main/java/io/spring/core/comment/Comment.java +++ b/src/main/java/io/spring/core/comment/Comment.java @@ -1,10 +1,10 @@ package io.spring.core.comment; +import java.time.Instant; import java.util.UUID; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; -import org.joda.time.DateTime; @Getter @NoArgsConstructor @@ -14,13 +14,13 @@ public class Comment { private String body; private String userId; private String articleId; - private DateTime createdAt; + private Instant createdAt; public Comment(String body, String userId, String articleId) { this.id = UUID.randomUUID().toString(); this.body = body; this.userId = userId; this.articleId = articleId; - this.createdAt = new DateTime(); + this.createdAt = Instant.now(); } } diff --git a/src/main/java/io/spring/graphql/ArticleDatafetcher.java b/src/main/java/io/spring/graphql/ArticleDatafetcher.java index 37c82939a..abeae8e3f 100644 --- a/src/main/java/io/spring/graphql/ArticleDatafetcher.java +++ b/src/main/java/io/spring/graphql/ArticleDatafetcher.java @@ -6,8 +6,6 @@ import com.netflix.graphql.dgs.DgsQuery; import com.netflix.graphql.dgs.InputArgument; import graphql.execution.DataFetcherResult; -import graphql.relay.DefaultConnectionCursor; -import graphql.relay.DefaultPageInfo; import graphql.schema.DataFetchingEnvironment; import io.spring.api.exception.ResourceNotFoundException; import io.spring.application.ArticleQueryService; @@ -26,11 +24,13 @@ import io.spring.graphql.types.Article; import io.spring.graphql.types.ArticleEdge; import io.spring.graphql.types.ArticlesConnection; +import io.spring.graphql.types.PageInfo; import io.spring.graphql.types.Profile; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; import java.util.HashMap; import java.util.stream.Collectors; import lombok.AllArgsConstructor; -import org.joda.time.format.ISODateTimeFormat; @DgsComponent @AllArgsConstructor @@ -64,7 +64,7 @@ public DataFetcherResult getFeed( current, new CursorPageParameter<>(DateTimeCursor.parse(before), last, Direction.PREV)); } - graphql.relay.PageInfo pageInfo = buildArticlePageInfo(articles); + PageInfo pageInfo = buildArticlePageInfo(articles); ArticlesConnection articlesConnection = ArticlesConnection.newBuilder() .pageInfo(pageInfo) @@ -114,7 +114,7 @@ public DataFetcherResult userFeed( target, new CursorPageParameter<>(DateTimeCursor.parse(before), last, Direction.PREV)); } - graphql.relay.PageInfo pageInfo = buildArticlePageInfo(articles); + PageInfo pageInfo = buildArticlePageInfo(articles); ArticlesConnection articlesConnection = ArticlesConnection.newBuilder() .pageInfo(pageInfo) @@ -167,7 +167,7 @@ public DataFetcherResult userFavorites( new CursorPageParameter<>(DateTimeCursor.parse(before), last, Direction.PREV), current); } - graphql.relay.PageInfo pageInfo = buildArticlePageInfo(articles); + PageInfo pageInfo = buildArticlePageInfo(articles); ArticlesConnection articlesConnection = ArticlesConnection.newBuilder() @@ -221,7 +221,7 @@ public DataFetcherResult userArticles( new CursorPageParameter<>(DateTimeCursor.parse(before), last, Direction.PREV), current); } - graphql.relay.PageInfo pageInfo = buildArticlePageInfo(articles); + PageInfo pageInfo = buildArticlePageInfo(articles); ArticlesConnection articlesConnection = ArticlesConnection.newBuilder() .pageInfo(pageInfo) @@ -276,7 +276,7 @@ public DataFetcherResult getArticles( new CursorPageParameter<>(DateTimeCursor.parse(before), last, Direction.PREV), current); } - graphql.relay.PageInfo pageInfo = buildArticlePageInfo(articles); + PageInfo pageInfo = buildArticlePageInfo(articles); ArticlesConnection articlesConnection = ArticlesConnection.newBuilder() .pageInfo(pageInfo) @@ -356,29 +356,33 @@ public DataFetcherResult
findArticleBySlug(@InputArgument("slug") Strin .build(); } - private DefaultPageInfo buildArticlePageInfo(CursorPager articles) { - return new DefaultPageInfo( - articles.getStartCursor() == null - ? null - : new DefaultConnectionCursor(articles.getStartCursor().toString()), - articles.getEndCursor() == null - ? null - : new DefaultConnectionCursor(articles.getEndCursor().toString()), - articles.hasPrevious(), - articles.hasNext()); + private PageInfo buildArticlePageInfo(CursorPager articles) { + return PageInfo.newBuilder() + .startCursor( + articles.getStartCursor() == null ? null : articles.getStartCursor().toString()) + .endCursor(articles.getEndCursor() == null ? null : articles.getEndCursor().toString()) + .hasPreviousPage(articles.hasPrevious()) + .hasNextPage(articles.hasNext()) + .build(); } private Article buildArticleResult(ArticleData articleData) { return Article.newBuilder() .body(articleData.getBody()) - .createdAt(ISODateTimeFormat.dateTime().withZoneUTC().print(articleData.getCreatedAt())) + .createdAt( + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") + .withZone(ZoneOffset.UTC) + .format(articleData.getCreatedAt())) .description(articleData.getDescription()) .favorited(articleData.isFavorited()) .favoritesCount(articleData.getFavoritesCount()) .slug(articleData.getSlug()) .tagList(articleData.getTagList()) .title(articleData.getTitle()) - .updatedAt(ISODateTimeFormat.dateTime().withZoneUTC().print(articleData.getUpdatedAt())) + .updatedAt( + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") + .withZone(ZoneOffset.UTC) + .format(articleData.getUpdatedAt())) .build(); } } diff --git a/src/main/java/io/spring/graphql/CommentDatafetcher.java b/src/main/java/io/spring/graphql/CommentDatafetcher.java index 334a04c36..7b12d0680 100644 --- a/src/main/java/io/spring/graphql/CommentDatafetcher.java +++ b/src/main/java/io/spring/graphql/CommentDatafetcher.java @@ -5,8 +5,6 @@ import com.netflix.graphql.dgs.DgsDataFetchingEnvironment; import com.netflix.graphql.dgs.InputArgument; import graphql.execution.DataFetcherResult; -import graphql.relay.DefaultConnectionCursor; -import graphql.relay.DefaultPageInfo; import io.spring.application.CommentQueryService; import io.spring.application.CursorPageParameter; import io.spring.application.CursorPager; @@ -21,11 +19,13 @@ import io.spring.graphql.types.Comment; import io.spring.graphql.types.CommentEdge; import io.spring.graphql.types.CommentsConnection; +import io.spring.graphql.types.PageInfo; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; import lombok.AllArgsConstructor; -import org.joda.time.format.ISODateTimeFormat; @DgsComponent @AllArgsConstructor @@ -78,7 +78,7 @@ public DataFetcherResult articleComments( current, new CursorPageParameter<>(DateTimeCursor.parse(before), last, Direction.PREV)); } - graphql.relay.PageInfo pageInfo = buildCommentPageInfo(comments); + PageInfo pageInfo = buildCommentPageInfo(comments); CommentsConnection result = CommentsConnection.newBuilder() .pageInfo(pageInfo) @@ -99,24 +99,28 @@ public DataFetcherResult articleComments( .build(); } - private DefaultPageInfo buildCommentPageInfo(CursorPager comments) { - return new DefaultPageInfo( - comments.getStartCursor() == null - ? null - : new DefaultConnectionCursor(comments.getStartCursor().toString()), - comments.getEndCursor() == null - ? null - : new DefaultConnectionCursor(comments.getEndCursor().toString()), - comments.hasPrevious(), - comments.hasNext()); + private PageInfo buildCommentPageInfo(CursorPager comments) { + return PageInfo.newBuilder() + .startCursor( + comments.getStartCursor() == null ? null : comments.getStartCursor().toString()) + .endCursor(comments.getEndCursor() == null ? null : comments.getEndCursor().toString()) + .hasPreviousPage(comments.hasPrevious()) + .hasNextPage(comments.hasNext()) + .build(); } private Comment buildCommentResult(CommentData comment) { return Comment.newBuilder() .id(comment.getId()) .body(comment.getBody()) - .updatedAt(ISODateTimeFormat.dateTime().withZoneUTC().print(comment.getCreatedAt())) - .createdAt(ISODateTimeFormat.dateTime().withZoneUTC().print(comment.getCreatedAt())) + .updatedAt( + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") + .withZone(ZoneOffset.UTC) + .format(comment.getCreatedAt())) + .createdAt( + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") + .withZone(ZoneOffset.UTC) + .format(comment.getCreatedAt())) .build(); } } diff --git a/src/main/java/io/spring/graphql/UserMutation.java b/src/main/java/io/spring/graphql/UserMutation.java index 581a5b7b5..216c50f9f 100644 --- a/src/main/java/io/spring/graphql/UserMutation.java +++ b/src/main/java/io/spring/graphql/UserMutation.java @@ -17,8 +17,8 @@ import io.spring.graphql.types.UpdateUserInput; import io.spring.graphql.types.UserPayload; import io.spring.graphql.types.UserResult; +import jakarta.validation.ConstraintViolationException; import java.util.Optional; -import javax.validation.ConstraintViolationException; import lombok.AllArgsConstructor; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.core.Authentication; diff --git a/src/main/java/io/spring/graphql/exception/GraphQLCustomizeExceptionHandler.java b/src/main/java/io/spring/graphql/exception/GraphQLCustomizeExceptionHandler.java index bf4768b3b..a252a7261 100644 --- a/src/main/java/io/spring/graphql/exception/GraphQLCustomizeExceptionHandler.java +++ b/src/main/java/io/spring/graphql/exception/GraphQLCustomizeExceptionHandler.java @@ -11,14 +11,15 @@ import io.spring.api.exception.InvalidAuthenticationException; import io.spring.graphql.types.Error; import io.spring.graphql.types.ErrorItem; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; -import javax.validation.ConstraintViolation; -import javax.validation.ConstraintViolationException; import org.springframework.stereotype.Component; @Component @@ -28,7 +29,7 @@ public class GraphQLCustomizeExceptionHandler implements DataFetcherExceptionHan new DefaultDataFetcherExceptionHandler(); @Override - public DataFetcherExceptionHandlerResult onException( + public CompletableFuture handleException( DataFetcherExceptionHandlerParameters handlerParameters) { if (handlerParameters.getException() instanceof InvalidAuthenticationException) { GraphQLError graphqlError = @@ -37,7 +38,8 @@ public DataFetcherExceptionHandlerResult onException( .message(handlerParameters.getException().getMessage()) .path(handlerParameters.getPath()) .build(); - return DataFetcherExceptionHandlerResult.newResult().error(graphqlError).build(); + return CompletableFuture.completedFuture( + DataFetcherExceptionHandlerResult.newResult().error(graphqlError).build()); } else if (handlerParameters.getException() instanceof ConstraintViolationException) { List errors = new ArrayList<>(); for (ConstraintViolation violation : @@ -61,9 +63,10 @@ public DataFetcherExceptionHandlerResult onException( .path(handlerParameters.getPath()) .extensions(errorsToMap(errors)) .build(); - return DataFetcherExceptionHandlerResult.newResult().error(graphqlError).build(); + return CompletableFuture.completedFuture( + DataFetcherExceptionHandlerResult.newResult().error(graphqlError).build()); } else { - return defaultHandler.onException(handlerParameters); + return defaultHandler.handleException(handlerParameters); } } diff --git a/src/main/java/io/spring/infrastructure/mybatis/DateTimeHandler.java b/src/main/java/io/spring/infrastructure/mybatis/DateTimeHandler.java index 19323e565..cfdef67c0 100644 --- a/src/main/java/io/spring/infrastructure/mybatis/DateTimeHandler.java +++ b/src/main/java/io/spring/infrastructure/mybatis/DateTimeHandler.java @@ -5,40 +5,39 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; +import java.time.Instant; import java.util.Calendar; import java.util.TimeZone; import org.apache.ibatis.type.JdbcType; import org.apache.ibatis.type.MappedTypes; import org.apache.ibatis.type.TypeHandler; -import org.joda.time.DateTime; -@MappedTypes(DateTime.class) -public class DateTimeHandler implements TypeHandler { +@MappedTypes(Instant.class) +public class DateTimeHandler implements TypeHandler { private static final Calendar UTC_CALENDAR = Calendar.getInstance(TimeZone.getTimeZone("UTC")); @Override - public void setParameter(PreparedStatement ps, int i, DateTime parameter, JdbcType jdbcType) + public void setParameter(PreparedStatement ps, int i, Instant parameter, JdbcType jdbcType) throws SQLException { - ps.setTimestamp( - i, parameter != null ? new Timestamp(parameter.getMillis()) : null, UTC_CALENDAR); + ps.setTimestamp(i, parameter != null ? Timestamp.from(parameter) : null, UTC_CALENDAR); } @Override - public DateTime getResult(ResultSet rs, String columnName) throws SQLException { + public Instant getResult(ResultSet rs, String columnName) throws SQLException { Timestamp timestamp = rs.getTimestamp(columnName, UTC_CALENDAR); - return timestamp != null ? new DateTime(timestamp.getTime()) : null; + return timestamp != null ? timestamp.toInstant() : null; } @Override - public DateTime getResult(ResultSet rs, int columnIndex) throws SQLException { + public Instant getResult(ResultSet rs, int columnIndex) throws SQLException { Timestamp timestamp = rs.getTimestamp(columnIndex, UTC_CALENDAR); - return timestamp != null ? new DateTime(timestamp.getTime()) : null; + return timestamp != null ? timestamp.toInstant() : null; } @Override - public DateTime getResult(CallableStatement cs, int columnIndex) throws SQLException { + public Instant getResult(CallableStatement cs, int columnIndex) throws SQLException { Timestamp ts = cs.getTimestamp(columnIndex, UTC_CALENDAR); - return ts != null ? new DateTime(ts.getTime()) : null; + return ts != null ? ts.toInstant() : null; } } diff --git a/src/main/java/io/spring/infrastructure/mybatis/readservice/CommentReadService.java b/src/main/java/io/spring/infrastructure/mybatis/readservice/CommentReadService.java index 1f7f1c159..88a78fbfe 100644 --- a/src/main/java/io/spring/infrastructure/mybatis/readservice/CommentReadService.java +++ b/src/main/java/io/spring/infrastructure/mybatis/readservice/CommentReadService.java @@ -2,10 +2,10 @@ import io.spring.application.CursorPageParameter; import io.spring.application.data.CommentData; +import java.time.Instant; import java.util.List; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; -import org.joda.time.DateTime; @Mapper public interface CommentReadService { @@ -14,5 +14,5 @@ public interface CommentReadService { List findByArticleId(@Param("articleId") String articleId); List findByArticleIdWithCursor( - @Param("articleId") String articleId, @Param("page") CursorPageParameter page); + @Param("articleId") String articleId, @Param("page") CursorPageParameter page); } diff --git a/src/main/java/io/spring/infrastructure/service/DefaultJwtService.java b/src/main/java/io/spring/infrastructure/service/DefaultJwtService.java index 515d66106..3f67f9f44 100644 --- a/src/main/java/io/spring/infrastructure/service/DefaultJwtService.java +++ b/src/main/java/io/spring/infrastructure/service/DefaultJwtService.java @@ -3,7 +3,6 @@ import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jws; import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; import io.spring.core.service.JwtService; import io.spring.core.user.User; import java.util.Date; @@ -17,32 +16,29 @@ @Component public class DefaultJwtService implements JwtService { private final SecretKey signingKey; - private final SignatureAlgorithm signatureAlgorithm; private int sessionTime; @Autowired public DefaultJwtService( @Value("${jwt.secret}") String secret, @Value("${jwt.sessionTime}") int sessionTime) { this.sessionTime = sessionTime; - signatureAlgorithm = SignatureAlgorithm.HS512; - this.signingKey = new SecretKeySpec(secret.getBytes(), signatureAlgorithm.getJcaName()); + this.signingKey = new SecretKeySpec(secret.getBytes(), "HmacSHA512"); } @Override public String toToken(User user) { return Jwts.builder() - .setSubject(user.getId()) - .setExpiration(expireTimeFromNow()) - .signWith(signingKey) + .subject(user.getId()) + .expiration(expireTimeFromNow()) + .signWith(signingKey, Jwts.SIG.HS512) .compact(); } @Override public Optional getSubFromToken(String token) { try { - Jws claimsJws = - Jwts.parserBuilder().setSigningKey(signingKey).build().parseClaimsJws(token); - return Optional.ofNullable(claimsJws.getBody().getSubject()); + Jws claimsJws = Jwts.parser().verifyWith(signingKey).build().parseSignedClaims(token); + return Optional.ofNullable(claimsJws.getPayload().getSubject()); } catch (Exception e) { return Optional.empty(); } diff --git a/src/test/java/io/spring/TestHelper.java b/src/test/java/io/spring/TestHelper.java index dcd57071c..3387eee6b 100644 --- a/src/test/java/io/spring/TestHelper.java +++ b/src/test/java/io/spring/TestHelper.java @@ -4,13 +4,13 @@ import io.spring.application.data.ProfileData; import io.spring.core.article.Article; import io.spring.core.user.User; +import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; -import org.joda.time.DateTime; public class TestHelper { public static ArticleData articleDataFixture(String seed, User user) { - DateTime now = new DateTime(); + Instant now = Instant.now(); return new ArticleData( seed + "id", "title-" + seed, diff --git a/src/test/java/io/spring/api/ArticleApiTest.java b/src/test/java/io/spring/api/ArticleApiTest.java index df2ebe755..3d887a5ae 100644 --- a/src/test/java/io/spring/api/ArticleApiTest.java +++ b/src/test/java/io/spring/api/ArticleApiTest.java @@ -19,19 +19,20 @@ import io.spring.core.article.Article; import io.spring.core.article.ArticleRepository; import io.spring.core.user.User; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; -import org.joda.time.DateTime; -import org.joda.time.format.ISODateTimeFormat; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; @WebMvcTest({ArticleApi.class}) @@ -39,11 +40,11 @@ public class ArticleApiTest extends TestWithCurrentUser { @Autowired private MockMvc mvc; - @MockBean private ArticleQueryService articleQueryService; + @MockitoBean private ArticleQueryService articleQueryService; - @MockBean private ArticleRepository articleRepository; + @MockitoBean private ArticleRepository articleRepository; - @MockBean ArticleCommandService articleCommandService; + @MockitoBean ArticleCommandService articleCommandService; @Override @BeforeEach @@ -55,7 +56,7 @@ public void setUp() throws Exception { @Test public void should_read_article_success() throws Exception { String slug = "test-new-article"; - DateTime time = new DateTime(); + Instant time = Instant.now(); Article article = new Article( "Test New Article", @@ -74,7 +75,12 @@ public void should_read_article_success() throws Exception { .statusCode(200) .body("article.slug", equalTo(slug)) .body("article.body", equalTo(articleData.getBody())) - .body("article.createdAt", equalTo(ISODateTimeFormat.dateTime().withZoneUTC().print(time))); + .body( + "article.createdAt", + equalTo( + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") + .withZone(ZoneOffset.UTC) + .format(time))); } @Test @@ -131,7 +137,7 @@ public void should_get_403_if_not_author_to_update_article() throws Exception { new Article( title, description, body, Arrays.asList("java", "spring", "jpg"), anotherUser.getId()); - DateTime time = new DateTime(); + Instant time = Instant.now(); ArticleData articleData = new ArticleData( article.getId(), diff --git a/src/test/java/io/spring/api/ArticleFavoriteApiTest.java b/src/test/java/io/spring/api/ArticleFavoriteApiTest.java index 7a609a255..b13936c04 100644 --- a/src/test/java/io/spring/api/ArticleFavoriteApiTest.java +++ b/src/test/java/io/spring/api/ArticleFavoriteApiTest.java @@ -26,8 +26,8 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; @WebMvcTest(ArticleFavoriteApi.class) @@ -35,11 +35,11 @@ public class ArticleFavoriteApiTest extends TestWithCurrentUser { @Autowired private MockMvc mvc; - @MockBean private ArticleFavoriteRepository articleFavoriteRepository; + @MockitoBean private ArticleFavoriteRepository articleFavoriteRepository; - @MockBean private ArticleRepository articleRepository; + @MockitoBean private ArticleRepository articleRepository; - @MockBean private ArticleQueryService articleQueryService; + @MockitoBean private ArticleQueryService articleQueryService; private Article article; diff --git a/src/test/java/io/spring/api/ArticlesApiTest.java b/src/test/java/io/spring/api/ArticlesApiTest.java index 18948417d..5462df028 100644 --- a/src/test/java/io/spring/api/ArticlesApiTest.java +++ b/src/test/java/io/spring/api/ArticlesApiTest.java @@ -16,17 +16,17 @@ import io.spring.application.data.ArticleData; import io.spring.application.data.ProfileData; import io.spring.core.article.Article; +import java.time.Instant; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; -import org.joda.time.DateTime; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; @WebMvcTest({ArticlesApi.class}) @@ -34,9 +34,9 @@ public class ArticlesApiTest extends TestWithCurrentUser { @Autowired private MockMvc mvc; - @MockBean private ArticleQueryService articleQueryService; + @MockitoBean private ArticleQueryService articleQueryService; - @MockBean private ArticleCommandService articleCommandService; + @MockitoBean private ArticleCommandService articleCommandService; @Override @BeforeEach @@ -63,8 +63,8 @@ public void should_create_article_success() throws Exception { body, false, 0, - new DateTime(), - new DateTime(), + Instant.now(), + Instant.now(), tagList, new ProfileData("userid", user.getUsername(), user.getBio(), user.getImage(), false)); @@ -132,8 +132,8 @@ public void should_get_error_message_with_duplicated_title() { body, false, 0, - new DateTime(), - new DateTime(), + Instant.now(), + Instant.now(), asList(tagList), new ProfileData("userid", user.getUsername(), user.getBio(), user.getImage(), false)); diff --git a/src/test/java/io/spring/api/CommentsApiTest.java b/src/test/java/io/spring/api/CommentsApiTest.java index 49700abe6..e7e83cf11 100644 --- a/src/test/java/io/spring/api/CommentsApiTest.java +++ b/src/test/java/io/spring/api/CommentsApiTest.java @@ -26,18 +26,18 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; @WebMvcTest(CommentsApi.class) @Import({WebSecurityConfig.class, JacksonCustomizations.class}) public class CommentsApiTest extends TestWithCurrentUser { - @MockBean private ArticleRepository articleRepository; + @MockitoBean private ArticleRepository articleRepository; - @MockBean private CommentRepository commentRepository; - @MockBean private CommentQueryService commentQueryService; + @MockitoBean private CommentRepository commentRepository; + @MockitoBean private CommentQueryService commentQueryService; private Article article; private CommentData commentData; diff --git a/src/test/java/io/spring/api/CurrentUserApiTest.java b/src/test/java/io/spring/api/CurrentUserApiTest.java index 08e8ece2e..02d2bd0d4 100644 --- a/src/test/java/io/spring/api/CurrentUserApiTest.java +++ b/src/test/java/io/spring/api/CurrentUserApiTest.java @@ -20,9 +20,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; @WebMvcTest(CurrentUserApi.class) @@ -37,7 +37,7 @@ public class CurrentUserApiTest extends TestWithCurrentUser { @Autowired private MockMvc mvc; - @MockBean private UserQueryService userQueryService; + @MockitoBean private UserQueryService userQueryService; @Override @BeforeEach diff --git a/src/test/java/io/spring/api/ListArticleApiTest.java b/src/test/java/io/spring/api/ListArticleApiTest.java index 032850bce..6df2960dd 100644 --- a/src/test/java/io/spring/api/ListArticleApiTest.java +++ b/src/test/java/io/spring/api/ListArticleApiTest.java @@ -18,18 +18,18 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; @WebMvcTest(ArticlesApi.class) @Import({WebSecurityConfig.class, JacksonCustomizations.class}) public class ListArticleApiTest extends TestWithCurrentUser { - @MockBean private ArticleRepository articleRepository; + @MockitoBean private ArticleRepository articleRepository; - @MockBean private ArticleQueryService articleQueryService; + @MockitoBean private ArticleQueryService articleQueryService; - @MockBean private ArticleCommandService articleCommandService; + @MockitoBean private ArticleCommandService articleCommandService; @Autowired private MockMvc mvc; diff --git a/src/test/java/io/spring/api/ProfileApiTest.java b/src/test/java/io/spring/api/ProfileApiTest.java index f32091ecd..d6b13036f 100644 --- a/src/test/java/io/spring/api/ProfileApiTest.java +++ b/src/test/java/io/spring/api/ProfileApiTest.java @@ -18,8 +18,8 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; @WebMvcTest(ProfileApi.class) @@ -29,7 +29,7 @@ public class ProfileApiTest extends TestWithCurrentUser { @Autowired private MockMvc mvc; - @MockBean private ProfileQueryService profileQueryService; + @MockitoBean private ProfileQueryService profileQueryService; private ProfileData profileData; diff --git a/src/test/java/io/spring/api/TestWithCurrentUser.java b/src/test/java/io/spring/api/TestWithCurrentUser.java index 7d3b104b3..1b9d0b632 100644 --- a/src/test/java/io/spring/api/TestWithCurrentUser.java +++ b/src/test/java/io/spring/api/TestWithCurrentUser.java @@ -10,12 +10,12 @@ import io.spring.infrastructure.mybatis.readservice.UserReadService; import java.util.Optional; import org.junit.jupiter.api.BeforeEach; -import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.bean.override.mockito.MockitoBean; abstract class TestWithCurrentUser { - @MockBean protected UserRepository userRepository; + @MockitoBean protected UserRepository userRepository; - @MockBean protected UserReadService userReadService; + @MockitoBean protected UserReadService userReadService; protected User user; protected UserData userData; @@ -24,7 +24,7 @@ abstract class TestWithCurrentUser { protected String username; protected String defaultAvatar; - @MockBean protected JwtService jwtService; + @MockitoBean protected JwtService jwtService; protected void userFixture() { email = "john@jacob.com"; diff --git a/src/test/java/io/spring/api/UsersApiTest.java b/src/test/java/io/spring/api/UsersApiTest.java index 9074f2edc..a0be7c82e 100644 --- a/src/test/java/io/spring/api/UsersApiTest.java +++ b/src/test/java/io/spring/api/UsersApiTest.java @@ -24,10 +24,10 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; @WebMvcTest(UsersApi.class) @@ -40,13 +40,13 @@ public class UsersApiTest { @Autowired private MockMvc mvc; - @MockBean private UserRepository userRepository; + @MockitoBean private UserRepository userRepository; - @MockBean private JwtService jwtService; + @MockitoBean private JwtService jwtService; - @MockBean private UserReadService userReadService; + @MockitoBean private UserReadService userReadService; - @MockBean private UserService userService; + @MockitoBean private UserService userService; @Autowired private PasswordEncoder passwordEncoder; diff --git a/src/test/java/io/spring/application/article/ArticleQueryServiceTest.java b/src/test/java/io/spring/application/article/ArticleQueryServiceTest.java index 96229376c..cf230a752 100644 --- a/src/test/java/io/spring/application/article/ArticleQueryServiceTest.java +++ b/src/test/java/io/spring/application/article/ArticleQueryServiceTest.java @@ -19,9 +19,9 @@ import io.spring.infrastructure.repository.MyBatisArticleFavoriteRepository; import io.spring.infrastructure.repository.MyBatisArticleRepository; import io.spring.infrastructure.repository.MyBatisUserRepository; +import java.time.Instant; import java.util.Arrays; import java.util.Optional; -import org.joda.time.DateTime; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -52,7 +52,7 @@ public void setUp() { userRepository.save(user); article = new Article( - "test", "desc", "body", Arrays.asList("java", "spring"), user.getId(), new DateTime()); + "test", "desc", "body", Arrays.asList("java", "spring"), user.getId(), Instant.now()); articleRepository.save(article); } @@ -92,7 +92,7 @@ public void should_get_default_article_list() { "body", Arrays.asList("test"), user.getId(), - new DateTime().minusHours(1)); + Instant.now().minusSeconds(3600)); articleRepository.save(anotherArticle); ArticleDataList recentArticles = @@ -116,7 +116,7 @@ public void should_get_default_article_list_by_cursor() { "body", Arrays.asList("test"), user.getId(), - new DateTime().minusHours(1)); + Instant.now().minusSeconds(3600)); articleRepository.save(anotherArticle); CursorPager recentArticles = @@ -130,7 +130,7 @@ public void should_get_default_article_list_by_cursor() { null, null, null, - new CursorPageParameter( + new CursorPageParameter( DateTimeCursor.parse(recentArticles.getEndCursor().toString()), 20, Direction.NEXT), user); Assertions.assertEquals(nodata.getData().size(), 0); diff --git a/src/test/java/io/spring/infrastructure/service/DefaultJwtServiceTest.java b/src/test/java/io/spring/infrastructure/service/DefaultJwtServiceTest.java index b2261705e..d052c984a 100644 --- a/src/test/java/io/spring/infrastructure/service/DefaultJwtServiceTest.java +++ b/src/test/java/io/spring/infrastructure/service/DefaultJwtServiceTest.java @@ -14,7 +14,8 @@ public class DefaultJwtServiceTest { @BeforeEach public void setUp() { jwtService = - new DefaultJwtService("123123123123123123123123123123123123123123123123123123123123", 3600); + new DefaultJwtService( + "1231231231231231231231231231231231231231231231231231231231231234", 3600); } @Test diff --git a/src/test/java/io/spring/selenium/pages/BasePage.java b/src/test/java/io/spring/selenium/pages/BasePage.java index 425f696ef..5847251f8 100644 --- a/src/test/java/io/spring/selenium/pages/BasePage.java +++ b/src/test/java/io/spring/selenium/pages/BasePage.java @@ -1,5 +1,6 @@ package io.spring.selenium.pages; +import java.time.Duration; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.PageFactory; @@ -15,7 +16,7 @@ public abstract class BasePage { public BasePage(WebDriver driver) { this.driver = driver; - this.wait = new WebDriverWait(driver, DEFAULT_TIMEOUT_SECONDS); + this.wait = new WebDriverWait(driver, Duration.ofSeconds(DEFAULT_TIMEOUT_SECONDS)); PageFactory.initElements(driver, this); }