Merge branch 'spring-projects:main' into main

This commit is contained in:
Lester Claudio 2023-11-07 15:22:17 -07:00 committed by GitHub
commit bf12157061
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 2814 additions and 1568 deletions

View file

@ -8,6 +8,6 @@ ARG USER=vscode
VOLUME /home/$USER/.m2 VOLUME /home/$USER/.m2
VOLUME /home/$USER/.gradle VOLUME /home/$USER/.gradle
ARG JAVA_VERSION=17.0.4.1-ms ARG JAVA_VERSION=17.0.7-ms
RUN sudo mkdir /home/$USER/.m2 /home/$USER/.gradle && sudo chown $USER:$USER /home/$USER/.m2 /home/$USER/.gradle RUN sudo mkdir /home/$USER/.m2 /home/$USER/.gradle && sudo chown $USER:$USER /home/$USER/.m2 /home/$USER/.gradle
RUN bash -lc '. /usr/local/sdkman/bin/sdkman-init.sh && sdk install java $JAVA_VERSION && sdk use java $JAVA_VERSION' RUN bash -lc '. /usr/local/sdkman/bin/sdkman-init.sh && sdk install java $JAVA_VERSION && sdk use java $JAVA_VERSION'

Binary file not shown.

View file

@ -14,5 +14,5 @@
# KIND, either express or implied. See the License for the # KIND, either express or implied. See the License for the
# specific language governing permissions and limitations # specific language governing permissions and limitations
# under the License. # under the License.
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.7/apache-maven-3.8.7-bin.zip distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.1/apache-maven-3.9.1-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar

View file

@ -1 +0,0 @@
java-baseline=8

View file

@ -1,14 +1,14 @@
plugins { plugins {
id 'java' id 'java'
id 'org.springframework.boot' version '3.0.4' id 'org.springframework.boot' version '3.1.1'
id 'io.spring.dependency-management' version '1.1.0' id 'io.spring.dependency-management' version '1.1.0'
id 'org.graalvm.buildtools.native' version '0.9.20' id 'org.graalvm.buildtools.native' version '0.9.23'
} }
apply plugin: 'java' apply plugin: 'java'
group = 'org.springframework.samples' group = 'org.springframework.samples'
version = '3.0.0' version = '3.1.0'
sourceCompatibility = '17' sourceCompatibility = '17'
repositories { repositories {
@ -35,6 +35,10 @@ dependencies {
runtimeOnly 'org.postgresql:postgresql' runtimeOnly 'org.postgresql:postgresql'
developmentOnly 'org.springframework.boot:spring-boot-devtools' developmentOnly 'org.springframework.boot:spring-boot-devtools'
testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.boot:spring-boot-testcontainers'
testImplementation 'org.springframework.boot:spring-boot-docker-compose'
testImplementation 'org.testcontainers:junit-jupiter'
testImplementation 'org.testcontainers:mysql'
} }
tasks.named('test') { tasks.named('test') {

View file

@ -13,11 +13,15 @@ services:
- MYSQL_DATABASE=petclinic - MYSQL_DATABASE=petclinic
volumes: volumes:
- "./conf.d:/etc/mysql/conf.d:ro" - "./conf.d:/etc/mysql/conf.d:ro"
profiles:
- mysql
postgres: postgres:
image: postgres:15.2 image: postgres:15.3
ports: ports:
- "5432:5432" - "5432:5432"
environment: environment:
- POSTGRES_PASSWORD=petclinic - POSTGRES_PASSWORD=petclinic
- POSTGRES_USER=petclinic - POSTGRES_USER=petclinic
- POSTGRES_DB=petclinic - POSTGRES_DB=petclinic
profiles:
- postgres

218
mvnw vendored
View file

@ -19,7 +19,7 @@
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
# Maven Start Up Batch script # Apache Maven Wrapper startup batch script, version 3.2.0
# #
# Required ENV vars: # Required ENV vars:
# ------------------ # ------------------
@ -27,7 +27,6 @@
# #
# Optional ENV vars # Optional ENV vars
# ----------------- # -----------------
# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven # MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use # e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@ -54,7 +53,7 @@ fi
cygwin=false; cygwin=false;
darwin=false; darwin=false;
mingw=false mingw=false
case "`uname`" in case "$(uname)" in
CYGWIN*) cygwin=true ;; CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;; MINGW*) mingw=true;;
Darwin*) darwin=true Darwin*) darwin=true
@ -62,9 +61,9 @@ case "`uname`" in
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then if [ -x "/usr/libexec/java_home" ]; then
export JAVA_HOME="`/usr/libexec/java_home`" JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
else else
export JAVA_HOME="/Library/Java/Home" JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
fi fi
fi fi
;; ;;
@ -72,68 +71,38 @@ esac
if [ -z "$JAVA_HOME" ] ; then if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home` JAVA_HOME=$(java-config --jre-home)
fi fi
fi fi
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
cd "$saveddir"
# echo Using m2 at $M2_HOME
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched # For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then if $cygwin ; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] && [ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --unix "$JAVA_HOME"` JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
[ -n "$CLASSPATH" ] && [ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --unix "$CLASSPATH"` CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
fi fi
# For Mingw, ensure paths are in UNIX format before anything is touched # For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then if $mingw ; then
[ -n "$M2_HOME" ] && [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`" JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
fi fi
if [ -z "$JAVA_HOME" ]; then if [ -z "$JAVA_HOME" ]; then
javaExecutable="`which javac`" javaExecutable="$(which javac)"
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10. # readlink(1) is not available as standard on Solaris 10.
readLink=`which readlink` readLink=$(which readlink)
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
if $darwin ; then if $darwin ; then
javaHome="`dirname \"$javaExecutable\"`" javaHome="$(dirname "\"$javaExecutable\"")"
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
else else
javaExecutable="`readlink -f \"$javaExecutable\"`" javaExecutable="$(readlink -f "\"$javaExecutable\"")"
fi fi
javaHome="`dirname \"$javaExecutable\"`" javaHome="$(dirname "\"$javaExecutable\"")"
javaHome=`expr "$javaHome" : '\(.*\)/bin'` javaHome=$(expr "$javaHome" : '\(.*\)/bin')
JAVA_HOME="$javaHome" JAVA_HOME="$javaHome"
export JAVA_HOME export JAVA_HOME
fi fi
@ -149,7 +118,7 @@ if [ -z "$JAVACMD" ] ; then
JAVACMD="$JAVA_HOME/bin/java" JAVACMD="$JAVA_HOME/bin/java"
fi fi
else else
JAVACMD="`\\unset -f command; \\command -v java`" JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
fi fi
fi fi
@ -163,12 +132,9 @@ if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set." echo "Warning: JAVA_HOME environment variable is not set."
fi fi
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
# traverses directory structure from process work directory to filesystem root # traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory # first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() { find_maven_basedir() {
if [ -z "$1" ] if [ -z "$1" ]
then then
echo "Path not specified to find_maven_basedir" echo "Path not specified to find_maven_basedir"
@ -184,96 +150,99 @@ find_maven_basedir() {
fi fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc) # workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then if [ -d "${wdir}" ]; then
wdir=`cd "$wdir/.."; pwd` wdir=$(cd "$wdir/.." || exit 1; pwd)
fi fi
# end of workaround # end of workaround
done done
echo "${basedir}" printf '%s' "$(cd "$basedir" || exit 1; pwd)"
} }
# concatenates all lines of a file # concatenates all lines of a file
concat_lines() { concat_lines() {
if [ -f "$1" ]; then if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")" # Remove \r in case we run on Windows within Git Bash
# and check out the repository with auto CRLF management
# enabled. Otherwise, we may read lines that are delimited with
# \r\n and produce $'-Xarg\r' rather than -Xarg due to word
# splitting rules.
tr -s '\r\n' ' ' < "$1"
fi fi
} }
BASE_DIR=`find_maven_basedir "$(pwd)"` log() {
if [ "$MVNW_VERBOSE" = true ]; then
printf '%s\n' "$1"
fi
}
BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
if [ -z "$BASE_DIR" ]; then if [ -z "$BASE_DIR" ]; then
exit 1; exit 1;
fi fi
MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
log "$MAVEN_PROJECTBASEDIR"
########################################################################################## ##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data. # This allows using the maven wrapper in projects that prohibit checking in binary data.
########################################################################################## ##########################################################################################
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
if [ "$MVNW_VERBOSE" = true ]; then if [ -r "$wrapperJarPath" ]; then
echo "Found .mvn/wrapper/maven-wrapper.jar" log "Found $wrapperJarPath"
fi
else else
if [ "$MVNW_VERBOSE" = true ]; then log "Couldn't find $wrapperJarPath, downloading it ..."
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
if [ -n "$MVNW_REPOURL" ]; then if [ -n "$MVNW_REPOURL" ]; then
jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
else else
jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
fi fi
while IFS="=" read key value; do while IFS="=" read -r key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;; # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
safeValue=$(echo "$value" | tr -d '\r')
case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
esac esac
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
if [ "$MVNW_VERBOSE" = true ]; then log "Downloading from: $wrapperUrl"
echo "Downloading from: $jarUrl"
fi
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
if $cygwin; then if $cygwin; then
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
fi fi
if command -v wget > /dev/null; then if command -v wget > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then log "Found wget ... using wget"
echo "Found wget ... using wget" [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
else else
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
fi fi
elif command -v curl > /dev/null; then elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then log "Found curl ... using curl"
echo "Found curl ... using curl" [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl -o "$wrapperJarPath" "$jarUrl" -f curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
else else
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
fi fi
else else
if [ "$MVNW_VERBOSE" = true ]; then log "Falling back to using Java to download"
echo "Falling back to using Java to download" javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
fi javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
# For Cygwin, switch paths to Windows format before running javac # For Cygwin, switch paths to Windows format before running javac
if $cygwin; then if $cygwin; then
javaClass=`cygpath --path --windows "$javaClass"` javaSource=$(cygpath --path --windows "$javaSource")
javaClass=$(cygpath --path --windows "$javaClass")
fi fi
if [ -e "$javaClass" ]; then if [ -e "$javaSource" ]; then
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then if [ ! -e "$javaClass" ]; then
if [ "$MVNW_VERBOSE" = true ]; then log " - Compiling MavenWrapperDownloader.java ..."
echo " - Compiling MavenWrapperDownloader.java ..." ("$JAVA_HOME/bin/javac" "$javaSource")
fi
# Compiling the Java class
("$JAVA_HOME/bin/javac" "$javaClass")
fi fi
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then if [ -e "$javaClass" ]; then
# Running the downloader log " - Running MavenWrapperDownloader.java ..."
if [ "$MVNW_VERBOSE" = true ]; then ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
echo " - Running MavenWrapperDownloader.java ..."
fi
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
fi fi
fi fi
fi fi
@ -282,35 +251,58 @@ fi
# End of extension # End of extension
########################################################################################## ##########################################################################################
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} # If specified, validate the SHA-256 sum of the Maven wrapper jar file
if [ "$MVNW_VERBOSE" = true ]; then wrapperSha256Sum=""
echo $MAVEN_PROJECTBASEDIR while IFS="=" read -r key value; do
case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
esac
done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
if [ -n "$wrapperSha256Sum" ]; then
wrapperSha256Result=false
if command -v sha256sum > /dev/null; then
if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
wrapperSha256Result=true
fi
elif command -v shasum > /dev/null; then
if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
wrapperSha256Result=true
fi
else
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
exit 1
fi
if [ $wrapperSha256Result = false ]; then
echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
exit 1
fi
fi fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java # For Cygwin, switch paths to Windows format before running java
if $cygwin; then if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] && [ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
[ -n "$CLASSPATH" ] && [ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"` CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
[ -n "$MAVEN_PROJECTBASEDIR" ] && [ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
fi fi
# Provide a "standardized" way to retrieve the CLI args that will # Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions. # work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
export MAVEN_CMD_LINE_ARGS export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
# shellcheck disable=SC2086 # safe args
exec "$JAVACMD" \ exec "$JAVACMD" \
$MAVEN_OPTS \ $MAVEN_OPTS \
$MAVEN_DEBUG_OPTS \ $MAVEN_DEBUG_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" \
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

31
mvnw.cmd vendored
View file

@ -18,13 +18,12 @@
@REM ---------------------------------------------------------------------------- @REM ----------------------------------------------------------------------------
@REM ---------------------------------------------------------------------------- @REM ----------------------------------------------------------------------------
@REM Maven Start Up Batch script @REM Apache Maven Wrapper startup batch script, version 3.2.0
@REM @REM
@REM Required ENV vars: @REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir @REM JAVA_HOME - location of a JDK home dir
@REM @REM
@REM Optional ENV vars @REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@ -120,10 +119,10 @@ SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
) )
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@ -134,11 +133,11 @@ if exist %WRAPPER_JAR% (
) )
) else ( ) else (
if not "%MVNW_REPOURL%" == "" ( if not "%MVNW_REPOURL%" == "" (
SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
) )
if "%MVNW_VERBOSE%" == "true" ( if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ... echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL% echo Downloading from: %WRAPPER_URL%
) )
powershell -Command "&{"^ powershell -Command "&{"^
@ -146,7 +145,7 @@ if exist %WRAPPER_JAR% (
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^ "}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
"}" "}"
if "%MVNW_VERBOSE%" == "true" ( if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR% echo Finished downloading %WRAPPER_JAR%
@ -154,6 +153,24 @@ if exist %WRAPPER_JAR% (
) )
@REM End of extension @REM End of extension
@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
SET WRAPPER_SHA_256_SUM=""
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
)
IF NOT %WRAPPER_SHA_256_SUM%=="" (
powershell -Command "&{"^
"$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
"If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
" Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
" Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
" Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
" exit 1;"^
"}"^
"}"
if ERRORLEVEL 1 goto error
)
@REM Provide a "standardized" way to retrieve the CLI args that will @REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions. @REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%* set MAVEN_CMD_LINE_ARGS=%*

87
pom.xml
View file

@ -3,12 +3,12 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.samples</groupId> <groupId>org.springframework.samples</groupId>
<artifactId>spring-petclinic</artifactId> <artifactId>spring-petclinic</artifactId>
<version>3.0.0-SNAPSHOT</version> <version>3.1.0-SNAPSHOT</version>
<parent> <parent>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId> <artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.4</version> <version>3.1.3</version>
</parent> </parent>
<name>petclinic</name> <name>petclinic</name>
@ -18,14 +18,20 @@
<java.version>17</java.version> <java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- Important for reproducible builds. Update using e.g. ./mvnw versions:set -DnewVersion=... -->
<project.build.outputTimestamp>2023-05-10T07:42:50Z</project.build.outputTimestamp>
<!-- Web dependencies --> <!-- Web dependencies -->
<webjars-bootstrap.version>5.2.3</webjars-bootstrap.version> <webjars-bootstrap.version>5.2.3</webjars-bootstrap.version>
<webjars-font-awesome.version>4.7.0</webjars-font-awesome.version> <webjars-font-awesome.version>4.7.0</webjars-font-awesome.version>
<jacoco.version>0.8.8</jacoco.version> <checkstyle.version>10.11.0</checkstyle.version>
<jacoco.version>0.8.10</jacoco.version>
<libsass.version>0.2.29</libsass.version>
<lifecycle-mapping>1.0.0</lifecycle-mapping>
<maven-checkstyle.version>3.2.2</maven-checkstyle.version>
<nohttp-checkstyle.version>0.0.11</nohttp-checkstyle.version> <nohttp-checkstyle.version>0.0.11</nohttp-checkstyle.version>
<spring-format.version>0.0.38</spring-format.version> <spring-format.version>0.0.39</spring-format.version>
</properties> </properties>
@ -104,7 +110,27 @@
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId> <artifactId>spring-boot-devtools</artifactId>
<optional>true</optional> <scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-testcontainers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-docker-compose</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>mysql</artifactId>
<scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
@ -114,21 +140,20 @@
</dependencies> </dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-bom</artifactId>
<version>${testcontainers.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build> <build>
<plugins> <plugins>
<plugin>
<groupId>io.spring.javaformat</groupId>
<artifactId>spring-javaformat-maven-plugin</artifactId>
<version>${spring-format.version}</version>
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>validate</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId> <artifactId>maven-enforcer-plugin</artifactId>
@ -149,15 +174,28 @@
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
<plugin>
<groupId>io.spring.javaformat</groupId>
<artifactId>spring-javaformat-maven-plugin</artifactId>
<version>${spring-format.version}</version>
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>validate</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId> <artifactId>maven-checkstyle-plugin</artifactId>
<version>3.2.1</version> <version>${maven-checkstyle.version}</version>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>com.puppycrawl.tools</groupId> <groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId> <artifactId>checkstyle</artifactId>
<version>10.8.1</version> <version>${checkstyle.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.spring.nohttp</groupId> <groupId>io.spring.nohttp</groupId>
@ -172,7 +210,6 @@
<configuration> <configuration>
<configLocation>src/checkstyle/nohttp-checkstyle.xml</configLocation> <configLocation>src/checkstyle/nohttp-checkstyle.xml</configLocation>
<suppressionsLocation>src/checkstyle/nohttp-checkstyle-suppressions.xml</suppressionsLocation> <suppressionsLocation>src/checkstyle/nohttp-checkstyle-suppressions.xml</suppressionsLocation>
<encoding>UTF-8</encoding>
<sourceDirectories>${basedir}</sourceDirectories> <sourceDirectories>${basedir}</sourceDirectories>
<includes>**/*</includes> <includes>**/*</includes>
<excludes>**/.git/**/*,**/.idea/**/*,**/target/**/,**/.flattened-pom.xml,**/*.class</excludes> <excludes>**/.git/**/*,**/.idea/**/*,**/target/**/,**/.flattened-pom.xml,**/*.class</excludes>
@ -201,8 +238,8 @@
<additionalProperties> <additionalProperties>
<encoding.source>${project.build.sourceEncoding}</encoding.source> <encoding.source>${project.build.sourceEncoding}</encoding.source>
<encoding.reporting>${project.reporting.outputEncoding}</encoding.reporting> <encoding.reporting>${project.reporting.outputEncoding}</encoding.reporting>
<java.source>${maven.compiler.source}</java.source> <java.source>${java.version}</java.source>
<java.target>${maven.compiler.target}</java.target> <java.target>${java.version}</java.target>
</additionalProperties> </additionalProperties>
</configuration> </configuration>
</execution> </execution>
@ -320,7 +357,7 @@
<plugin> <plugin>
<groupId>com.gitlab.haynes</groupId> <groupId>com.gitlab.haynes</groupId>
<artifactId>libsass-maven-plugin</artifactId> <artifactId>libsass-maven-plugin</artifactId>
<version>0.2.29</version> <version>${libsass.version}</version>
<executions> <executions>
<execution> <execution>
<phase>generate-resources</phase> <phase>generate-resources</phase>
@ -354,7 +391,7 @@
<plugin> <plugin>
<groupId>org.eclipse.m2e</groupId> <groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId> <artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version> <version>${lifecycle-mapping}</version>
<configuration> <configuration>
<lifecycleMappingMetadata> <lifecycleMappingMetadata>
<pluginExecutions> <pluginExecutions>

View file

@ -29,8 +29,6 @@ Or you can run it from Maven directly using the Spring Boot Maven plugin. If you
./mvnw spring-boot:run ./mvnw spring-boot:run
``` ```
> NOTE: Windows users should set `git config core.autocrlf true` to avoid format assertions failing the build (use `--global` to set that flag globally).
> NOTE: If you prefer to use Gradle, you can build the app using `./gradlew build` and look for the jar file in `build/libs`. > NOTE: If you prefer to use Gradle, you can build the app using `./gradlew build` and look for the jar file in `build/libs`.
## Building a Container ## Building a Container
@ -68,6 +66,22 @@ docker run -e POSTGRES_USER=petclinic -e POSTGRES_PASSWORD=petclinic -e POSTGRES
Further documentation is provided for [MySQL](https://github.com/spring-projects/spring-petclinic/blob/main/src/main/resources/db/mysql/petclinic_db_setup_mysql.txt) Further documentation is provided for [MySQL](https://github.com/spring-projects/spring-petclinic/blob/main/src/main/resources/db/mysql/petclinic_db_setup_mysql.txt)
and for [PostgreSQL](https://github.com/spring-projects/spring-petclinic/blob/main/src/main/resources/db/postgres/petclinic_db_setup_postgres.txt). and for [PostgreSQL](https://github.com/spring-projects/spring-petclinic/blob/main/src/main/resources/db/postgres/petclinic_db_setup_postgres.txt).
Instead of vanilla `docker` you can also use the provided `docker-compose.yml` file to start the database containers. Each one has a profile just like the Spring profile:
```
$ docker-compose --profile mysql up
```
or
```
$ docker-compose --profile postgres up
```
## Test Applications
At development time we recommend you use the test applications set up as `main()` methods in `PetClinicIntegrationTests` (using the default H2 database and also adding Spring Boot devtools), `MySqlTestApplication` and `PostgresIntegrationTests`. These are set up so that you can run the apps in your IDE and get fast feedback, and also run the same classes as integration tests against the respective database. The MySql integration tests use Testcontainers to start the database in a Docker container, and the Postgres tests use Docker Compose to do the same thing.
## Compiling the CSS ## Compiling the CSS
There is a `petclinic.css` in `src/main/resources/static/resources/css`. It was generated from the `petclinic.scss` source, combined with the [Bootstrap](https://getbootstrap.com/) library. If you make changes to the `scss`, or upgrade Bootstrap, you will need to re-compile the CSS resources using the Maven profile "css", i.e. `./mvnw package -P css`. There is no build profile for Gradle to compile the CSS. There is a `petclinic.css` in `src/main/resources/static/resources/css`. It was generated from the `petclinic.scss` source, combined with the [Bootstrap](https://getbootstrap.com/) library. If you make changes to the `scss`, or upgrade Bootstrap, you will need to re-compile the CSS resources using the Maven profile "css", i.e. `./mvnw package -P css`. There is no build profile for Gradle to compile the CSS.

View file

@ -18,6 +18,9 @@ package org.springframework.samples.petclinic;
import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.samples.petclinic.model.BaseEntity;
import org.springframework.samples.petclinic.model.Person;
import org.springframework.samples.petclinic.vet.Vet;
public class PetClinicRuntimeHints implements RuntimeHintsRegistrar { public class PetClinicRuntimeHints implements RuntimeHintsRegistrar {
@ -26,6 +29,10 @@ public class PetClinicRuntimeHints implements RuntimeHintsRegistrar {
hints.resources().registerPattern("db/*"); // https://github.com/spring-projects/spring-boot/issues/32654 hints.resources().registerPattern("db/*"); // https://github.com/spring-projects/spring-boot/issues/32654
hints.resources().registerPattern("messages/*"); hints.resources().registerPattern("messages/*");
hints.resources().registerPattern("META-INF/resources/webjars/*"); hints.resources().registerPattern("META-INF/resources/webjars/*");
hints.resources().registerPattern("mysql-default-conf");
hints.serialization().registerType(BaseEntity.class);
hints.serialization().registerType(Person.class);
hints.serialization().registerType(Vet.class);
} }
} }

View file

@ -17,7 +17,7 @@ package org.springframework.samples.petclinic.model;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.MappedSuperclass; import jakarta.persistence.MappedSuperclass;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotBlank;
/** /**
* Simple JavaBean domain object representing an person. * Simple JavaBean domain object representing an person.
@ -28,11 +28,11 @@ import jakarta.validation.constraints.NotEmpty;
public class Person extends BaseEntity { public class Person extends BaseEntity {
@Column(name = "first_name") @Column(name = "first_name")
@NotEmpty @NotBlank
private String firstName; private String firstName;
@Column(name = "last_name") @Column(name = "last_name")
@NotEmpty @NotBlank
private String lastName; private String lastName;
public String getFirstName() { public String getFirstName() {

View file

@ -31,7 +31,7 @@ import jakarta.persistence.OneToMany;
import jakarta.persistence.OrderBy; import jakarta.persistence.OrderBy;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import jakarta.validation.constraints.Digits; import jakarta.validation.constraints.Digits;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotBlank;
/** /**
* Simple JavaBean domain object representing an owner. * Simple JavaBean domain object representing an owner.
@ -47,15 +47,15 @@ import jakarta.validation.constraints.NotEmpty;
public class Owner extends Person { public class Owner extends Person {
@Column(name = "address") @Column(name = "address")
@NotEmpty @NotBlank
private String address; private String address;
@Column(name = "city") @Column(name = "city")
@NotEmpty @NotBlank
private String city; private String city;
@Column(name = "telephone") @Column(name = "telephone")
@NotEmpty @NotBlank
@Digits(fraction = 0, integer = 10) @Digits(fraction = 0, integer = 10)
private String telephone; private String telephone;
@ -132,10 +132,9 @@ public class Owner extends Person {
public Pet getPet(String name, boolean ignoreNew) { public Pet getPet(String name, boolean ignoreNew) {
name = name.toLowerCase(); name = name.toLowerCase();
for (Pet pet : getPets()) { for (Pet pet : getPets()) {
if (!ignoreNew || !pet.isNew()) { String compName = pet.getName();
String compName = pet.getName(); if (compName != null && compName.equalsIgnoreCase(name)) {
compName = compName == null ? "" : compName.toLowerCase(); if (!ignoreNew || !pet.isNew()) {
if (compName.equals(name)) {
return pet; return pet;
} }
} }

View file

@ -111,7 +111,6 @@ class OwnerController {
} }
private String addPaginationModel(int page, Model model, Page<Owner> paginated) { private String addPaginationModel(int page, Model model, Page<Owner> paginated) {
model.addAttribute("listOwners", paginated);
List<Owner> listOwners = paginated.getContent(); List<Owner> listOwners = paginated.getContent();
model.addAttribute("currentPage", page); model.addAttribute("currentPage", page);
model.addAttribute("totalPages", paginated.getTotalPages()); model.addAttribute("totalPages", paginated.getTotalPages());

View file

@ -15,6 +15,7 @@
*/ */
package org.springframework.samples.petclinic.owner; package org.springframework.samples.petclinic.owner;
import java.time.LocalDate;
import java.util.Collection; import java.util.Collection;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
@ -55,13 +56,23 @@ class PetController {
@ModelAttribute("owner") @ModelAttribute("owner")
public Owner findOwner(@PathVariable("ownerId") int ownerId) { public Owner findOwner(@PathVariable("ownerId") int ownerId) {
return this.owners.findById(ownerId);
Owner owner = this.owners.findById(ownerId);
if (owner == null) {
throw new IllegalArgumentException("Owner ID not found: " + ownerId);
}
return owner;
} }
@ModelAttribute("pet") @ModelAttribute("pet")
public Pet findPet(@PathVariable("ownerId") int ownerId, public Pet findPet(@PathVariable("ownerId") int ownerId,
@PathVariable(name = "petId", required = false) Integer petId) { @PathVariable(name = "petId", required = false) Integer petId) {
return petId == null ? new Pet() : this.owners.findById(ownerId).getPet(petId);
Owner owner = this.owners.findById(ownerId);
if (owner == null) {
throw new IllegalArgumentException("Owner ID not found: " + ownerId);
}
return petId == null ? new Pet() : owner.getPet(petId);
} }
@InitBinder("owner") @InitBinder("owner")
@ -84,10 +95,15 @@ class PetController {
@PostMapping("/pets/new") @PostMapping("/pets/new")
public String processCreationForm(Owner owner, @Valid Pet pet, BindingResult result, ModelMap model) { public String processCreationForm(Owner owner, @Valid Pet pet, BindingResult result, ModelMap model) {
if (StringUtils.hasLength(pet.getName()) && pet.isNew() && owner.getPet(pet.getName(), true) != null) { if (StringUtils.hasText(pet.getName()) && pet.isNew() && owner.getPet(pet.getName(), true) != null) {
result.rejectValue("name", "duplicate", "already exists"); result.rejectValue("name", "duplicate", "already exists");
} }
LocalDate currentDate = LocalDate.now();
if (pet.getBirthDate() != null && pet.getBirthDate().isAfter(currentDate)) {
result.rejectValue("birthDate", "typeMismatch.birthDate");
}
owner.addPet(pet); owner.addPet(pet);
if (result.hasErrors()) { if (result.hasErrors()) {
model.put("pet", pet); model.put("pet", pet);
@ -107,6 +123,22 @@ class PetController {
@PostMapping("/pets/{petId}/edit") @PostMapping("/pets/{petId}/edit")
public String processUpdateForm(@Valid Pet pet, BindingResult result, Owner owner, ModelMap model) { public String processUpdateForm(@Valid Pet pet, BindingResult result, Owner owner, ModelMap model) {
String petName = pet.getName();
// checking if the pet name already exist for the owner
if (StringUtils.hasText(petName)) {
Pet existingPet = owner.getPet(petName.toLowerCase(), false);
if (existingPet != null && existingPet.getId() != pet.getId()) {
result.rejectValue("name", "duplicate", "already exists");
}
}
LocalDate currentDate = LocalDate.now();
if (pet.getBirthDate() != null && pet.getBirthDate().isAfter(currentDate)) {
result.rejectValue("birthDate", "typeMismatch.birthDate");
}
if (result.hasErrors()) { if (result.hasErrors()) {
model.put("pet", pet); model.put("pet", pet);
return VIEWS_PETS_CREATE_OR_UPDATE_FORM; return VIEWS_PETS_CREATE_OR_UPDATE_FORM;

View file

@ -38,7 +38,7 @@ public class PetValidator implements Validator {
Pet pet = (Pet) obj; Pet pet = (Pet) obj;
String name = pet.getName(); String name = pet.getName();
// name validation // name validation
if (!StringUtils.hasLength(name)) { if (!StringUtils.hasText(name)) {
errors.rejectValue("name", REQUIRED, REQUIRED); errors.rejectValue("name", REQUIRED, REQUIRED);
} }

View file

@ -23,7 +23,7 @@ import org.springframework.samples.petclinic.model.BaseEntity;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotBlank;
/** /**
* Simple JavaBean domain object representing a visit. * Simple JavaBean domain object representing a visit.
@ -39,7 +39,7 @@ public class Visit extends BaseEntity {
@DateTimeFormat(pattern = "yyyy-MM-dd") @DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate date; private LocalDate date;
@NotEmpty @NotBlank
private String description; private String description;
/** /**

View file

@ -49,7 +49,6 @@ class VetController {
Page<Vet> paginated = findPaginated(page); Page<Vet> paginated = findPaginated(page);
vets.getVetList().addAll(paginated.toList()); vets.getVetList().addAll(paginated.toList());
return addPaginationModel(page, paginated, model); return addPaginationModel(page, paginated, model);
} }
private String addPaginationModel(int page, Page<Vet> paginated, Model model) { private String addPaginationModel(int page, Page<Vet> paginated, Model model) {

View file

@ -4,5 +4,6 @@ notFound=wurde nicht gefunden
duplicate=ist bereits vergeben duplicate=ist bereits vergeben
nonNumeric=darf nur numerisch sein nonNumeric=darf nur numerisch sein
duplicateFormSubmission=Wiederholtes Absenden des Formulars ist nicht erlaubt duplicateFormSubmission=Wiederholtes Absenden des Formulars ist nicht erlaubt
typeMismatch.date=ungültiges Datum typeMismatch.date=ung<EFBFBD>ltiges Datum
typeMismatch.birthDate=ungültiges Datum typeMismatch.birthDate=ung<EFBFBD>ltiges Datum

View file

@ -6,3 +6,4 @@ nonNumeric=Sólo debe contener numeros
duplicateFormSubmission=No se permite el envío de formularios duplicados duplicateFormSubmission=No se permite el envío de formularios duplicados
typeMismatch.date=Fecha invalida typeMismatch.date=Fecha invalida
typeMismatch.birthDate=Fecha invalida typeMismatch.birthDate=Fecha invalida

View file

@ -0,0 +1,8 @@
welcome=환영합니다
required=입력이 필요합니다
notFound=찾을 수 없습니다
duplicate=이미 존재합니다
nonNumeric=모두 숫자로 입력해야 합니다
duplicateFormSubmission=중복 제출은 허용되지 않습니다
typeMismatch.date=잘못된 날짜입니다
typeMismatch.birthDate=잘못된 날짜입니다

File diff suppressed because it is too large Load diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

View file

@ -0,0 +1,66 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 2674.9 417">
<style>
.st0, .st1 {
fill: #6db33f;
}
.st3 {
fill: #fff;
}
</style>
<g>
<path class="st0"
d="M366.9,29c-5.8,14.1-13.3,26.6-21.6,37.8c-36.6-37.3-87.8-61-144.3-61C90,5.8-0.7,96-0.7,207.4 c0,58.2,24.9,110.6,64.4,147.6l7.5,6.7c34.9,29.5,80.3,47.4,129.7,47.4c106,0,193.3-82.7,200.8-187.1 C407.7,171.3,392.3,106.4,366.9,29z M92.9,356.7c-5.8,7.5-16.6,8.3-24.1,2.5s-8.3-16.6-2.5-24.1s16.6-8.3,24.1-2.5 C97.5,338.4,98.7,349.2,92.9,356.7z M365.7,296.4c-49.5,66.1-155.9,43.7-223.7,47c0,0-12.1,0.8-24.1,2.5c0,0,4.6-2.1,10.4-4.2 c47.8-16.6,70.3-20,99.4-34.9c54.5-27.9,108.9-89,119.8-152.2c-20.8,60.7-84,113.1-141.4,134.3c-39.5,14.6-110.6,28.7-110.6,28.7 l-2.9-1.7c-48.2-23.7-49.9-128.5,38.3-162.2c38.7-15,75.3-6.7,117.3-16.6c44.5-10.4,96.1-43.7,116.8-87.3 C388.1,120.1,416.4,229,365.7,296.4z"></path>
<g>
<path class="st1"
d="M516.2,286.4c-5-2.5-8.3-8.3-8.3-15.4c0-10,7.9-18.3,18.3-18.3c3.7,0,7.1,1.2,9.6,2.5 c18.7,12.5,38.7,18.7,56.1,18.7c19.1,0,30.4-8.3,30.4-21.2v-0.8c0-15.4-20.8-20.4-43.7-27.4c-28.7-8.3-61.1-20-61.1-57.4v-0.8 c0-37,30.8-59.5,69.4-59.5c20.8,0,42.4,5.8,61.5,15.8c6.2,3.3,10.8,9.1,10.8,17c0,10.4-8.3,18.3-18.7,18.3c-3.7,0-5.8-0.8-8.7-2.1 c-15.8-8.3-32-13.3-45.7-13.3c-17.5,0-27.4,8.3-27.4,19.1v0.8c0,14.6,21.2,20.4,44.1,27.9c28.7,8.7,60.7,22,60.7,57v0.8 c0,41.2-32,61.5-72.3,61.5C565.7,309.7,538.6,301.8,516.2,286.4z"></path>
<path class="st1"
d="M680,129.7c0-12.5,9.6-22.5,22-22.5s22.5,10,22.5,22.5V143c14.6-20.4,34.9-36.6,66.5-36.6 c45.7,0,90.6,36.2,90.6,101.5v0.8c0,64.9-44.5,101.5-90.6,101.5c-32.4,0-52.8-16.2-66.5-34.5v69c0,12.5-10,22.5-22.5,22.5 c-12.1,0-22-9.6-22-22.5V129.7z M836.8,208.7v-0.8c0-37.8-25.4-62.4-55.7-62.4c-30.4,0-57,25.4-57,62.4v0.8 c0,37.4,26.6,62.4,57,62.4C811.4,271.5,836.8,247.3,836.8,208.7z"></path>
<path class="st1"
d="M899.1,129.7c0-12.5,9.6-22.5,22-22.5s22.5,10,22.5,22.5v10.8c2.1-16.6,29.5-33.3,49.1-33.3 c14.1,0,22,9.1,22,22c0,11.6-7.9,19.5-17.9,21.6c-32,5.4-53.6,33.3-53.6,71.9v64.4c0,12.1-10,22-22.5,22c-12.1,0-22-9.6-22-22 V129.7H899.1z"></path>
<path class="st1"
d="M1032.6,130.1c0-12.5,9.6-22.5,22-22.5s22.5,10,22.5,22.5v157.6c0,12.5-10,22-22.5,22c-12.1,0-22-9.6-22-22 V130.1z"></path>
<path class="st1"
d="M1100,130.1c0-12.5,9.6-22.5,22-22.5s22.5,10,22.5,22.5v9.1c12.5-18.3,30.8-32,61.1-32 c44.1,0,69.4,29.5,69.4,74.8v105.2c0,12.5-9.6,22-22,22s-22.5-9.6-22.5-22v-91.5c0-30.4-15-47.8-42-47.8 c-25.8,0-44.1,18.3-44.1,48.6v91.1c0,12.5-10,22-22.5,22c-12.1,0-22-9.6-22-22L1100,130.1L1100,130.1z"></path>
<path class="st1"
d="M1472.1,106.8c-12.5,0-22.5,10-22.5,22.5v13.3c-14.6-20.4-34.9-36.6-66.5-36.6c-45.7,0-90.6,36.2-90.6,101.5 v0.8c0,64.9,44.5,101.5,90.6,101.5c32.4,0,52.8-16.2,66.5-34.1c-2.1,35.3-23.7,53.6-61.5,53.6c-22.5,0-42-5.4-59.9-15.4 c-2.1-1.2-5-1.7-7.9-1.7c-10.4,0-19.1,8.3-19.1,18.3c0,8.7,5,15,12.5,17.9c23.7,11.6,48.2,17.5,75.7,17.5 c35.3,0,62.8-8.3,80.3-26.2c16.2-16.2,24.9-40.7,24.9-73.6V129.7C1494.6,116.8,1484.6,106.8,1472.1,106.8z M1393.5,271 c-30.8,0-55.7-24.1-55.7-62.8v-0.8c0-37.8,25.4-62.4,55.7-62.4s57,25.4,57,62.4v0.8C1450.9,245.7,1424.3,271,1393.5,271z"></path>
<path class="st1"
d="M1077.5,53.6c0,12.5-10,22.5-22.5,22.5s-22.5-10-22.5-22.5s10-22.5,22.5-22.5 C1067.1,30.7,1077.5,40.7,1077.5,53.6z"></path>
</g>
<g>
<path class="st1"
d="M1545.7,153.8c-12.5,0-22.9-10.4-22.9-22.9c0-12.9,10.4-22.9,22.9-22.9c12.9,0,22.9,10.4,22.9,22.9 S1558.6,153.8,1545.7,153.8z M1545.7,111.4c-10.8,0-19.5,8.7-19.5,19.5s8.7,19.5,19.5,19.5s19.5-8.7,19.5-19.5 C1565.2,119.7,1556.5,111.4,1545.7,111.4z M1551.9,143.8l-6.7-10.4h-4.6v10.4h-3.7v-26.2h10.8c4.6,0,8.7,3.3,8.7,7.9 c0,5.8-5.4,7.9-6.7,7.9l7.1,10.4H1551.9L1551.9,143.8z M1547.4,120.9h-6.7v9.1h7.1c2.1,0,4.6-1.7,4.6-4.6 C1552.4,122.6,1549.9,120.9,1547.4,120.9z"></path>
</g>
<g>
<path class="st1"
d="M1652.3,260.1c0,20-12.8,34.1-30,34.1c-10.4,0-18.8-5.2-23.7-13.7v12.2h-9.4v-88h9.4v35.1 c4.9-8.6,13.2-13.7,23.7-13.7C1639.4,226.1,1652.3,240.2,1652.3,260.1z M1642.3,260.1c0-15-9.1-25.6-21.8-25.6 c-12.8,0-21.9,10.6-21.9,25.6s9.1,25.7,21.9,25.7C1633.3,285.8,1642.3,275.1,1642.3,260.1z"></path>
<path class="st1"
d="M1668,313.3l2.3-8.1c2.3,1.1,4.9,1.6,7.7,1.6c4.2,0,6.9-1.4,9.4-5.8l3.8-8.1l-28.3-65.3h10.4l22.8,54.1 l21.5-54.1h10.2l-29.7,72.4c-4.5,11.1-10.8,15.2-19.8,15.4C1674.6,315.4,1671,314.6,1668,313.3z"></path>
<path class="st1"
d="M1861.3,206.2l-38.1,86.5h-10.7l-38.1-86.5h10.7l32.9,74.4l32.7-74.4L1861.3,206.2L1861.3,206.2z"></path>
<path class="st1"
d="M1877.1,206.2h9.8l32.1,60.3l32.1-60.3h9.8v86.5h-9.8v-65.3l-32.1,60.3l-32.1-60.3v65.3h-9.8V206.2z"></path>
<path class="st1"
d="M1977.2,227.6h10.1l16.2,52.5l17.8-52.5h8.2l17.8,52.5l16.2-52.5h10.1l-21.9,65.1h-8.9l-17.2-52l-17.5,52 h-8.9L1977.2,227.6z"></path>
<path class="st1"
d="M2140.1,253.1v39.7h-9.4v-10.2c-4.7,7.7-14.4,11.7-22.9,11.7c-13.7,0-23.8-7.9-23.8-20.8 c0-13,11.6-21.7,25.6-21.7c7,0,14.5,1.5,21.2,3.7v-2.4c0-8.8-3.5-18.9-17.6-18.9c-6.5,0-13.1,2.9-19,5.9l-3.8-7.7 c9.3-4.7,17.1-6.3,23.4-6.3C2131.4,226.1,2140.1,237.3,2140.1,253.1z M2130.7,272.3v-9.9c-5.9-1.6-12.6-2.6-19.6-2.6 c-9.7,0-17.9,5.5-17.9,13.3c0,8.1,7,12.8,16.2,12.8C2118,285.9,2128.2,281.5,2130.7,272.3z"></path>
<path class="st1"
d="M2194.9,226.1v8.4c-14.7,0-25.2,9.1-25.2,21.8v36.4h-9.4v-65.1h9.4v12.7 C2173.6,231.7,2182.9,226.1,2194.9,226.1z"></path>
<path class="st1"
d="M2257.8,278l5.8,6.2c-5.7,6.2-17.1,10.1-26.2,10.1c-17.4,0-33-14.2-33-34.2c0-19.4,14.6-33.9,32.1-33.9 c19.6,0,30.8,14.9,30.8,37.6H2214c1.4,12.7,10.3,22,23.2,22C2245,285.8,2253.8,282.4,2257.8,278z M2214.3,255.3h43.8 c-1.3-11.7-8.2-20.8-21.2-20.8C2225.4,234.6,2216.1,242.7,2214.3,255.3z"></path>
<path class="st1"
d="M2344.6,215.6h-29.1v-9.3h68v9.3h-29.1v77.2h-9.8L2344.6,215.6L2344.6,215.6z"></path>
<path class="st1"
d="M2451.6,253.1v39.7h-9.4v-10.2c-4.7,7.7-14.4,11.7-22.9,11.7c-13.7,0-23.8-7.9-23.8-20.8 c0-13,11.6-21.7,25.6-21.7c7,0,14.5,1.5,21.2,3.7v-2.4c0-8.8-3.5-18.9-17.6-18.9c-6.5,0-13.1,2.9-19,5.9l-3.8-7.7 c9.3-4.7,17.1-6.3,23.4-6.3C2442.9,226.1,2451.6,237.3,2451.6,253.1z M2442.2,272.3v-9.9c-5.9-1.6-12.6-2.6-19.6-2.6 c-9.7,0-17.9,5.5-17.9,13.3c0,8.1,7,12.8,16.2,12.8C2429.4,285.9,2439.6,281.5,2442.2,272.3z"></path>
<path class="st1"
d="M2526.3,251.3v41.4h-9.4v-40.2c0-10.6-6.7-18-16.2-18c-11,0-20.1,7.7-20.1,16.7v41.4h-9.4v-65.1h9.4v10.4 c3.8-6.9,12.2-12,21.4-12C2516.2,226.1,2526.3,236.6,2526.3,251.3z"></path>
<path class="st1"
d="M2542.6,285.3l38.4-48.7h-37.6v-8.9h50.4v7.4l-38.5,48.7h38.8v8.9h-51.4L2542.6,285.3L2542.6,285.3z"></path>
<path class="st1"
d="M2665.4,227.6v65.1h-9.4v-10.4c-3.8,6.9-12.2,12-21.4,12c-14.4,0-24.4-10.4-24.4-25.2v-41.4h9.4v40.2 c0,10.6,6.7,18,16.2,18c11,0,20.1-7.7,20.1-16.7v-41.4h9.5V227.6z"></path>
</g>
</g>
<path class="st3"
d="M92.9,356.7c-5.8,7.5-16.6,8.3-24.1,2.5s-8.3-16.6-2.5-24.1s16.6-8.3,24.1-2.5 C97.5,338.4,98.7,349.2,92.9,356.7z"></path>
<path class="st3"
d="M365.7,296.4c-49.5,66.1-155.9,43.7-223.7,47c0,0-12.1,0.8-24.1,2.5c0,0,4.6-2.1,10.4-4.2 c47.8-16.6,70.3-20,99.4-34.9c54.5-27.9,108.9-89,119.8-152.2c-20.8,60.7-84,113.1-141.4,134.3c-39.5,14.6-110.6,28.7-110.6,28.7 l-2.9-1.7c-48.2-23.7-49.9-128.5,38.3-162.2c38.7-15,75.3-6.7,117.3-16.6c44.5-10.4,96.1-43.7,116.8-87.3 C388.1,120.1,416.4,229,365.7,296.4z"></path>
</svg>

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

View file

@ -80,8 +80,8 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-12 text-center"> <div class="col-12 text-center">
<img src="../static/resources/images/spring-pivotal-logo.png" <img src="../static/images/spring-logo.svg" th:src="@{/resources/images/spring-logo.svg}" alt="VMware Tanzu Logo" class="logo">
th:src="@{/resources/images/spring-pivotal-logo.png}" alt="Sponsored by Pivotal" /></div> </div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -188,6 +188,10 @@ table td.action-column {
color: $spring-brown; color: $spring-brown;
} }
.logo {
width: 200px;
}
.myspinner { .myspinner {
animation-name: spinner; animation-name: spinner;
animation-duration: 2s; animation-duration: 2s;

View file

@ -0,0 +1,71 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledInNativeImage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.HttpStatus;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.samples.petclinic.vet.VetRepository;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.web.client.RestTemplate;
import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@ActiveProfiles("mysql")
@Testcontainers(disabledWithoutDocker = true)
@DisabledInNativeImage
class MySqlIntegrationTests {
@ServiceConnection
@Container
static MySQLContainer<?> container = new MySQLContainer<>("mysql:5.7");
@LocalServerPort
int port;
@Autowired
private VetRepository vets;
@Autowired
private RestTemplateBuilder builder;
@Test
void testFindAll() throws Exception {
vets.findAll();
vets.findAll(); // served from cache
}
@Test
void testOwnerDetails() {
RestTemplate template = builder.rootUri("http://localhost:" + port).build();
ResponseEntity<String> result = template.exchange(RequestEntity.get("/owners/1").build(), String.class);
assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK);
}
}

View file

@ -0,0 +1,46 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.testcontainers.containers.MySQLContainer;
/**
* PetClinic Spring Boot Application.
*
* @author Dave Syer
*
*/
@Configuration
public class MysqlTestApplication {
@ServiceConnection
@Profile("mysql")
@Bean
static MySQLContainer<?> container() {
return new MySQLContainer<>("mysql:5.7");
}
public static void main(String[] args) {
SpringApplication.run(PetClinicApplication.class, "--spring.profiles.active=mysql");
}
}

View file

@ -20,6 +20,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.boot.test.web.server.LocalServerPort;
@ -31,7 +32,7 @@ import org.springframework.samples.petclinic.vet.VetRepository;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class PetClinicIntegrationTests { public class PetClinicIntegrationTests {
@LocalServerPort @LocalServerPort
int port; int port;
@ -55,4 +56,8 @@ class PetClinicIntegrationTests {
assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK);
} }
public static void main(String[] args) {
SpringApplication.run(PetClinicApplication.class, args);
}
} }

View file

@ -0,0 +1,140 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledInNativeImage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.ApplicationListener;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.http.HttpStatus;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.samples.petclinic.vet.VetRepository;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.web.client.RestTemplate;
import org.testcontainers.DockerClientFactory;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = { "spring.docker.compose.skip.in-tests=false", //
"spring.docker.compose.profiles.active=postgres" })
@ActiveProfiles("postgres")
@DisabledInNativeImage
public class PostgresIntegrationTests {
@LocalServerPort
int port;
@Autowired
private VetRepository vets;
@Autowired
private RestTemplateBuilder builder;
@BeforeAll
static void available() {
assumeTrue(DockerClientFactory.instance().isDockerAvailable(), "Docker not available");
}
public static void main(String[] args) {
new SpringApplicationBuilder(PetClinicApplication.class) //
.profiles("postgres") //
.properties( //
"spring.docker.compose.profiles.active=postgres" //
) //
.listeners(new PropertiesLogger()) //
.run(args);
}
@Test
void testFindAll() throws Exception {
vets.findAll();
vets.findAll(); // served from cache
}
@Test
void testOwnerDetails() {
RestTemplate template = builder.rootUri("http://localhost:" + port).build();
ResponseEntity<String> result = template.exchange(RequestEntity.get("/owners/1").build(), String.class);
assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK);
}
static class PropertiesLogger implements ApplicationListener<ApplicationPreparedEvent> {
private static final Log log = LogFactory.getLog(PropertiesLogger.class);
private ConfigurableEnvironment environment;
private boolean isFirstRun = true;
@Override
public void onApplicationEvent(ApplicationPreparedEvent event) {
if (isFirstRun) {
environment = event.getApplicationContext().getEnvironment();
printProperties();
}
isFirstRun = false;
}
public void printProperties() {
for (EnumerablePropertySource<?> source : findPropertiesPropertySources()) {
log.info("PropertySource: " + source.getName());
String[] names = source.getPropertyNames();
Arrays.sort(names);
for (String name : names) {
String resolved = environment.getProperty(name);
String value = source.getProperty(name).toString();
if (resolved.equals(value)) {
log.info(name + "=" + resolved);
}
else {
log.info(name + "=" + value + " OVERRIDDEN to " + resolved);
}
}
}
}
private List<EnumerablePropertySource<?>> findPropertiesPropertySources() {
List<EnumerablePropertySource<?>> sources = new LinkedList<>();
for (PropertySource<?> source : environment.getPropertySources()) {
if (source instanceof EnumerablePropertySource enumerable) {
sources.add(enumerable);
}
}
return sources;
}
}
}

View file

@ -54,7 +54,7 @@ class ValidatorTests {
assertThat(constraintViolations).hasSize(1); assertThat(constraintViolations).hasSize(1);
ConstraintViolation<Person> violation = constraintViolations.iterator().next(); ConstraintViolation<Person> violation = constraintViolations.iterator().next();
assertThat(violation.getPropertyPath().toString()).isEqualTo("firstName"); assertThat(violation.getPropertyPath().toString()).isEqualTo("firstName");
assertThat(violation.getMessage()).isEqualTo("must not be empty"); assertThat(violation.getMessage()).isEqualTo("must not be blank");
} }
} }

View file

@ -38,6 +38,7 @@ import org.hamcrest.BaseMatcher;
import org.hamcrest.Description; import org.hamcrest.Description;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledInNativeImage;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
@ -53,6 +54,7 @@ import org.springframework.test.web.servlet.MockMvc;
* @author Colin But * @author Colin But
*/ */
@WebMvcTest(OwnerController.class) @WebMvcTest(OwnerController.class)
@DisabledInNativeImage
class OwnerControllerTests { class OwnerControllerTests {
private static final int TEST_OWNER_ID = 1; private static final int TEST_OWNER_ID = 1;

View file

@ -19,6 +19,7 @@ package org.springframework.samples.petclinic.owner;
import org.assertj.core.util.Lists; import org.assertj.core.util.Lists;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledInNativeImage;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.MockBean;
@ -29,7 +30,9 @@ import org.springframework.test.web.servlet.MockMvc;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
/** /**
* Test class for the {@link PetController} * Test class for the {@link PetController}
@ -38,6 +41,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
*/ */
@WebMvcTest(value = PetController.class, @WebMvcTest(value = PetController.class,
includeFilters = @ComponentScan.Filter(value = PetTypeFormatter.class, type = FilterType.ASSIGNABLE_TYPE)) includeFilters = @ComponentScan.Filter(value = PetTypeFormatter.class, type = FilterType.ASSIGNABLE_TYPE))
@DisabledInNativeImage
class PetControllerTests { class PetControllerTests {
private static final int TEST_OWNER_ID = 1; private static final int TEST_OWNER_ID = 1;

View file

@ -28,6 +28,7 @@ import java.util.Locale;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledInNativeImage;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
@ -38,6 +39,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
* @author Colin But * @author Colin But
*/ */
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
@DisabledInNativeImage
class PetTypeFormatterTests { class PetTypeFormatterTests {
@Mock @Mock

View file

@ -25,6 +25,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledInNativeImage;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.MockBean;
@ -36,6 +37,7 @@ import org.springframework.test.web.servlet.MockMvc;
* @author Colin But * @author Colin But
*/ */
@WebMvcTest(VisitController.class) @WebMvcTest(VisitController.class)
@DisabledInNativeImage
class VisitControllerTests { class VisitControllerTests {
private static final int TEST_OWNER_ID = 1; private static final int TEST_OWNER_ID = 1;

View file

@ -0,0 +1,98 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.system;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
/**
* Integration Test for {@link CrashController}.
*
* @author Alex Lutz
*/
// NOT Waiting https://github.com/spring-projects/spring-boot/issues/5574
@SpringBootTest(webEnvironment = RANDOM_PORT,
properties = { "server.error.include-message=ALWAYS", "management.endpoints.enabled-by-default=false" })
class CrashControllerIntegrationTests {
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class, HibernateJpaAutoConfiguration.class })
static class TestConfiguration {
}
@Value(value = "${local.server.port}")
private int port;
@Autowired
private TestRestTemplate rest;
@Test
void testTriggerExceptionJson() {
ResponseEntity<Map<String, Object>> resp = rest.exchange(
RequestEntity.get("http://localhost:" + port + "/oups").build(),
new ParameterizedTypeReference<Map<String, Object>>() {
});
assertThat(resp).isNotNull();
assertThat(resp.getStatusCode().is5xxServerError());
assertThat(resp.getBody().containsKey("timestamp"));
assertThat(resp.getBody().containsKey("status"));
assertThat(resp.getBody().containsKey("error"));
assertThat(resp.getBody()).containsEntry("message",
"Expected: controller used to showcase what happens when an exception is thrown");
assertThat(resp.getBody()).containsEntry("path", "/oups");
}
@Test
void testTriggerExceptionHtml() {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(List.of(MediaType.TEXT_HTML));
ResponseEntity<String> resp = rest.exchange("http://localhost:" + port + "/oups", HttpMethod.GET,
new HttpEntity<>(headers), String.class);
assertThat(resp).isNotNull();
assertThat(resp.getStatusCode().is5xxServerError());
assertThat(resp.getBody()).isNotNull();
// html:
assertThat(resp.getBody()).containsSubsequence("<body>", "<h2>", "Something happened...", "</h2>", "<p>",
"Expected:", "controller", "used", "to", "showcase", "what", "happens", "when", "an", "exception", "is",
"thrown", "</p>", "</body>");
// Not the whitelabel error page:
assertThat(resp.getBody()).doesNotContain("Whitelabel Error Page",
"This application has no explicit mapping for");
}
}

View file

@ -16,35 +16,31 @@
package org.springframework.samples.petclinic.system; package org.springframework.samples.petclinic.system;
import org.junit.jupiter.api.Disabled; import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import org.junit.jupiter.api.Test;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
/** /**
* Test class for {@link CrashController} * Test class for {@link CrashController}
* *
* @author Colin But * @author Colin But
* @author Alex Lutz
*/ */
// Waiting https://github.com/spring-projects/spring-boot/issues/5574 // Waiting https://github.com/spring-projects/spring-boot/issues/5574 ..good
@Disabled // luck ((plain(st) UNIT test)! :)
@WebMvcTest(controllers = CrashController.class)
class CrashControllerTests { class CrashControllerTests {
@Autowired CrashController testee = new CrashController();
private MockMvc mockMvc;
@Test @Test
void testTriggerException() throws Exception { void testTriggerException() throws Exception {
mockMvc.perform(get("/oups")) RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
.andExpect(view().name("exception")) testee.triggerException();
.andExpect(model().attributeExists("exception")) });
.andExpect(forwardedUrl("exception"))
.andExpect(status().isOk()); assertEquals("Expected: controller used to showcase what happens when an exception is thrown",
thrown.getMessage());
} }
} }

View file

@ -19,6 +19,7 @@ package org.springframework.samples.petclinic.vet;
import org.assertj.core.util.Lists; import org.assertj.core.util.Lists;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledInNativeImage;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.MockBean;
@ -39,6 +40,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
*/ */
@WebMvcTest(VetController.class) @WebMvcTest(VetController.class)
@DisabledInNativeImage
class VetControllerTests { class VetControllerTests {
@Autowired @Autowired

View file

@ -31,6 +31,7 @@ class VetTests {
vet.setFirstName("Zaphod"); vet.setFirstName("Zaphod");
vet.setLastName("Beeblebrox"); vet.setLastName("Beeblebrox");
vet.setId(123); vet.setId(123);
@SuppressWarnings("deprecation")
Vet other = (Vet) SerializationUtils.deserialize(SerializationUtils.serialize(vet)); Vet other = (Vet) SerializationUtils.deserialize(SerializationUtils.serialize(vet));
assertThat(other.getFirstName()).isEqualTo(vet.getFirstName()); assertThat(other.getFirstName()).isEqualTo(vet.getFirstName());
assertThat(other.getLastName()).isEqualTo(vet.getLastName()); assertThat(other.getLastName()).isEqualTo(vet.getLastName());

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="2.8" jmeter="2.13 r1665067"> <jmeterTestPlan version="1.2" properties="5.0" jmeter="5.5">
<hashTree> <hashTree>
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true"> <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true">
<stringProp name="TestPlan.comments"></stringProp> <stringProp name="TestPlan.comments"></stringProp>
@ -69,7 +69,7 @@
<hashTree/> <hashTree/>
<CounterConfig guiclass="CounterConfigGui" testclass="CounterConfig" testname="User Count" enabled="true"> <CounterConfig guiclass="CounterConfigGui" testclass="CounterConfig" testname="User Count" enabled="true">
<stringProp name="CounterConfig.start">1</stringProp> <stringProp name="CounterConfig.start">1</stringProp>
<stringProp name="CounterConfig.end">10</stringProp> <stringProp name="CounterConfig.end">3</stringProp>
<stringProp name="CounterConfig.incr">1</stringProp> <stringProp name="CounterConfig.incr">1</stringProp>
<stringProp name="CounterConfig.name">count</stringProp> <stringProp name="CounterConfig.name">count</stringProp>
<stringProp name="CounterConfig.format"></stringProp> <stringProp name="CounterConfig.format"></stringProp>
@ -78,7 +78,7 @@
<hashTree/> <hashTree/>
<CounterConfig guiclass="CounterConfigGui" testclass="CounterConfig" testname="Pet Count" enabled="true"> <CounterConfig guiclass="CounterConfigGui" testclass="CounterConfig" testname="Pet Count" enabled="true">
<stringProp name="CounterConfig.start">1</stringProp> <stringProp name="CounterConfig.start">1</stringProp>
<stringProp name="CounterConfig.end">13</stringProp> <stringProp name="CounterConfig.end">3</stringProp>
<stringProp name="CounterConfig.incr">1</stringProp> <stringProp name="CounterConfig.incr">1</stringProp>
<stringProp name="CounterConfig.name">petCount</stringProp> <stringProp name="CounterConfig.name">petCount</stringProp>
<stringProp name="CounterConfig.format"></stringProp> <stringProp name="CounterConfig.format"></stringProp>
@ -135,7 +135,7 @@
<stringProp name="HTTPSampler.response_timeout"></stringProp> <stringProp name="HTTPSampler.response_timeout"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp> <stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp> <stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">${CONTEXT_WEB}/webjars/jquery/jquery.min.js</stringProp> <stringProp name="HTTPSampler.path">${CONTEXT_WEB}/webjars/bootstrap/5.2.3/dist/js/bootstrap.bundle.min.js</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp> <stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp> <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp> <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
@ -246,13 +246,42 @@
</HTTPSamplerProxy> </HTTPSamplerProxy>
<hashTree/> <hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="POST Edit Owner" enabled="true"> <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="POST Edit Owner" enabled="true">
<boolProp name="HTTPSampler.postBodyRaw">true</boolProp> <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="Variables pr<70>-d<>finies" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments">
<collectionProp name="Arguments.arguments"> <collectionProp name="Arguments.arguments">
<elementProp name="" elementType="HTTPArgument"> <elementProp name="firstName" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp> <boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">firstName=Test&amp;lastName=${count}&amp;address=1234+Test+St.&amp;city=TestCity&amp;telephone=612345678</stringProp> <stringProp name="Argument.value">Test</stringProp>
<stringProp name="Argument.metadata">=</stringProp> <stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">firstName</stringProp>
</elementProp>
<elementProp name="lastName" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">${count}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">lastName</stringProp>
</elementProp>
<elementProp name="address" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">1234+Test+St.</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">address</stringProp>
</elementProp>
<elementProp name="city" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">TestCity</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">city</stringProp>
</elementProp>
<elementProp name="telephone" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">612345678</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">telephone</stringProp>
</elementProp> </elementProp>
</collectionProp> </collectionProp>
</elementProp> </elementProp>
@ -272,7 +301,7 @@
<stringProp name="HTTPSampler.embedded_url_re"></stringProp> <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
</HTTPSamplerProxy> </HTTPSamplerProxy>
<hashTree/> <hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="New visit" enabled="true"> <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="New Pet" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="Variables pr<70>-d<>finies" enabled="true"> <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="Variables pr<70>-d<>finies" enabled="true">
<collectionProp name="Arguments.arguments"/> <collectionProp name="Arguments.arguments"/>
</elementProp> </elementProp>
@ -282,7 +311,7 @@
<stringProp name="HTTPSampler.response_timeout"></stringProp> <stringProp name="HTTPSampler.response_timeout"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp> <stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp> <stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">${CONTEXT_WEB}/owners/${count}/pets/${petCount}/visits/new</stringProp> <stringProp name="HTTPSampler.path">${CONTEXT_WEB}/owners/${count}/pets/new</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp> <stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp> <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp> <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
@ -292,14 +321,29 @@
<stringProp name="HTTPSampler.embedded_url_re"></stringProp> <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
</HTTPSamplerProxy> </HTTPSamplerProxy>
<hashTree/> <hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="POST new visit" enabled="true"> <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="POST new pet" enabled="true">
<boolProp name="HTTPSampler.postBodyRaw">true</boolProp> <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="Variables pr<70>-d<>finies" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments">
<collectionProp name="Arguments.arguments"> <collectionProp name="Arguments.arguments">
<elementProp name="" elementType="HTTPArgument"> <elementProp name="name" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp> <boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">date=2013%2F02%2F22&amp;description=visit</stringProp> <stringProp name="Argument.value">Test+Fluffy+${petCount}</stringProp>
<stringProp name="Argument.metadata">=</stringProp> <stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">name</stringProp>
</elementProp>
<elementProp name="birthDate" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">2020-12-20</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">birthDate</stringProp>
</elementProp>
<elementProp name="type" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">cat</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">type</stringProp>
</elementProp> </elementProp>
</collectionProp> </collectionProp>
</elementProp> </elementProp>
@ -309,7 +353,7 @@
<stringProp name="HTTPSampler.response_timeout"></stringProp> <stringProp name="HTTPSampler.response_timeout"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp> <stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp> <stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">${CONTEXT_WEB}/owners/${count}/pets/${petCount}/visits/new</stringProp> <stringProp name="HTTPSampler.path">${CONTEXT_WEB}/owners/${count}/pets/new</stringProp>
<stringProp name="HTTPSampler.method">POST</stringProp> <stringProp name="HTTPSampler.method">POST</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp> <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp> <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
@ -319,22 +363,54 @@
<stringProp name="HTTPSampler.embedded_url_re"></stringProp> <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
</HTTPSamplerProxy> </HTTPSamplerProxy>
<hashTree/> <hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Owner" enabled="true"> <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="New visit" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="Variables pr<70>-d<>finies" enabled="true"> <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="Variables pr<70>-d<>finies" enabled="true">
<collectionProp name="Arguments.arguments"/> <collectionProp name="Arguments.arguments"/>
</elementProp> </elementProp>
<stringProp name="HTTPSampler.domain"></stringProp> <stringProp name="HTTPSampler.domain"></stringProp>
<stringProp name="HTTPSampler.port"></stringProp> <stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp> <stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp> <stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">${CONTEXT_WEB}/owners/${count}</stringProp> <stringProp name="HTTPSampler.path">${CONTEXT_WEB}/owners/${count}/pets/${petCount}/visits/new</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp> <stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp> <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp> <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp> <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="POST new visit" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="Variables pr<70>-d<>finies" enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="date" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">2013-02-22</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">date</stringProp>
</elementProp>
<elementProp name="description" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">visit</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">description</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="HTTPSampler.domain"></stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">${CONTEXT_WEB}/owners/${count}/pets/${petCount}/visits/new</stringProp>
<stringProp name="HTTPSampler.method">POST</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<boolProp name="HTTPSampler.monitor">false</boolProp> <boolProp name="HTTPSampler.monitor">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp> <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
</HTTPSamplerProxy> </HTTPSamplerProxy>