diff --git a/.gitattributes b/.gitattributes index 31f1bf50b..8a09fad07 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,5 @@ -mvnw text eol=lf +/mvnw text eol=lf +*.cmd text eol=crlf *.java text eol=lf /gradlew text eol=lf diff --git a/.gitignore b/.gitignore index d2767ad28..76cb65ff3 100644 --- a/.gitignore +++ b/.gitignore @@ -42,9 +42,6 @@ out/ ### VS Code ### .vscode/ -### SDK Man ### -.sdkmanrc - ### CSS ### _site/ *.css diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index 4bc659978..c0bcafe98 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -1,19 +1,3 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -wrapperVersion=3.3.2 +wrapperVersion=3.3.4 distributionType=only-script -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.10/apache-maven-3.9.10-bin.zip +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip diff --git a/README.md b/README.md index 1f865c56f..a1c65baff 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,17 @@ ## Understanding the Spring Petclinic application with a few diagrams -[See the presentation here](https://speakerdeck.com/michaelisvy/spring-petclinic-sample-application) +See the presentation here: +[Spring Petclinic Sample Application (legacy slides)](https://speakerdeck.com/michaelisvy/spring-petclinic-sample-application?slide=20) + +> **Note:** These slides refer to a legacy, pre–Spring Boot version of Petclinic and may not reflect the current Spring Boot–based implementation. +> For up-to-date information, please refer to this repository and its documentation. + ## Run Petclinic locally -Spring Petclinic is a [Spring Boot](https://spring.io/guides/gs/spring-boot) application built using [Maven](https://spring.io/guides/gs/maven/) or [Gradle](https://spring.io/guides/gs/gradle/). You can build a jar file and run it from the command line (it should work just as well with Java 17 or newer): +Spring Petclinic is a [Spring Boot](https://spring.io/guides/gs/spring-boot) application built using [Maven](https://spring.io/guides/gs/maven/) or [Gradle](https://spring.io/guides/gs/gradle/). +Java 17 or later is required for the build, and the application can run with Java 17 or newer: ```bash git clone https://github.com/spring-projects/spring-petclinic.git @@ -54,13 +60,13 @@ A similar setup is provided for MySQL and PostgreSQL if a persistent database co You can start MySQL or PostgreSQL locally with whatever installer works for your OS or use docker: ```bash -docker run -e MYSQL_USER=petclinic -e MYSQL_PASSWORD=petclinic -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=petclinic -p 3306:3306 mysql:9.2 +docker run -e MYSQL_USER=petclinic -e MYSQL_PASSWORD=petclinic -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=petclinic -p 3306:3306 mysql:9.5 ``` or ```bash -docker run -e POSTGRES_USER=petclinic -e POSTGRES_PASSWORD=petclinic -e POSTGRES_DB=petclinic -p 5432:5432 postgres:17.5 +docker run -e POSTGRES_USER=petclinic -e POSTGRES_PASSWORD=petclinic -e POSTGRES_DB=petclinic -p 5432:5432 postgres:18.1 ``` 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) @@ -92,7 +98,7 @@ There is a `petclinic.css` in `src/main/resources/static/resources/css`. It was The following items should be installed in your system: -- Java 17 or newer (full JDK, not a JRE) +- Java 25 or newer (full JDK, not a JRE) - [Git command line tool](https://help.github.com/articles/set-up-git) - Your preferred IDE - Eclipse with the m2e plugin. Note: when m2e is available, there is an m2 icon in `Help -> About` dialog. If m2e is diff --git a/build.gradle b/build.gradle index d0cd5f587..e7540a9f5 100644 --- a/build.gradle +++ b/build.gradle @@ -1,18 +1,18 @@ plugins { id 'java' id 'checkstyle' - id 'org.springframework.boot' version '3.5.0' + id 'org.springframework.boot' version '4.0.0' id 'io.spring.dependency-management' version '1.1.7' - id 'org.graalvm.buildtools.native' version '0.10.6' - id 'org.cyclonedx.bom' version '2.3.1' - id 'io.spring.javaformat' version '0.0.46' + id 'org.graalvm.buildtools.native' version '0.11.1' + id 'org.cyclonedx.bom' version '3.0.2' + id 'io.spring.javaformat' version '0.0.47' id "io.spring.nohttp" version "0.0.11" } gradle.startParameter.excludedTaskNames += [ "checkFormatAot", "checkFormatAotTest" ] group = 'org.springframework.samples' -version = '3.5.0' +version = '4.0.0-SNAPSHOT' java { toolchain { @@ -24,17 +24,17 @@ repositories { mavenCentral() } -ext.checkstyleVersion = "10.25.0" -ext.springJavaformatCheckstyleVersion = "0.0.46" -ext.webjarsLocatorLiteVersion = "1.1.0" +ext.checkstyleVersion = "12.1.2" +ext.springJavaformatCheckstyleVersion = "0.0.47" +ext.webjarsLocatorLiteVersion = "1.1.2" ext.webjarsFontawesomeVersion = "4.7.0" -ext.webjarsBootstrapVersion = "5.3.6" +ext.webjarsBootstrapVersion = "5.3.8" dependencies { implementation 'org.springframework.boot:spring-boot-starter-cache' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' - implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-webmvc' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'javax.cache:cache-api' implementation 'jakarta.xml.bind:jakarta.xml.bind-api' @@ -47,11 +47,13 @@ dependencies { runtimeOnly 'com.mysql:mysql-connector-j' runtimeOnly 'org.postgresql:postgresql' developmentOnly 'org.springframework.boot:spring-boot-devtools' - testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.springframework.boot:spring-boot-starter-data-jpa-test' + testImplementation 'org.springframework.boot:spring-boot-starter-restclient-test' + testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-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' + testImplementation 'org.testcontainers:testcontainers-junit-jupiter' + testImplementation 'org.testcontainers:testcontainers-mysql' checkstyle "io.spring.javaformat:spring-javaformat-checkstyle:${springJavaformatCheckstyleVersion}" checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}" } @@ -84,8 +86,3 @@ checkFormatAotTest.enabled = false formatAot.enabled = false formatAotTest.enabled = false - -wrapper { - gradleVersion = "8.14.3" - distributionType = Wrapper.DistributionType.ALL -} diff --git a/docker-compose.yml b/docker-compose.yml index e066d1237..b2313a1e6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: mysql: - image: mysql:9.2 + image: mysql:9.5 ports: - "3306:3306" environment: @@ -12,7 +12,7 @@ services: volumes: - "./conf.d:/etc/mysql/conf.d:ro" postgres: - image: postgres:17.5 + image: postgres:18.1 ports: - "5432:5432" environment: diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 1b33c55ba..f8e1ee312 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 dbc089ed3..23449a2b5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=ed1a8d686605fd7c23bdf62c7fc7add1c5b23b2bbc3721e661934ef4a4911d7c -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 23d15a936..adff685a0 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 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. @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -172,7 +171,6 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -212,7 +210,6 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" diff --git a/gradlew.bat b/gradlew.bat index db3a6ac20..c4bdd3ab8 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,10 @@ goto fail :execute @rem Setup the command line -set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/k8s/db.yml b/k8s/db.yml index 29d5ea4f8..5c6e0f9e2 100644 --- a/k8s/db.yml +++ b/k8s/db.yml @@ -41,7 +41,7 @@ spec: app: demo-db spec: containers: - - image: postgres:17.5 + - image: postgres:18.1 name: postgresql env: - name: POSTGRES_USER diff --git a/mvnw b/mvnw index d7c358e5a..33c353fae 100755 --- a/mvnw +++ b/mvnw @@ -19,7 +19,7 @@ # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- -# Apache Maven Wrapper startup batch script, version 3.3.2 +# Apache Maven Wrapper startup batch script, version 3.3.4 # # Optional ENV vars # ----------------- @@ -105,14 +105,17 @@ trim() { printf "%s" "${1}" | tr -d '[:space:]' } +scriptDir="$(dirname "$0")" +scriptName="$(basename "$0")" + # parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties while IFS="=" read -r key value; do case "${key-}" in distributionUrl) distributionUrl=$(trim "${value-}") ;; distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; esac -done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" -[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" +done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" case "${distributionUrl##*/}" in maven-mvnd-*bin.*) @@ -130,7 +133,7 @@ maven-mvnd-*bin.*) distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" ;; maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; -*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; esac # apply MVNW_REPOURL and calculate MAVEN_HOME @@ -227,7 +230,7 @@ if [ -n "${distributionSha256Sum-}" ]; then echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 exit 1 elif command -v sha256sum >/dev/null; then - if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then distributionSha256Result=true fi elif command -v shasum >/dev/null; then @@ -252,8 +255,41 @@ if command -v unzip >/dev/null; then else tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" fi -printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" -mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +actualDistributionDir="" + +# First try the expected directory name (for regular distributions) +if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then + if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then + actualDistributionDir="$distributionUrlNameMain" + fi +fi + +# If not found, search for any directory with the Maven executable (for snapshots) +if [ -z "$actualDistributionDir" ]; then + # enable globbing to iterate over items + set +f + for dir in "$TMP_DOWNLOAD_DIR"/*; do + if [ -d "$dir" ]; then + if [ -f "$dir/bin/$MVN_CMD" ]; then + actualDistributionDir="$(basename "$dir")" + break + fi + fi + done + set -f +fi + +if [ -z "$actualDistributionDir" ]; then + verbose "Contents of $TMP_DOWNLOAD_DIR:" + verbose "$(ls -la "$TMP_DOWNLOAD_DIR")" + die "Could not find Maven distribution directory in extracted archive" +fi + +verbose "Found extracted Maven distribution directory: $actualDistributionDir" +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" clean || : exec_maven "$@" diff --git a/mvnw.cmd b/mvnw.cmd index 6f779cff2..bba35af22 100644 --- a/mvnw.cmd +++ b/mvnw.cmd @@ -19,7 +19,7 @@ @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- -@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM Apache Maven Wrapper startup batch script, version 3.3.4 @REM @REM Optional ENV vars @REM MVNW_REPOURL - repo url base for downloading maven distribution @@ -40,7 +40,7 @@ @SET __MVNW_ARG0_NAME__= @SET MVNW_USERNAME= @SET MVNW_PASSWORD= -@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*) @echo Cannot start maven from wrapper >&2 && exit /b 1 @GOTO :EOF : end batch / begin powershell #> @@ -73,16 +73,30 @@ switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { # apply MVNW_REPOURL and calculate MAVEN_HOME # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ if ($env:MVNW_REPOURL) { - $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } - $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" + $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')" } $distributionUrlName = $distributionUrl -replace '^.*/','' $distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' -$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" + +$MAVEN_M2_PATH = "$HOME/.m2" if ($env:MAVEN_USER_HOME) { - $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" + $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME" } -$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' + +if (-not (Test-Path -Path $MAVEN_M2_PATH)) { + New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null +} + +$MAVEN_WRAPPER_DISTS = $null +if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) { + $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists" +} else { + $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists" +} + +$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain" +$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' $MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { @@ -134,7 +148,33 @@ if ($distributionSha256Sum) { # unzip and move Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null -Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +$actualDistributionDir = "" + +# First try the expected directory name (for regular distributions) +$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain" +$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD" +if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) { + $actualDistributionDir = $distributionUrlNameMain +} + +# If not found, search for any directory with the Maven executable (for snapshots) +if (!$actualDistributionDir) { + Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object { + $testPath = Join-Path $_.FullName "bin/$MVN_CMD" + if (Test-Path -Path $testPath -PathType Leaf) { + $actualDistributionDir = $_.Name + } + } +} + +if (!$actualDistributionDir) { + Write-Error "Could not find Maven distribution directory in extracted archive" +} + +Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir" +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null try { Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null } catch { diff --git a/pom.xml b/pom.xml index 8576c22ba..ae930ae5c 100644 --- a/pom.xml +++ b/pom.xml @@ -1,22 +1,17 @@ 4.0.0 - org.springframework.boot spring-boot-starter-parent - 3.5.0 - + 4.0.0 - org.springframework.samples spring-petclinic - 3.5.0-SNAPSHOT - + 4.0.0-SNAPSHOT petclinic - 17 UTF-8 @@ -25,20 +20,26 @@ 2024-11-28T14:37:52Z - 1.1.0 - 5.3.6 + 1.1.2 + 5.3.8 4.7.0 - 10.25.0 - 0.8.13 + 12.1.2 + 0.8.14 0.3.4 1.0.0 3.6.0 0.0.11 - 0.0.46 - + 0.0.47 + + + Apache License, Version 2.0 + https://www.apache.org/licenses/LICENSE-2.0 + + + @@ -55,7 +56,7 @@ org.springframework.boot - spring-boot-starter-web + spring-boot-starter-thymeleaf org.springframework.boot @@ -63,20 +64,28 @@ org.springframework.boot - spring-boot-starter-thymeleaf - - - org.springframework.boot - spring-boot-starter-test - test + spring-boot-starter-webmvc + + + + javax.cache + cache-api + + + jakarta.xml.bind + jakarta.xml.bind-api - com.h2database h2 runtime + + com.github.ben-manes.caffeine + caffeine + runtime + com.mysql mysql-connector-j @@ -87,37 +96,44 @@ postgresql runtime - - - - javax.cache - cache-api - - - com.github.ben-manes.caffeine - caffeine - - - org.webjars webjars-locator-lite ${webjars-locator.version} + runtime org.webjars.npm bootstrap ${webjars-bootstrap.version} + runtime org.webjars.npm font-awesome ${webjars-font-awesome.version} + runtime org.springframework.boot spring-boot-devtools + true + + + + org.springframework.boot + spring-boot-starter-data-jpa-test + test + + + org.springframework.boot + spring-boot-starter-restclient-test + test + + + org.springframework.boot + spring-boot-starter-webmvc-test test @@ -132,20 +148,14 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test org.testcontainers - mysql + testcontainers-mysql test - - - jakarta.xml.bind - jakarta.xml.bind-api - - @@ -278,53 +288,8 @@ org.cyclonedx cyclonedx-maven-plugin - - - - Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 - - - - - - - true - - spring-snapshots - Spring Snapshots - https://repo.spring.io/snapshot - - - - false - - spring-milestones - Spring Milestones - https://repo.spring.io/milestone - - - - - - true - - spring-snapshots - Spring Snapshots - https://repo.spring.io/snapshot - - - - false - - spring-milestones - Spring Milestones - https://repo.spring.io/milestone - - - css @@ -354,7 +319,6 @@ - com.gitlab.haynes libsass-maven-plugin diff --git a/src/main/java/org/springframework/samples/petclinic/model/NamedEntity.java b/src/main/java/org/springframework/samples/petclinic/model/NamedEntity.java index d4be03e9e..61e882a95 100644 --- a/src/main/java/org/springframework/samples/petclinic/model/NamedEntity.java +++ b/src/main/java/org/springframework/samples/petclinic/model/NamedEntity.java @@ -30,7 +30,7 @@ import jakarta.validation.constraints.NotBlank; @MappedSuperclass public class NamedEntity extends BaseEntity { - @Column(name = "name") + @Column @NotBlank private String name; @@ -44,7 +44,8 @@ public class NamedEntity extends BaseEntity { @Override public String toString() { - return this.getName(); + String name = this.getName(); + return (name != null) ? name : ""; } } diff --git a/src/main/java/org/springframework/samples/petclinic/model/Person.java b/src/main/java/org/springframework/samples/petclinic/model/Person.java index 7ee1f0397..30b5829d8 100644 --- a/src/main/java/org/springframework/samples/petclinic/model/Person.java +++ b/src/main/java/org/springframework/samples/petclinic/model/Person.java @@ -27,11 +27,11 @@ import jakarta.validation.constraints.NotBlank; @MappedSuperclass public class Person extends BaseEntity { - @Column(name = "first_name") + @Column @NotBlank private String firstName; - @Column(name = "last_name") + @Column @NotBlank private String lastName; diff --git a/src/main/java/org/springframework/samples/petclinic/owner/Owner.java b/src/main/java/org/springframework/samples/petclinic/owner/Owner.java index 63e3acc7a..480a7a690 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/Owner.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/Owner.java @@ -17,6 +17,7 @@ package org.springframework.samples.petclinic.owner; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import org.springframework.core.style.ToStringCreator; import org.springframework.samples.petclinic.model.Person; @@ -47,15 +48,15 @@ import jakarta.validation.constraints.NotBlank; @Table(name = "owners") public class Owner extends Person { - @Column(name = "address") + @Column @NotBlank private String address; - @Column(name = "city") + @Column @NotBlank private String city; - @Column(name = "telephone") + @Column @NotBlank @Pattern(regexp = "\\d{10}", message = "{telephone.invalid}") private String telephone; @@ -117,7 +118,7 @@ public class Owner extends Person { for (Pet pet : getPets()) { if (!pet.isNew()) { Integer compId = pet.getId(); - if (compId.equals(id)) { + if (Objects.equals(compId, id)) { return pet; } } diff --git a/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java b/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java index 1348457ee..199ca3611 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java @@ -16,6 +16,7 @@ package org.springframework.samples.petclinic.owner; import java.util.List; +import java.util.Objects; import java.util.Optional; import org.springframework.data.domain.Page; @@ -34,6 +35,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.ModelAndView; import jakarta.validation.Valid; + import org.springframework.web.servlet.mvc.support.RedirectAttributes; /** @@ -93,12 +95,13 @@ class OwnerController { public String processFindForm(@RequestParam(defaultValue = "1") int page, Owner owner, BindingResult result, Model model) { // allow parameterless GET request for /owners to return all records - if (owner.getLastName() == null) { - owner.setLastName(""); // empty string signifies broadest possible search + String lastName = owner.getLastName(); + if (lastName == null) { + lastName = ""; // empty string signifies broadest possible search } // find owners by last name - Page ownersResults = findPaginatedForOwnersLastName(page, owner.getLastName()); + Page ownersResults = findPaginatedForOwnersLastName(page, lastName); if (ownersResults.isEmpty()) { // no owners found result.rejectValue("lastName", "notFound", "not found"); @@ -143,7 +146,7 @@ class OwnerController { return VIEWS_OWNER_CREATE_OR_UPDATE_FORM; } - if (owner.getId() != ownerId) { + if (!Objects.equals(owner.getId(), ownerId)) { result.rejectValue("id", "mismatch", "The owner ID in the form does not match the URL."); redirectAttributes.addFlashAttribute("error", "Owner ID mismatch. Please try again."); return "redirect:/owners/{ownerId}/edit"; diff --git a/src/main/java/org/springframework/samples/petclinic/owner/OwnerRepository.java b/src/main/java/org/springframework/samples/petclinic/owner/OwnerRepository.java index 9384b318e..d2b3dde40 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/OwnerRepository.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/OwnerRepository.java @@ -15,14 +15,11 @@ */ package org.springframework.samples.petclinic.owner; -import java.util.List; import java.util.Optional; -import jakarta.annotation.Nonnull; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; /** * Repository class for Owner domain objects. All method names are compliant @@ -60,6 +57,6 @@ public interface OwnerRepository extends JpaRepository { * @throws IllegalArgumentException if the id is null (assuming null is not a valid * input for id) */ - Optional findById(@Nonnull Integer id); + Optional findById(Integer id); } diff --git a/src/main/java/org/springframework/samples/petclinic/owner/Pet.java b/src/main/java/org/springframework/samples/petclinic/owner/Pet.java index 1945f9b67..4f8409ef2 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/Pet.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/Pet.java @@ -45,7 +45,7 @@ import jakarta.persistence.Table; @Table(name = "pets") public class Pet extends NamedEntity { - @Column(name = "birth_date") + @Column @DateTimeFormat(pattern = "yyyy-MM-dd") private LocalDate birthDate; diff --git a/src/main/java/org/springframework/samples/petclinic/owner/PetController.java b/src/main/java/org/springframework/samples/petclinic/owner/PetController.java index 56707d99f..8398e4f13 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/PetController.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/PetController.java @@ -17,10 +17,12 @@ package org.springframework.samples.petclinic.owner; import java.time.LocalDate; import java.util.Collection; +import java.util.Objects; import java.util.Optional; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; +import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.validation.BindingResult; import org.springframework.web.bind.WebDataBinder; @@ -32,6 +34,7 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import jakarta.validation.Valid; + import org.springframework.web.servlet.mvc.support.RedirectAttributes; /** @@ -135,7 +138,7 @@ class PetController { // checking if the pet name already exists for the owner if (StringUtils.hasText(petName)) { Pet existingPet = owner.getPet(petName, false); - if (existingPet != null && !existingPet.getId().equals(pet.getId())) { + if (existingPet != null && !Objects.equals(existingPet.getId(), pet.getId())) { result.rejectValue("name", "duplicate", "already exists"); } } @@ -160,7 +163,9 @@ class PetController { * @param pet The pet with updated details */ private void updatePetDetails(Owner owner, Pet pet) { - Pet existingPet = owner.getPet(pet.getId()); + Integer id = pet.getId(); + Assert.state(id != null, "'pet.getId()' must not be null"); + Pet existingPet = owner.getPet(id); if (existingPet != null) { // Update existing pet's properties existingPet.setName(pet.getName()); diff --git a/src/main/java/org/springframework/samples/petclinic/owner/PetTypeFormatter.java b/src/main/java/org/springframework/samples/petclinic/owner/PetTypeFormatter.java index 7ddc6e923..b62751c97 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/PetTypeFormatter.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/PetTypeFormatter.java @@ -21,6 +21,7 @@ import org.springframework.stereotype.Component; import java.text.ParseException; import java.util.Collection; import java.util.Locale; +import java.util.Objects; /** * Instructs Spring MVC on how to parse and print elements of type 'PetType'. Starting @@ -43,14 +44,15 @@ public class PetTypeFormatter implements Formatter { @Override public String print(PetType petType, Locale locale) { - return petType.getName(); + String name = petType.getName(); + return (name != null) ? name : ""; } @Override public PetType parse(String text, Locale locale) throws ParseException { Collection findPetTypes = this.types.findPetTypes(); for (PetType type : findPetTypes) { - if (type.getName().equals(text)) { + if (Objects.equals(type.getName(), text)) { return type; } } diff --git a/src/main/java/org/springframework/samples/petclinic/owner/PetTypeRepository.java b/src/main/java/org/springframework/samples/petclinic/owner/PetTypeRepository.java index 0412948eb..0b0d7bbd0 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/PetTypeRepository.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/PetTypeRepository.java @@ -17,11 +17,7 @@ package org.springframework.samples.petclinic.owner; import java.util.List; -import java.util.Optional; -import jakarta.annotation.Nonnull; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; diff --git a/src/main/java/org/springframework/samples/petclinic/owner/VisitController.java b/src/main/java/org/springframework/samples/petclinic/owner/VisitController.java index 21c1823a5..cc3e3ce1a 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/VisitController.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/VisitController.java @@ -67,6 +67,10 @@ class VisitController { "Owner not found with id: " + ownerId + ". Please ensure the ID is correct ")); Pet pet = owner.getPet(petId); + if (pet == null) { + throw new IllegalArgumentException( + "Pet with id " + petId + " not found for owner with id " + ownerId + "."); + } model.put("pet", pet); model.put("owner", owner); diff --git a/src/main/java/org/springframework/samples/petclinic/owner/package-info.java b/src/main/java/org/springframework/samples/petclinic/owner/package-info.java new file mode 100644 index 000000000..9e2a3a4ec --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/owner/package-info.java @@ -0,0 +1,16 @@ +/* + * Copyright 2012-2025 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.owner; diff --git a/src/main/java/org/springframework/samples/petclinic/package-info.java b/src/main/java/org/springframework/samples/petclinic/package-info.java new file mode 100644 index 000000000..8c54417d9 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/package-info.java @@ -0,0 +1,16 @@ +/* + * Copyright 2012-2025 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; diff --git a/src/main/java/org/springframework/samples/petclinic/system/CacheConfiguration.java b/src/main/java/org/springframework/samples/petclinic/system/CacheConfiguration.java index 1382f3aea..13cb74301 100644 --- a/src/main/java/org/springframework/samples/petclinic/system/CacheConfiguration.java +++ b/src/main/java/org/springframework/samples/petclinic/system/CacheConfiguration.java @@ -16,7 +16,7 @@ package org.springframework.samples.petclinic.system; -import org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer; +import org.springframework.boot.cache.autoconfigure.JCacheManagerCustomizer; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/src/main/java/org/springframework/samples/petclinic/system/package-info.java b/src/main/java/org/springframework/samples/petclinic/system/package-info.java new file mode 100644 index 000000000..7d1468177 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/system/package-info.java @@ -0,0 +1,16 @@ +/* + * Copyright 2012-2025 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; diff --git a/src/main/java/org/springframework/samples/petclinic/vet/package-info.java b/src/main/java/org/springframework/samples/petclinic/vet/package-info.java new file mode 100644 index 000000000..544db4c7c --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/vet/package-info.java @@ -0,0 +1,16 @@ +/* + * Copyright 2012-2025 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.vet; diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 6ed985654..630c1145a 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -9,6 +9,7 @@ spring.thymeleaf.mode=HTML # JPA spring.jpa.hibernate.ddl-auto=none spring.jpa.open-in-view=false +spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategySnakeCaseImpl # Internationalization spring.messages.basename=messages/messages diff --git a/src/main/resources/db/mysql/user.sql b/src/main/resources/db/mysql/user.sql index d2c7b88a0..4cdf825e3 100644 --- a/src/main/resources/db/mysql/user.sql +++ b/src/main/resources/db/mysql/user.sql @@ -4,4 +4,8 @@ ALTER DATABASE petclinic DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci; -GRANT ALL PRIVILEGES ON petclinic.* TO 'petclinic'@'%' IDENTIFIED BY 'petclinic'; +CREATE USER IF NOT EXISTS 'petclinic'@'%' IDENTIFIED BY 'petclinic'; + +GRANT ALL PRIVILEGES ON petclinic.* TO 'petclinic'@'%'; + +FLUSH PRIVILEGES; diff --git a/src/main/resources/messages/messages.properties b/src/main/resources/messages/messages.properties index 7b8b5dfd8..193565895 100644 --- a/src/main/resources/messages/messages.properties +++ b/src/main/resources/messages/messages.properties @@ -46,3 +46,6 @@ visitDate=Visit Date editOwner=Edit Owner addNewPet=Add New Pet petsAndVisits=Pets and Visits +error.404=The requested page was not found. +error.500=An internal server error occurred. +error.general=An unexpected error occurred. diff --git a/src/main/resources/messages/messages_de.properties b/src/main/resources/messages/messages_de.properties index 8d208410c..89a08eaad 100644 --- a/src/main/resources/messages/messages_de.properties +++ b/src/main/resources/messages/messages_de.properties @@ -46,3 +46,6 @@ visitDate=Besuchsdatum editOwner=Besitzer bearbeiten addNewPet=Neues Haustier hinzufügen petsAndVisits=Haustiere und Besuche +error.404=Die angeforderte Seite wurde nicht gefunden. +error.500=Ein interner Serverfehler ist aufgetreten. +error.general=Ein unerwarteter Fehler ist aufgetreten. diff --git a/src/main/resources/messages/messages_es.properties b/src/main/resources/messages/messages_es.properties index 91d5c9ebd..911d7337f 100644 --- a/src/main/resources/messages/messages_es.properties +++ b/src/main/resources/messages/messages_es.properties @@ -46,3 +46,6 @@ visitDate=Fecha de visita editOwner=Editar propietario addNewPet=Agregar nueva mascota petsAndVisits=Mascotas y visitas +error.404=La página solicitada no fue encontrada. +error.500=Ocurrió un error interno del servidor. +error.general=Ocurrió un error inesperado. diff --git a/src/main/resources/messages/messages_fa.properties b/src/main/resources/messages/messages_fa.properties index 2566b8949..6d0994a92 100644 --- a/src/main/resources/messages/messages_fa.properties +++ b/src/main/resources/messages/messages_fa.properties @@ -46,3 +46,6 @@ visitDate=تاریخ ویزیت editOwner=ویرایش مالک addNewPet=افزودن حیوان خانگی جدید petsAndVisits=حیوانات و ویزیت‌ها +error.404=صفحه درخواستی پیدا نشد. +error.500=خطای داخلی سرور رخ داد. +error.general=خطای غیرمنتظره‌ای رخ داد. diff --git a/src/main/resources/messages/messages_ko.properties b/src/main/resources/messages/messages_ko.properties index ca3ae5a05..6e2f4880a 100644 --- a/src/main/resources/messages/messages_ko.properties +++ b/src/main/resources/messages/messages_ko.properties @@ -46,3 +46,6 @@ visitDate=방문 날짜 editOwner=소유자 수정 addNewPet=새 반려동물 추가 petsAndVisits=반려동물 및 방문 +error.404=요청하신 페이지를 찾을 수 없습니다. +error.500=서버 내부 오류가 발생했습니다. +error.general=알 수 없는 오류가 발생했습니다. diff --git a/src/main/resources/messages/messages_pt.properties b/src/main/resources/messages/messages_pt.properties index 03f5cd531..7eea4b9d1 100644 --- a/src/main/resources/messages/messages_pt.properties +++ b/src/main/resources/messages/messages_pt.properties @@ -46,3 +46,6 @@ visitDate=Data da visita editOwner=Editar proprietário addNewPet=Adicionar novo animal petsAndVisits=Animais e visitas +error.404=A página solicitada não foi encontrada. +error.500=Ocorreu um erro interno no servidor. +error.general=Ocorreu um erro inesperado. diff --git a/src/main/resources/messages/messages_ru.properties b/src/main/resources/messages/messages_ru.properties index b9ed998be..f06d2cb6c 100644 --- a/src/main/resources/messages/messages_ru.properties +++ b/src/main/resources/messages/messages_ru.properties @@ -46,3 +46,6 @@ visitDate=Дата визита editOwner=Редактировать владельца addNewPet=Добавить нового питомца petsAndVisits=Питомцы и визиты +error.404=Запрашиваемая страница не найдена. +error.500=Произошла внутренняя ошибка сервера. +error.general=Произошла непредвиденная ошибка. diff --git a/src/main/resources/messages/messages_tr.properties b/src/main/resources/messages/messages_tr.properties index ff9ffdabd..2c806f9e6 100644 --- a/src/main/resources/messages/messages_tr.properties +++ b/src/main/resources/messages/messages_tr.properties @@ -46,3 +46,6 @@ visitDate=Ziyaret Tarihi editOwner=Sahibi Düzenle addNewPet=Yeni Evcil Hayvan Ekle petsAndVisits=Evcil Hayvanlar ve Ziyaretler +error.404=İstenen sayfa bulunamadı. +error.500=Sunucuda dahili bir hata oluştu. +error.general=Beklenmeyen bir hata oluştu. diff --git a/src/main/resources/static/resources/css/petclinic.css b/src/main/resources/static/resources/css/petclinic.css index 22e3e50ea..4b701b13b 100644 --- a/src/main/resources/static/resources/css/petclinic.css +++ b/src/main/resources/static/resources/css/petclinic.css @@ -12,7 +12,7 @@ * limitations under the License. */ /*! - * Bootstrap v5.3.6 (https://getbootstrap.com/) + * Bootstrap v5.3.8 (https://getbootstrap.com/) * Copyright 2011-2025 The Bootstrap Authors * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) */ @@ -479,6 +479,9 @@ legend { [type="search"] { -webkit-appearance: textfield; outline-offset: -2px; } + [type="search"]::-webkit-search-cancel-button { + cursor: pointer; + filter: grayscale(1); } /* rtl:raw: [type="tel"], @@ -5072,6 +5075,7 @@ textarea.form-control-lg { .spinner-grow, .spinner-border { display: inline-block; + flex-shrink: 0; width: var(--bs-spinner-width); height: var(--bs-spinner-height); vertical-align: var(--bs-spinner-vertical-align); diff --git a/src/main/resources/templates/error.html b/src/main/resources/templates/error.html index 4d76d8d16..5f1d6f3ab 100644 --- a/src/main/resources/templates/error.html +++ b/src/main/resources/templates/error.html @@ -4,8 +4,18 @@ +

Something happened...

+ + +

+ The requested page was not found. + An internal server error occurred. + An unexpected error occurred. +

+ +

Exception message

- \ No newline at end of file + diff --git a/src/test/java/org/springframework/samples/petclinic/MySqlIntegrationTests.java b/src/test/java/org/springframework/samples/petclinic/MySqlIntegrationTests.java index a78e2e6e1..0cacc580a 100644 --- a/src/test/java/org/springframework/samples/petclinic/MySqlIntegrationTests.java +++ b/src/test/java/org/springframework/samples/petclinic/MySqlIntegrationTests.java @@ -21,11 +21,11 @@ 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.restclient.RestTemplateBuilder; 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; @@ -33,9 +33,9 @@ import org.springframework.samples.petclinic.vet.VetRepository; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.aot.DisabledInAotMode; import org.springframework.web.client.RestTemplate; -import org.testcontainers.containers.MySQLContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.mysql.MySQLContainer; import org.testcontainers.utility.DockerImageName; @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) @@ -47,7 +47,7 @@ class MySqlIntegrationTests { @ServiceConnection @Container - static MySQLContainer container = new MySQLContainer<>(DockerImageName.parse("mysql:9.2")); + static MySQLContainer container = new MySQLContainer(DockerImageName.parse("mysql:9.5")); @LocalServerPort int port; diff --git a/src/test/java/org/springframework/samples/petclinic/MysqlTestApplication.java b/src/test/java/org/springframework/samples/petclinic/MysqlTestApplication.java index 2a4ea8e06..e18b6cbaf 100644 --- a/src/test/java/org/springframework/samples/petclinic/MysqlTestApplication.java +++ b/src/test/java/org/springframework/samples/petclinic/MysqlTestApplication.java @@ -21,7 +21,7 @@ import org.springframework.boot.testcontainers.service.connection.ServiceConnect import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; -import org.testcontainers.containers.MySQLContainer; +import org.testcontainers.mysql.MySQLContainer; import org.testcontainers.utility.DockerImageName; /** @@ -35,8 +35,8 @@ public class MysqlTestApplication { @ServiceConnection @Profile("mysql") @Bean - static MySQLContainer container() { - return new MySQLContainer<>(DockerImageName.parse("mysql:9.2")); + static MySQLContainer container() { + return new MySQLContainer(DockerImageName.parse("mysql:9.5")); } public static void main(String[] args) { diff --git a/src/test/java/org/springframework/samples/petclinic/PetClinicIntegrationTests.java b/src/test/java/org/springframework/samples/petclinic/PetClinicIntegrationTests.java index 617af9652..8c0451dfd 100644 --- a/src/test/java/org/springframework/samples/petclinic/PetClinicIntegrationTests.java +++ b/src/test/java/org/springframework/samples/petclinic/PetClinicIntegrationTests.java @@ -21,10 +21,10 @@ import static org.assertj.core.api.Assertions.assertThat; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; +import org.springframework.boot.restclient.RestTemplateBuilder; 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.http.HttpStatus; import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; diff --git a/src/test/java/org/springframework/samples/petclinic/PostgresIntegrationTests.java b/src/test/java/org/springframework/samples/petclinic/PostgresIntegrationTests.java index 709d33e66..f3c5181e7 100644 --- a/src/test/java/org/springframework/samples/petclinic/PostgresIntegrationTests.java +++ b/src/test/java/org/springframework/samples/petclinic/PostgresIntegrationTests.java @@ -32,10 +32,10 @@ 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.restclient.RestTemplateBuilder; 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; diff --git a/src/test/java/org/springframework/samples/petclinic/owner/OwnerControllerTests.java b/src/test/java/org/springframework/samples/petclinic/owner/OwnerControllerTests.java index 7570ae132..bcab19749 100644 --- a/src/test/java/org/springframework/samples/petclinic/owner/OwnerControllerTests.java +++ b/src/test/java/org/springframework/samples/petclinic/owner/OwnerControllerTests.java @@ -20,7 +20,7 @@ import org.junit.jupiter.api.BeforeEach; 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.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; diff --git a/src/test/java/org/springframework/samples/petclinic/owner/PetControllerTests.java b/src/test/java/org/springframework/samples/petclinic/owner/PetControllerTests.java index d60636f1f..391bb3dbb 100644 --- a/src/test/java/org/springframework/samples/petclinic/owner/PetControllerTests.java +++ b/src/test/java/org/springframework/samples/petclinic/owner/PetControllerTests.java @@ -21,11 +21,9 @@ import org.junit.jupiter.api.Nested; 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.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.FilterType; -import org.springframework.samples.petclinic.owner.PetTypeRepository; -import org.springframework.samples.petclinic.owner.OwnerRepository; import org.springframework.test.context.aot.DisabledInAotMode; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; diff --git a/src/test/java/org/springframework/samples/petclinic/owner/VisitControllerTests.java b/src/test/java/org/springframework/samples/petclinic/owner/VisitControllerTests.java index e142a8975..781de3894 100644 --- a/src/test/java/org/springframework/samples/petclinic/owner/VisitControllerTests.java +++ b/src/test/java/org/springframework/samples/petclinic/owner/VisitControllerTests.java @@ -27,7 +27,7 @@ import org.junit.jupiter.api.BeforeEach; 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.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest; import org.springframework.test.context.aot.DisabledInAotMode; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; diff --git a/src/test/java/org/springframework/samples/petclinic/service/ClinicServiceTests.java b/src/test/java/org/springframework/samples/petclinic/service/ClinicServiceTests.java index 4c78ab317..ddc5bab1e 100644 --- a/src/test/java/org/springframework/samples/petclinic/service/ClinicServiceTests.java +++ b/src/test/java/org/springframework/samples/petclinic/service/ClinicServiceTests.java @@ -24,16 +24,16 @@ import java.util.Optional; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace; -import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest; +import org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase; +import org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase.Replace; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.samples.petclinic.owner.Owner; import org.springframework.samples.petclinic.owner.OwnerRepository; -import org.springframework.samples.petclinic.owner.PetTypeRepository; import org.springframework.samples.petclinic.owner.Pet; import org.springframework.samples.petclinic.owner.PetType; +import org.springframework.samples.petclinic.owner.PetTypeRepository; import org.springframework.samples.petclinic.owner.Visit; import org.springframework.samples.petclinic.vet.Vet; import org.springframework.samples.petclinic.vet.VetRepository; @@ -82,7 +82,7 @@ class ClinicServiceTests { @Autowired protected VetRepository vets; - Pageable pageable; + private final Pageable pageable = Pageable.unpaged(); @Test void shouldFindOwnersByLastName() { diff --git a/src/test/java/org/springframework/samples/petclinic/service/EntityUtils.java b/src/test/java/org/springframework/samples/petclinic/service/EntityUtils.java index 180ef07f1..d1ce9d74f 100644 --- a/src/test/java/org/springframework/samples/petclinic/service/EntityUtils.java +++ b/src/test/java/org/springframework/samples/petclinic/service/EntityUtils.java @@ -43,7 +43,7 @@ public abstract class EntityUtils { public static T getById(Collection entities, Class entityClass, int entityId) throws ObjectRetrievalFailureException { for (T entity : entities) { - if (entity.getId() == entityId && entityClass.isInstance(entity)) { + if (entity.getId() != null && entity.getId() == entityId && entityClass.isInstance(entity)) { return entity; } } diff --git a/src/test/java/org/springframework/samples/petclinic/system/CrashControllerIntegrationTests.java b/src/test/java/org/springframework/samples/petclinic/system/CrashControllerIntegrationTests.java index ed8b0819a..509585129 100644 --- a/src/test/java/org/springframework/samples/petclinic/system/CrashControllerIntegrationTests.java +++ b/src/test/java/org/springframework/samples/petclinic/system/CrashControllerIntegrationTests.java @@ -26,11 +26,12 @@ 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.hibernate.autoconfigure.HibernateJpaAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.DataSourceTransactionManagerAutoConfiguration; +import org.springframework.boot.resttestclient.TestRestTemplate; +import org.springframework.boot.resttestclient.autoconfigure.AutoConfigureTestRestTemplate; 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; @@ -47,7 +48,8 @@ import org.springframework.http.ResponseEntity; */ // 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" }) + properties = { "spring.web.error.include-message=ALWAYS", "management.endpoints.access.default=none" }) +@AutoConfigureTestRestTemplate class CrashControllerIntegrationTests { @Value(value = "${local.server.port}") diff --git a/src/test/java/org/springframework/samples/petclinic/vet/VetControllerTests.java b/src/test/java/org/springframework/samples/petclinic/vet/VetControllerTests.java index f295cd68a..b8618c35d 100644 --- a/src/test/java/org/springframework/samples/petclinic/vet/VetControllerTests.java +++ b/src/test/java/org/springframework/samples/petclinic/vet/VetControllerTests.java @@ -21,7 +21,7 @@ import org.junit.jupiter.api.BeforeEach; 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.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.http.MediaType;