├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .editorconfig ├── .gitattributes ├── .github ├── dco.yml └── workflows │ ├── deploy-and-test-cluster.yml │ ├── gradle-build.yml │ └── maven-build.yml ├── .gitignore ├── .gitpod.yml ├── .mvn └── wrapper │ └── maven-wrapper.properties ├── LICENSE.txt ├── README.md ├── build.gradle ├── docker-compose.yml ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── k8s ├── db.yml └── petclinic.yml ├── mvnw ├── mvnw.cmd ├── pom.xml ├── settings.gradle └── src ├── checkstyle ├── nohttp-checkstyle-suppressions.xml └── nohttp-checkstyle.xml ├── main ├── java │ └── org │ │ └── springframework │ │ └── samples │ │ └── petclinic │ │ ├── PetClinicApplication.java │ │ ├── PetClinicRuntimeHints.java │ │ ├── model │ │ ├── BaseEntity.java │ │ ├── NamedEntity.java │ │ ├── Person.java │ │ └── package-info.java │ │ ├── owner │ │ ├── Owner.java │ │ ├── OwnerController.java │ │ ├── OwnerRepository.java │ │ ├── Pet.java │ │ ├── PetController.java │ │ ├── PetType.java │ │ ├── PetTypeFormatter.java │ │ ├── PetTypeRepository.java │ │ ├── PetValidator.java │ │ ├── Visit.java │ │ └── VisitController.java │ │ ├── system │ │ ├── CacheConfiguration.java │ │ ├── CrashController.java │ │ ├── WebConfiguration.java │ │ └── WelcomeController.java │ │ └── vet │ │ ├── Specialty.java │ │ ├── Vet.java │ │ ├── VetController.java │ │ ├── VetRepository.java │ │ └── Vets.java ├── resources │ ├── application-mysql.properties │ ├── application-postgres.properties │ ├── application.properties │ ├── banner.txt │ ├── db │ │ ├── h2 │ │ │ ├── data.sql │ │ │ └── schema.sql │ │ ├── hsqldb │ │ │ ├── data.sql │ │ │ └── schema.sql │ │ ├── mysql │ │ │ ├── data.sql │ │ │ ├── petclinic_db_setup_mysql.txt │ │ │ ├── schema.sql │ │ │ └── user.sql │ │ └── postgres │ │ │ ├── data.sql │ │ │ ├── petclinic_db_setup_postgres.txt │ │ │ └── schema.sql │ ├── messages │ │ ├── messages.properties │ │ ├── messages_de.properties │ │ ├── messages_en.properties │ │ ├── messages_es.properties │ │ ├── messages_fa.properties │ │ ├── messages_ko.properties │ │ ├── messages_pt.properties │ │ ├── messages_ru.properties │ │ └── messages_tr.properties │ ├── static │ │ └── resources │ │ │ ├── css │ │ │ └── petclinic.css │ │ │ ├── fonts │ │ │ ├── montserrat-webfont.eot │ │ │ ├── montserrat-webfont.svg │ │ │ ├── montserrat-webfont.ttf │ │ │ ├── montserrat-webfont.woff │ │ │ ├── varela_round-webfont.eot │ │ │ ├── varela_round-webfont.svg │ │ │ ├── varela_round-webfont.ttf │ │ │ └── varela_round-webfont.woff │ │ │ └── images │ │ │ ├── favicon.png │ │ │ ├── pets.png │ │ │ ├── spring-logo-dataflow-mobile.png │ │ │ ├── spring-logo-dataflow.png │ │ │ └── spring-logo.svg │ └── templates │ │ ├── error.html │ │ ├── fragments │ │ ├── inputField.html │ │ ├── layout.html │ │ └── selectField.html │ │ ├── owners │ │ ├── createOrUpdateOwnerForm.html │ │ ├── findOwners.html │ │ ├── ownerDetails.html │ │ └── ownersList.html │ │ ├── pets │ │ ├── createOrUpdatePetForm.html │ │ └── createOrUpdateVisitForm.html │ │ ├── vets │ │ └── vetList.html │ │ └── welcome.html └── scss │ ├── header.scss │ ├── petclinic.scss │ ├── responsive.scss │ └── typography.scss └── test ├── java └── org │ └── springframework │ └── samples │ └── petclinic │ ├── MySqlIntegrationTests.java │ ├── MysqlTestApplication.java │ ├── PetClinicIntegrationTests.java │ ├── PostgresIntegrationTests.java │ ├── model │ └── ValidatorTests.java │ ├── owner │ ├── OwnerControllerTests.java │ ├── PetControllerTests.java │ ├── PetTypeFormatterTests.java │ ├── PetValidatorTests.java │ └── VisitControllerTests.java │ ├── service │ ├── ClinicServiceTests.java │ └── EntityUtils.java │ ├── system │ ├── CrashControllerIntegrationTests.java │ ├── CrashControllerTests.java │ └── I18nPropertiesSyncTest.java │ └── vet │ ├── VetControllerTests.java │ └── VetTests.java └── jmeter └── petclinic_test_plan.jmx /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # Not actually used by the devcontainer, but it is used by gitpod 2 | ARG VARIANT=17-bullseye 3 | FROM mcr.microsoft.com/vscode/devcontainers/java:0-${VARIANT} 4 | ARG NODE_VERSION="none" 5 | RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi 6 | ARG USER=vscode 7 | VOLUME /home/$USER/.m2 8 | VOLUME /home/$USER/.gradle 9 | ARG JAVA_VERSION=17.0.7-ms 10 | RUN sudo mkdir /home/$USER/.m2 /home/$USER/.gradle && sudo chown $USER:$USER /home/$USER/.m2 /home/$USER/.gradle 11 | RUN bash -lc '. /usr/local/sdkman/bin/sdkman-init.sh && sdk install java $JAVA_VERSION && sdk use java $JAVA_VERSION' -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Java", 3 | "image": "mcr.microsoft.com/devcontainers/base:ubuntu", 4 | "features": { 5 | "ghcr.io/devcontainers/features/java:1": { 6 | "version": "21-oracle", 7 | "jdkDistro": "oracle" 8 | }, 9 | "ghcr.io/devcontainers/features/azure-cli:1": {}, 10 | "ghcr.io/devcontainers/features/docker-in-docker:2": {}, 11 | "ghcr.io/devcontainers/features/github-cli:1": {} 12 | }, 13 | 14 | "customizations": { 15 | "vscode": { 16 | "settings": {}, 17 | "extensions": [ 18 | "redhat.vscode-xml", 19 | "visualstudioexptteam.vscodeintellicode", 20 | "vscjava.vscode-java-pack" 21 | ] 22 | } 23 | }, 24 | "remoteUser": "vscode" 25 | } 26 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | insert_final_newline = true 8 | indent_style = space 9 | 10 | [*.{java,xml}] 11 | indent_size = 4 12 | trim_trailing_whitespace = true 13 | indent_style = tab 14 | tab_width = 4 15 | 16 | [{pom,wro}.xml] 17 | indent_size = 2 18 | indent_style = space 19 | 20 | [*.{html,sql,less}] 21 | indent_size = 2 22 | 23 | [*.gradle] 24 | indent_size = 2 25 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | mvnw text eol=lf 2 | *.java text eol=lf 3 | 4 | /gradlew text eol=lf 5 | *.bat text eol=crlf 6 | -------------------------------------------------------------------------------- /.github/dco.yml: -------------------------------------------------------------------------------- 1 | require: 2 | members: false -------------------------------------------------------------------------------- /.github/workflows/deploy-and-test-cluster.yml: -------------------------------------------------------------------------------- 1 | name: Deploy and Test Cluster 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | paths: 7 | - 'k8s/**' 8 | pull_request: 9 | branches: [main] 10 | paths: 11 | - 'k8s/**' 12 | 13 | jobs: 14 | deploy-and-test-cluster: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Check out the repository 18 | uses: actions/checkout@v2 19 | 20 | - name: Create k8s Kind Cluster 21 | uses: helm/kind-action@v1 22 | 23 | - name: Deploy application 24 | run: | 25 | kubectl apply -f k8s/ 26 | 27 | - name: Wait for Pods to be ready 28 | run: | 29 | kubectl wait --for=condition=ready pod -l app=demo-db --timeout=180s 30 | kubectl wait --for=condition=ready pod -l app=petclinic --timeout=180s 31 | 32 | -------------------------------------------------------------------------------- /.github/workflows/gradle-build.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Gradle, and cache/restore any dependencies to improve the workflow execution time 2 | # For more information see: https://docs.github.com/en/actions/use-cases-and-examples/building-and-testing/building-and-testing-java-with-gradle 3 | 4 | name: Java CI with Gradle 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | branches: [ main ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | java: [ '17' ] 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | - name: Set up JDK ${{matrix.java}} 23 | uses: actions/setup-java@v4 24 | with: 25 | java-version: ${{matrix.java}} 26 | distribution: 'adopt' 27 | cache: maven 28 | - name: Setup Gradle 29 | uses: gradle/actions/setup-gradle@v4 30 | - name: Build with Gradle 31 | run: ./gradlew build 32 | -------------------------------------------------------------------------------- /.github/workflows/maven-build.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time 2 | # For more information see: https://docs.github.com/en/actions/use-cases-and-examples/building-and-testing/building-and-testing-java-with-maven 3 | 4 | name: Java CI with Maven 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | branches: [ main ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | java: [ '17' ] 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | - name: Set up JDK ${{matrix.java}} 23 | uses: actions/setup-java@v4 24 | with: 25 | java-version: ${{matrix.java}} 26 | distribution: 'adopt' 27 | cache: maven 28 | - name: Build with Maven Wrapper 29 | run: ./mvnw -B verify 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | pom.xml.bak 3 | target/ 4 | !.mvn/wrapper/maven-wrapper.jar 5 | !**/src/main/**/target/ 6 | !**/src/test/**/target/ 7 | .gradle 8 | build/ 9 | !gradle/wrapper/gradle-wrapper.jar 10 | !**/src/main/**/build/ 11 | !**/src/test/**/build/ 12 | 13 | ### STS ### 14 | .attach_pid* 15 | .apt_generated 16 | .classpath 17 | .factorypath 18 | .project 19 | .settings 20 | .springBeans 21 | .sts4-cache 22 | bin/ 23 | !**/src/main/**/bin/ 24 | !**/src/test/**/bin/ 25 | 26 | ### IntelliJ IDEA ### 27 | .idea 28 | *.iws 29 | *.iml 30 | *.ipr 31 | out/ 32 | !**/src/main/**/out/ 33 | !**/src/test/**/out/ 34 | 35 | ### NetBeans ### 36 | /nbproject/private/ 37 | /nbbuild/ 38 | /dist/ 39 | /nbdist/ 40 | /.nb-gradle/ 41 | 42 | ### VS Code ### 43 | .vscode/ 44 | 45 | ### SDK Man ### 46 | .sdkmanrc 47 | 48 | ### CSS ### 49 | _site/ 50 | *.css 51 | !petclinic.css 52 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: 2 | file: ./.devcontainer/Dockerfile 3 | tasks: 4 | - before: sudo usermod -a -G sdkman gitpod && sudo usermod -a -G nvm gitpod && sudo chown -R gitpod /usr/local/sdkman /usr/local/share/nvm 5 | - init: ./mvnw install 6 | vscode: 7 | extensions: 8 | - vscjava.vscode-java-pack 9 | - redhat.vscode-xml -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | wrapperVersion=3.3.2 18 | distributionType=only-script 19 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip 20 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.springframework.boot' version '3.5.0' 4 | id 'io.spring.dependency-management' version '1.1.7' 5 | id 'org.graalvm.buildtools.native' version '0.10.6' 6 | id 'org.cyclonedx.bom' version '2.3.1' 7 | id 'io.spring.javaformat' version '0.0.46' 8 | id "io.spring.nohttp" version "0.0.11" 9 | } 10 | 11 | apply plugin: 'java' 12 | apply plugin: 'checkstyle' 13 | apply plugin: 'io.spring.javaformat' 14 | 15 | gradle.startParameter.excludedTaskNames += [ "checkFormatAot", "checkFormatAotTest" ] 16 | 17 | group = 'org.springframework.samples' 18 | version = '3.5.0' 19 | 20 | java { 21 | sourceCompatibility = JavaVersion.VERSION_17 22 | } 23 | 24 | repositories { 25 | mavenCentral() 26 | } 27 | 28 | ext.checkstyleVersion = "10.25.0" 29 | ext.springJavaformatCheckstyleVersion = "0.0.46" 30 | ext.webjarsLocatorLiteVersion = "1.1.0" 31 | ext.webjarsFontawesomeVersion = "4.7.0" 32 | ext.webjarsBootstrapVersion = "5.3.6" 33 | 34 | dependencies { 35 | // Workaround for AOT issue (https://github.com/spring-projects/spring-framework/pull/33949) --> 36 | implementation 'io.projectreactor:reactor-core' 37 | 38 | implementation 'org.springframework.boot:spring-boot-starter-cache' 39 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 40 | implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' 41 | implementation 'org.springframework.boot:spring-boot-starter-web' 42 | implementation 'org.springframework.boot:spring-boot-starter-validation' 43 | implementation 'javax.cache:cache-api' 44 | implementation 'jakarta.xml.bind:jakarta.xml.bind-api' 45 | runtimeOnly 'org.springframework.boot:spring-boot-starter-actuator' 46 | runtimeOnly "org.webjars:webjars-locator-lite:${webjarsLocatorLiteVersion}" 47 | runtimeOnly "org.webjars.npm:bootstrap:${webjarsBootstrapVersion}" 48 | runtimeOnly "org.webjars.npm:font-awesome:${webjarsFontawesomeVersion}" 49 | runtimeOnly 'com.github.ben-manes.caffeine:caffeine' 50 | runtimeOnly 'com.h2database:h2' 51 | runtimeOnly 'com.mysql:mysql-connector-j' 52 | runtimeOnly 'org.postgresql:postgresql' 53 | developmentOnly 'org.springframework.boot:spring-boot-devtools' 54 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 55 | testImplementation 'org.springframework.boot:spring-boot-testcontainers' 56 | testImplementation 'org.springframework.boot:spring-boot-docker-compose' 57 | testImplementation 'org.testcontainers:junit-jupiter' 58 | testImplementation 'org.testcontainers:mysql' 59 | checkstyle "io.spring.javaformat:spring-javaformat-checkstyle:${springJavaformatCheckstyleVersion}" 60 | checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}" 61 | } 62 | 63 | tasks.named('test') { 64 | useJUnitPlatform() 65 | } 66 | 67 | checkstyle { 68 | configDirectory = project.file('src/checkstyle') 69 | configFile = file('src/checkstyle/nohttp-checkstyle.xml') 70 | } 71 | 72 | checkstyleNohttp { 73 | configDirectory = project.file('src/checkstyle') 74 | configFile = file('src/checkstyle/nohttp-checkstyle.xml') 75 | } 76 | 77 | tasks.named("formatMain").configure { dependsOn("checkstyleMain") } 78 | tasks.named("formatMain").configure { dependsOn("checkstyleNohttp") } 79 | 80 | tasks.named("formatTest").configure { dependsOn("checkstyleTest") } 81 | tasks.named("formatTest").configure { dependsOn("checkstyleNohttp") } 82 | 83 | checkstyleAot.enabled = false 84 | checkstyleAotTest.enabled = false 85 | 86 | checkFormatAot.enabled = false 87 | checkFormatAotTest.enabled = false 88 | 89 | formatAot.enabled = false 90 | formatAotTest.enabled = false 91 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | mysql: 3 | image: mysql:9.2 4 | ports: 5 | - "3306:3306" 6 | environment: 7 | - MYSQL_ROOT_PASSWORD= 8 | - MYSQL_ALLOW_EMPTY_PASSWORD=true 9 | - MYSQL_USER=petclinic 10 | - MYSQL_PASSWORD=petclinic 11 | - MYSQL_DATABASE=petclinic 12 | volumes: 13 | - "./conf.d:/etc/mysql/conf.d:ro" 14 | postgres: 15 | image: postgres:17.5 16 | ports: 17 | - "5432:5432" 18 | environment: 19 | - POSTGRES_PASSWORD=petclinic 20 | - POSTGRES_USER=petclinic 21 | - POSTGRES_DB=petclinic 22 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-projects/spring-petclinic/cefaf55dd124d0635abfe857c3c99a3d3ea62017/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%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" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /k8s/db.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: demo-db 6 | type: servicebinding.io/postgresql 7 | stringData: 8 | type: "postgresql" 9 | provider: "postgresql" 10 | host: "demo-db" 11 | port: "5432" 12 | database: "petclinic" 13 | username: "user" 14 | password: "pass" 15 | 16 | --- 17 | apiVersion: v1 18 | kind: Service 19 | metadata: 20 | name: demo-db 21 | spec: 22 | ports: 23 | - port: 5432 24 | selector: 25 | app: demo-db 26 | 27 | --- 28 | apiVersion: apps/v1 29 | kind: Deployment 30 | metadata: 31 | name: demo-db 32 | labels: 33 | app: demo-db 34 | spec: 35 | selector: 36 | matchLabels: 37 | app: demo-db 38 | template: 39 | metadata: 40 | labels: 41 | app: demo-db 42 | spec: 43 | containers: 44 | - image: postgres:17.5 45 | name: postgresql 46 | env: 47 | - name: POSTGRES_USER 48 | valueFrom: 49 | secretKeyRef: 50 | name: demo-db 51 | key: username 52 | - name: POSTGRES_PASSWORD 53 | valueFrom: 54 | secretKeyRef: 55 | name: demo-db 56 | key: password 57 | - name: POSTGRES_DB 58 | valueFrom: 59 | secretKeyRef: 60 | name: demo-db 61 | key: database 62 | ports: 63 | - containerPort: 5432 64 | name: postgresql 65 | livenessProbe: 66 | tcpSocket: 67 | port: postgresql 68 | readinessProbe: 69 | tcpSocket: 70 | port: postgresql 71 | startupProbe: 72 | tcpSocket: 73 | port: postgresql 74 | -------------------------------------------------------------------------------- /k8s/petclinic.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: petclinic 6 | spec: 7 | type: NodePort 8 | ports: 9 | - port: 80 10 | targetPort: 8080 11 | selector: 12 | app: petclinic 13 | 14 | --- 15 | apiVersion: apps/v1 16 | kind: Deployment 17 | metadata: 18 | name: petclinic 19 | labels: 20 | app: petclinic 21 | spec: 22 | replicas: 1 23 | selector: 24 | matchLabels: 25 | app: petclinic 26 | template: 27 | metadata: 28 | labels: 29 | app: petclinic 30 | spec: 31 | containers: 32 | - name: workload 33 | image: dsyer/petclinic 34 | env: 35 | - name: SPRING_PROFILES_ACTIVE 36 | value: postgres 37 | - name: SERVICE_BINDING_ROOT 38 | value: /bindings 39 | - name: SPRING_APPLICATION_JSON 40 | value: | 41 | { 42 | "management.endpoint.health.probes.add-additional-paths": true 43 | } 44 | ports: 45 | - name: http 46 | containerPort: 8080 47 | livenessProbe: 48 | httpGet: 49 | path: /livez 50 | port: http 51 | readinessProbe: 52 | httpGet: 53 | path: /readyz 54 | port: http 55 | volumeMounts: 56 | - mountPath: /bindings/secret 57 | name: binding 58 | readOnly: true 59 | volumes: 60 | - name: binding 61 | projected: 62 | sources: 63 | - secret: 64 | name: demo-db 65 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | <# : batch portion 2 | @REM ---------------------------------------------------------------------------- 3 | @REM Licensed to the Apache Software Foundation (ASF) under one 4 | @REM or more contributor license agreements. See the NOTICE file 5 | @REM distributed with this work for additional information 6 | @REM regarding copyright ownership. The ASF licenses this file 7 | @REM to you under the Apache License, Version 2.0 (the 8 | @REM "License"); you may not use this file except in compliance 9 | @REM with the License. You may obtain a copy of the License at 10 | @REM 11 | @REM https://www.apache.org/licenses/LICENSE-2.0 12 | @REM 13 | @REM Unless required by applicable law or agreed to in writing, 14 | @REM software distributed under the License is distributed on an 15 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | @REM KIND, either express or implied. See the License for the 17 | @REM specific language governing permissions and limitations 18 | @REM under the License. 19 | @REM ---------------------------------------------------------------------------- 20 | 21 | @REM ---------------------------------------------------------------------------- 22 | @REM Apache Maven Wrapper startup batch script, version 3.3.2 23 | @REM 24 | @REM Optional ENV vars 25 | @REM MVNW_REPOURL - repo url base for downloading maven distribution 26 | @REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven 27 | @REM MVNW_VERBOSE - true: enable verbose log; others: silence the output 28 | @REM ---------------------------------------------------------------------------- 29 | 30 | @IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) 31 | @SET __MVNW_CMD__= 32 | @SET __MVNW_ERROR__= 33 | @SET __MVNW_PSMODULEP_SAVE=%PSModulePath% 34 | @SET PSModulePath= 35 | @FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( 36 | IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) 37 | ) 38 | @SET PSModulePath=%__MVNW_PSMODULEP_SAVE% 39 | @SET __MVNW_PSMODULEP_SAVE= 40 | @SET __MVNW_ARG0_NAME__= 41 | @SET MVNW_USERNAME= 42 | @SET MVNW_PASSWORD= 43 | @IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) 44 | @echo Cannot start maven from wrapper >&2 && exit /b 1 45 | @GOTO :EOF 46 | : end batch / begin powershell #> 47 | 48 | $ErrorActionPreference = "Stop" 49 | if ($env:MVNW_VERBOSE -eq "true") { 50 | $VerbosePreference = "Continue" 51 | } 52 | 53 | # calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties 54 | $distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl 55 | if (!$distributionUrl) { 56 | Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" 57 | } 58 | 59 | switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { 60 | "maven-mvnd-*" { 61 | $USE_MVND = $true 62 | $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" 63 | $MVN_CMD = "mvnd.cmd" 64 | break 65 | } 66 | default { 67 | $USE_MVND = $false 68 | $MVN_CMD = $script -replace '^mvnw','mvn' 69 | break 70 | } 71 | } 72 | 73 | # apply MVNW_REPOURL and calculate MAVEN_HOME 74 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ 75 | if ($env:MVNW_REPOURL) { 76 | $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } 77 | $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" 78 | } 79 | $distributionUrlName = $distributionUrl -replace '^.*/','' 80 | $distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' 81 | $MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" 82 | if ($env:MAVEN_USER_HOME) { 83 | $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" 84 | } 85 | $MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' 86 | $MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" 87 | 88 | if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { 89 | Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" 90 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" 91 | exit $? 92 | } 93 | 94 | if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { 95 | Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" 96 | } 97 | 98 | # prepare tmp dir 99 | $TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile 100 | $TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" 101 | $TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null 102 | trap { 103 | if ($TMP_DOWNLOAD_DIR.Exists) { 104 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } 105 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } 106 | } 107 | } 108 | 109 | New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null 110 | 111 | # Download and Install Apache Maven 112 | Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." 113 | Write-Verbose "Downloading from: $distributionUrl" 114 | Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" 115 | 116 | $webclient = New-Object System.Net.WebClient 117 | if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { 118 | $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) 119 | } 120 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 121 | $webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null 122 | 123 | # If specified, validate the SHA-256 sum of the Maven distribution zip file 124 | $distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum 125 | if ($distributionSha256Sum) { 126 | if ($USE_MVND) { 127 | Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." 128 | } 129 | Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash 130 | if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { 131 | Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." 132 | } 133 | } 134 | 135 | # unzip and move 136 | Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null 137 | Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null 138 | try { 139 | Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null 140 | } catch { 141 | if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { 142 | Write-Error "fail to move MAVEN_HOME" 143 | } 144 | } finally { 145 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } 146 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } 147 | } 148 | 149 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" 150 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'spring-petclinic' 2 | -------------------------------------------------------------------------------- /src/checkstyle/nohttp-checkstyle-suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/checkstyle/nohttp-checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/PetClinicApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.samples.petclinic; 18 | 19 | import org.springframework.boot.SpringApplication; 20 | import org.springframework.boot.autoconfigure.SpringBootApplication; 21 | import org.springframework.context.annotation.ImportRuntimeHints; 22 | 23 | /** 24 | * PetClinic Spring Boot Application. 25 | * 26 | * @author Dave Syer 27 | */ 28 | @SpringBootApplication 29 | @ImportRuntimeHints(PetClinicRuntimeHints.class) 30 | public class PetClinicApplication { 31 | 32 | public static void main(String[] args) { 33 | SpringApplication.run(PetClinicApplication.class, args); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/PetClinicRuntimeHints.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.samples.petclinic; 18 | 19 | import org.springframework.aot.hint.RuntimeHints; 20 | import org.springframework.aot.hint.RuntimeHintsRegistrar; 21 | import org.springframework.samples.petclinic.model.BaseEntity; 22 | import org.springframework.samples.petclinic.model.Person; 23 | import org.springframework.samples.petclinic.vet.Vet; 24 | 25 | public class PetClinicRuntimeHints implements RuntimeHintsRegistrar { 26 | 27 | @Override 28 | public void registerHints(RuntimeHints hints, ClassLoader classLoader) { 29 | hints.resources().registerPattern("db/*"); // https://github.com/spring-projects/spring-boot/issues/32654 30 | hints.resources().registerPattern("messages/*"); 31 | hints.resources().registerPattern("mysql-default-conf"); 32 | hints.serialization().registerType(BaseEntity.class); 33 | hints.serialization().registerType(Person.class); 34 | hints.serialization().registerType(Vet.class); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/model/BaseEntity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.samples.petclinic.model; 17 | 18 | import java.io.Serializable; 19 | 20 | import jakarta.persistence.GeneratedValue; 21 | import jakarta.persistence.GenerationType; 22 | import jakarta.persistence.Id; 23 | import jakarta.persistence.MappedSuperclass; 24 | 25 | /** 26 | * Simple JavaBean domain object with an id property. Used as a base class for objects 27 | * needing this property. 28 | * 29 | * @author Ken Krebs 30 | * @author Juergen Hoeller 31 | */ 32 | @MappedSuperclass 33 | public class BaseEntity implements Serializable { 34 | 35 | @Id 36 | @GeneratedValue(strategy = GenerationType.IDENTITY) 37 | private Integer id; 38 | 39 | public Integer getId() { 40 | return id; 41 | } 42 | 43 | public void setId(Integer id) { 44 | this.id = id; 45 | } 46 | 47 | public boolean isNew() { 48 | return this.id == null; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/model/NamedEntity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.samples.petclinic.model; 17 | 18 | import jakarta.persistence.Column; 19 | import jakarta.persistence.MappedSuperclass; 20 | import jakarta.validation.constraints.NotBlank; 21 | 22 | /** 23 | * Simple JavaBean domain object adds a name property to BaseEntity. Used as 24 | * a base class for objects needing these properties. 25 | * 26 | * @author Ken Krebs 27 | * @author Juergen Hoeller 28 | * @author Wick Dynex 29 | */ 30 | @MappedSuperclass 31 | public class NamedEntity extends BaseEntity { 32 | 33 | @Column(name = "name") 34 | @NotBlank 35 | private String name; 36 | 37 | public String getName() { 38 | return this.name; 39 | } 40 | 41 | public void setName(String name) { 42 | this.name = name; 43 | } 44 | 45 | @Override 46 | public String toString() { 47 | return this.getName(); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/model/Person.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.samples.petclinic.model; 17 | 18 | import jakarta.persistence.Column; 19 | import jakarta.persistence.MappedSuperclass; 20 | import jakarta.validation.constraints.NotBlank; 21 | 22 | /** 23 | * Simple JavaBean domain object representing an person. 24 | * 25 | * @author Ken Krebs 26 | */ 27 | @MappedSuperclass 28 | public class Person extends BaseEntity { 29 | 30 | @Column(name = "first_name") 31 | @NotBlank 32 | private String firstName; 33 | 34 | @Column(name = "last_name") 35 | @NotBlank 36 | private String lastName; 37 | 38 | public String getFirstName() { 39 | return this.firstName; 40 | } 41 | 42 | public void setFirstName(String firstName) { 43 | this.firstName = firstName; 44 | } 45 | 46 | public String getLastName() { 47 | return this.lastName; 48 | } 49 | 50 | public void setLastName(String lastName) { 51 | this.lastName = lastName; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/model/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * The classes in this package represent utilities used by the domain. 19 | */ 20 | package org.springframework.samples.petclinic.model; 21 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/owner/Owner.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.samples.petclinic.owner; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | import org.springframework.core.style.ToStringCreator; 22 | import org.springframework.samples.petclinic.model.Person; 23 | import org.springframework.util.Assert; 24 | 25 | import jakarta.persistence.CascadeType; 26 | import jakarta.persistence.Column; 27 | import jakarta.persistence.Entity; 28 | import jakarta.persistence.FetchType; 29 | import jakarta.persistence.JoinColumn; 30 | import jakarta.persistence.OneToMany; 31 | import jakarta.persistence.OrderBy; 32 | import jakarta.persistence.Table; 33 | import jakarta.validation.constraints.Pattern; 34 | import jakarta.validation.constraints.NotBlank; 35 | 36 | /** 37 | * Simple JavaBean domain object representing an owner. 38 | * 39 | * @author Ken Krebs 40 | * @author Juergen Hoeller 41 | * @author Sam Brannen 42 | * @author Michael Isvy 43 | * @author Oliver Drotbohm 44 | * @author Wick Dynex 45 | */ 46 | @Entity 47 | @Table(name = "owners") 48 | public class Owner extends Person { 49 | 50 | @Column(name = "address") 51 | @NotBlank 52 | private String address; 53 | 54 | @Column(name = "city") 55 | @NotBlank 56 | private String city; 57 | 58 | @Column(name = "telephone") 59 | @NotBlank 60 | @Pattern(regexp = "\\d{10}", message = "{telephone.invalid}") 61 | private String telephone; 62 | 63 | @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) 64 | @JoinColumn(name = "owner_id") 65 | @OrderBy("name") 66 | private final List pets = new ArrayList<>(); 67 | 68 | public String getAddress() { 69 | return this.address; 70 | } 71 | 72 | public void setAddress(String address) { 73 | this.address = address; 74 | } 75 | 76 | public String getCity() { 77 | return this.city; 78 | } 79 | 80 | public void setCity(String city) { 81 | this.city = city; 82 | } 83 | 84 | public String getTelephone() { 85 | return this.telephone; 86 | } 87 | 88 | public void setTelephone(String telephone) { 89 | this.telephone = telephone; 90 | } 91 | 92 | public List getPets() { 93 | return this.pets; 94 | } 95 | 96 | public void addPet(Pet pet) { 97 | if (pet.isNew()) { 98 | getPets().add(pet); 99 | } 100 | } 101 | 102 | /** 103 | * Return the Pet with the given name, or null if none found for this Owner. 104 | * @param name to test 105 | * @return the Pet with the given name, or null if no such Pet exists for this Owner 106 | */ 107 | public Pet getPet(String name) { 108 | return getPet(name, false); 109 | } 110 | 111 | /** 112 | * Return the Pet with the given id, or null if none found for this Owner. 113 | * @param id to test 114 | * @return the Pet with the given id, or null if no such Pet exists for this Owner 115 | */ 116 | public Pet getPet(Integer id) { 117 | for (Pet pet : getPets()) { 118 | if (!pet.isNew()) { 119 | Integer compId = pet.getId(); 120 | if (compId.equals(id)) { 121 | return pet; 122 | } 123 | } 124 | } 125 | return null; 126 | } 127 | 128 | /** 129 | * Return the Pet with the given name, or null if none found for this Owner. 130 | * @param name to test 131 | * @param ignoreNew whether to ignore new pets (pets that are not saved yet) 132 | * @return the Pet with the given name, or null if no such Pet exists for this Owner 133 | */ 134 | public Pet getPet(String name, boolean ignoreNew) { 135 | for (Pet pet : getPets()) { 136 | String compName = pet.getName(); 137 | if (compName != null && compName.equalsIgnoreCase(name)) { 138 | if (!ignoreNew || !pet.isNew()) { 139 | return pet; 140 | } 141 | } 142 | } 143 | return null; 144 | } 145 | 146 | @Override 147 | public String toString() { 148 | return new ToStringCreator(this).append("id", this.getId()) 149 | .append("new", this.isNew()) 150 | .append("lastName", this.getLastName()) 151 | .append("firstName", this.getFirstName()) 152 | .append("address", this.address) 153 | .append("city", this.city) 154 | .append("telephone", this.telephone) 155 | .toString(); 156 | } 157 | 158 | /** 159 | * Adds the given {@link Visit} to the {@link Pet} with the given identifier. 160 | * @param petId the identifier of the {@link Pet}, must not be {@literal null}. 161 | * @param visit the visit to add, must not be {@literal null}. 162 | */ 163 | public void addVisit(Integer petId, Visit visit) { 164 | 165 | Assert.notNull(petId, "Pet identifier must not be null!"); 166 | Assert.notNull(visit, "Visit must not be null!"); 167 | 168 | Pet pet = getPet(petId); 169 | 170 | Assert.notNull(pet, "Invalid Pet identifier!"); 171 | 172 | pet.addVisit(visit); 173 | } 174 | 175 | } 176 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.samples.petclinic.owner; 17 | 18 | import java.util.List; 19 | import java.util.Optional; 20 | 21 | import org.springframework.data.domain.Page; 22 | import org.springframework.data.domain.PageRequest; 23 | import org.springframework.data.domain.Pageable; 24 | import org.springframework.stereotype.Controller; 25 | import org.springframework.ui.Model; 26 | import org.springframework.validation.BindingResult; 27 | import org.springframework.web.bind.WebDataBinder; 28 | import org.springframework.web.bind.annotation.GetMapping; 29 | import org.springframework.web.bind.annotation.InitBinder; 30 | import org.springframework.web.bind.annotation.ModelAttribute; 31 | import org.springframework.web.bind.annotation.PathVariable; 32 | import org.springframework.web.bind.annotation.PostMapping; 33 | import org.springframework.web.bind.annotation.RequestParam; 34 | import org.springframework.web.servlet.ModelAndView; 35 | 36 | import jakarta.validation.Valid; 37 | import org.springframework.web.servlet.mvc.support.RedirectAttributes; 38 | 39 | /** 40 | * @author Juergen Hoeller 41 | * @author Ken Krebs 42 | * @author Arjen Poutsma 43 | * @author Michael Isvy 44 | * @author Wick Dynex 45 | */ 46 | @Controller 47 | class OwnerController { 48 | 49 | private static final String VIEWS_OWNER_CREATE_OR_UPDATE_FORM = "owners/createOrUpdateOwnerForm"; 50 | 51 | private final OwnerRepository owners; 52 | 53 | public OwnerController(OwnerRepository owners) { 54 | this.owners = owners; 55 | } 56 | 57 | @InitBinder 58 | public void setAllowedFields(WebDataBinder dataBinder) { 59 | dataBinder.setDisallowedFields("id"); 60 | } 61 | 62 | @ModelAttribute("owner") 63 | public Owner findOwner(@PathVariable(name = "ownerId", required = false) Integer ownerId) { 64 | return ownerId == null ? new Owner() 65 | : this.owners.findById(ownerId) 66 | .orElseThrow(() -> new IllegalArgumentException("Owner not found with id: " + ownerId 67 | + ". Please ensure the ID is correct " + "and the owner exists in the database.")); 68 | } 69 | 70 | @GetMapping("/owners/new") 71 | public String initCreationForm() { 72 | return VIEWS_OWNER_CREATE_OR_UPDATE_FORM; 73 | } 74 | 75 | @PostMapping("/owners/new") 76 | public String processCreationForm(@Valid Owner owner, BindingResult result, RedirectAttributes redirectAttributes) { 77 | if (result.hasErrors()) { 78 | redirectAttributes.addFlashAttribute("error", "There was an error in creating the owner."); 79 | return VIEWS_OWNER_CREATE_OR_UPDATE_FORM; 80 | } 81 | 82 | this.owners.save(owner); 83 | redirectAttributes.addFlashAttribute("message", "New Owner Created"); 84 | return "redirect:/owners/" + owner.getId(); 85 | } 86 | 87 | @GetMapping("/owners/find") 88 | public String initFindForm() { 89 | return "owners/findOwners"; 90 | } 91 | 92 | @GetMapping("/owners") 93 | public String processFindForm(@RequestParam(defaultValue = "1") int page, Owner owner, BindingResult result, 94 | Model model) { 95 | // allow parameterless GET request for /owners to return all records 96 | if (owner.getLastName() == null) { 97 | owner.setLastName(""); // empty string signifies broadest possible search 98 | } 99 | 100 | // find owners by last name 101 | Page ownersResults = findPaginatedForOwnersLastName(page, owner.getLastName()); 102 | if (ownersResults.isEmpty()) { 103 | // no owners found 104 | result.rejectValue("lastName", "notFound", "not found"); 105 | return "owners/findOwners"; 106 | } 107 | 108 | if (ownersResults.getTotalElements() == 1) { 109 | // 1 owner found 110 | owner = ownersResults.iterator().next(); 111 | return "redirect:/owners/" + owner.getId(); 112 | } 113 | 114 | // multiple owners found 115 | return addPaginationModel(page, model, ownersResults); 116 | } 117 | 118 | private String addPaginationModel(int page, Model model, Page paginated) { 119 | List listOwners = paginated.getContent(); 120 | model.addAttribute("currentPage", page); 121 | model.addAttribute("totalPages", paginated.getTotalPages()); 122 | model.addAttribute("totalItems", paginated.getTotalElements()); 123 | model.addAttribute("listOwners", listOwners); 124 | return "owners/ownersList"; 125 | } 126 | 127 | private Page findPaginatedForOwnersLastName(int page, String lastname) { 128 | int pageSize = 5; 129 | Pageable pageable = PageRequest.of(page - 1, pageSize); 130 | return owners.findByLastNameStartingWith(lastname, pageable); 131 | } 132 | 133 | @GetMapping("/owners/{ownerId}/edit") 134 | public String initUpdateOwnerForm() { 135 | return VIEWS_OWNER_CREATE_OR_UPDATE_FORM; 136 | } 137 | 138 | @PostMapping("/owners/{ownerId}/edit") 139 | public String processUpdateOwnerForm(@Valid Owner owner, BindingResult result, @PathVariable("ownerId") int ownerId, 140 | RedirectAttributes redirectAttributes) { 141 | if (result.hasErrors()) { 142 | redirectAttributes.addFlashAttribute("error", "There was an error in updating the owner."); 143 | return VIEWS_OWNER_CREATE_OR_UPDATE_FORM; 144 | } 145 | 146 | if (owner.getId() != ownerId) { 147 | result.rejectValue("id", "mismatch", "The owner ID in the form does not match the URL."); 148 | redirectAttributes.addFlashAttribute("error", "Owner ID mismatch. Please try again."); 149 | return "redirect:/owners/{ownerId}/edit"; 150 | } 151 | 152 | owner.setId(ownerId); 153 | this.owners.save(owner); 154 | redirectAttributes.addFlashAttribute("message", "Owner Values Updated"); 155 | return "redirect:/owners/{ownerId}"; 156 | } 157 | 158 | /** 159 | * Custom handler for displaying an owner. 160 | * @param ownerId the ID of the owner to display 161 | * @return a ModelMap with the model attributes for the view 162 | */ 163 | @GetMapping("/owners/{ownerId}") 164 | public ModelAndView showOwner(@PathVariable("ownerId") int ownerId) { 165 | ModelAndView mav = new ModelAndView("owners/ownerDetails"); 166 | Optional optionalOwner = this.owners.findById(ownerId); 167 | Owner owner = optionalOwner.orElseThrow(() -> new IllegalArgumentException( 168 | "Owner not found with id: " + ownerId + ". Please ensure the ID is correct ")); 169 | mav.addObject(owner); 170 | return mav; 171 | } 172 | 173 | } 174 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/owner/OwnerRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.samples.petclinic.owner; 17 | 18 | import java.util.List; 19 | import java.util.Optional; 20 | 21 | import jakarta.annotation.Nonnull; 22 | import org.springframework.data.domain.Page; 23 | import org.springframework.data.domain.Pageable; 24 | import org.springframework.data.jpa.repository.JpaRepository; 25 | import org.springframework.data.jpa.repository.Query; 26 | 27 | /** 28 | * Repository class for Owner domain objects. All method names are compliant 29 | * with Spring Data naming conventions so this interface can easily be extended for Spring 30 | * Data. See: 31 | * https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-creation 32 | * 33 | * @author Ken Krebs 34 | * @author Juergen Hoeller 35 | * @author Sam Brannen 36 | * @author Michael Isvy 37 | * @author Wick Dynex 38 | */ 39 | public interface OwnerRepository extends JpaRepository { 40 | 41 | /** 42 | * Retrieve {@link Owner}s from the data store by last name, returning all owners 43 | * whose last name starts with the given name. 44 | * @param lastName Value to search for 45 | * @return a Collection of matching {@link Owner}s (or an empty Collection if none 46 | * found) 47 | */ 48 | Page findByLastNameStartingWith(String lastName, Pageable pageable); 49 | 50 | /** 51 | * Retrieve an {@link Owner} from the data store by id. 52 | *

53 | * This method returns an {@link Optional} containing the {@link Owner} if found. If 54 | * no {@link Owner} is found with the provided id, it will return an empty 55 | * {@link Optional}. 56 | *

57 | * @param id the id to search for 58 | * @return an {@link Optional} containing the {@link Owner} if found, or an empty 59 | * {@link Optional} if not found. 60 | * @throws IllegalArgumentException if the id is null (assuming null is not a valid 61 | * input for id) 62 | */ 63 | Optional findById(@Nonnull Integer id); 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/owner/Pet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.samples.petclinic.owner; 17 | 18 | import java.time.LocalDate; 19 | import java.util.Collection; 20 | import java.util.LinkedHashSet; 21 | import java.util.Set; 22 | 23 | import org.springframework.format.annotation.DateTimeFormat; 24 | import org.springframework.samples.petclinic.model.NamedEntity; 25 | 26 | import jakarta.persistence.CascadeType; 27 | import jakarta.persistence.Column; 28 | import jakarta.persistence.Entity; 29 | import jakarta.persistence.FetchType; 30 | import jakarta.persistence.JoinColumn; 31 | import jakarta.persistence.ManyToOne; 32 | import jakarta.persistence.OneToMany; 33 | import jakarta.persistence.OrderBy; 34 | import jakarta.persistence.Table; 35 | 36 | /** 37 | * Simple business object representing a pet. 38 | * 39 | * @author Ken Krebs 40 | * @author Juergen Hoeller 41 | * @author Sam Brannen 42 | * @author Wick Dynex 43 | */ 44 | @Entity 45 | @Table(name = "pets") 46 | public class Pet extends NamedEntity { 47 | 48 | @Column(name = "birth_date") 49 | @DateTimeFormat(pattern = "yyyy-MM-dd") 50 | private LocalDate birthDate; 51 | 52 | @ManyToOne 53 | @JoinColumn(name = "type_id") 54 | private PetType type; 55 | 56 | @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) 57 | @JoinColumn(name = "pet_id") 58 | @OrderBy("date ASC") 59 | private final Set visits = new LinkedHashSet<>(); 60 | 61 | public void setBirthDate(LocalDate birthDate) { 62 | this.birthDate = birthDate; 63 | } 64 | 65 | public LocalDate getBirthDate() { 66 | return this.birthDate; 67 | } 68 | 69 | public PetType getType() { 70 | return this.type; 71 | } 72 | 73 | public void setType(PetType type) { 74 | this.type = type; 75 | } 76 | 77 | public Collection getVisits() { 78 | return this.visits; 79 | } 80 | 81 | public void addVisit(Visit visit) { 82 | getVisits().add(visit); 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/owner/PetController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.samples.petclinic.owner; 17 | 18 | import java.time.LocalDate; 19 | import java.util.Collection; 20 | import java.util.Optional; 21 | 22 | import org.springframework.stereotype.Controller; 23 | import org.springframework.ui.ModelMap; 24 | import org.springframework.util.StringUtils; 25 | import org.springframework.validation.BindingResult; 26 | import org.springframework.web.bind.WebDataBinder; 27 | import org.springframework.web.bind.annotation.GetMapping; 28 | import org.springframework.web.bind.annotation.InitBinder; 29 | import org.springframework.web.bind.annotation.ModelAttribute; 30 | import org.springframework.web.bind.annotation.PathVariable; 31 | import org.springframework.web.bind.annotation.PostMapping; 32 | import org.springframework.web.bind.annotation.RequestMapping; 33 | 34 | import jakarta.validation.Valid; 35 | import org.springframework.web.servlet.mvc.support.RedirectAttributes; 36 | 37 | /** 38 | * @author Juergen Hoeller 39 | * @author Ken Krebs 40 | * @author Arjen Poutsma 41 | * @author Wick Dynex 42 | */ 43 | @Controller 44 | @RequestMapping("/owners/{ownerId}") 45 | class PetController { 46 | 47 | private static final String VIEWS_PETS_CREATE_OR_UPDATE_FORM = "pets/createOrUpdatePetForm"; 48 | 49 | private final OwnerRepository owners; 50 | 51 | private final PetTypeRepository types; 52 | 53 | public PetController(OwnerRepository owners, PetTypeRepository types) { 54 | this.owners = owners; 55 | this.types = types; 56 | } 57 | 58 | @ModelAttribute("types") 59 | public Collection populatePetTypes() { 60 | return this.types.findPetTypes(); 61 | } 62 | 63 | @ModelAttribute("owner") 64 | public Owner findOwner(@PathVariable("ownerId") int ownerId) { 65 | Optional optionalOwner = this.owners.findById(ownerId); 66 | Owner owner = optionalOwner.orElseThrow(() -> new IllegalArgumentException( 67 | "Owner not found with id: " + ownerId + ". Please ensure the ID is correct ")); 68 | return owner; 69 | } 70 | 71 | @ModelAttribute("pet") 72 | public Pet findPet(@PathVariable("ownerId") int ownerId, 73 | @PathVariable(name = "petId", required = false) Integer petId) { 74 | 75 | if (petId == null) { 76 | return new Pet(); 77 | } 78 | 79 | Optional optionalOwner = this.owners.findById(ownerId); 80 | Owner owner = optionalOwner.orElseThrow(() -> new IllegalArgumentException( 81 | "Owner not found with id: " + ownerId + ". Please ensure the ID is correct ")); 82 | return owner.getPet(petId); 83 | } 84 | 85 | @InitBinder("owner") 86 | public void initOwnerBinder(WebDataBinder dataBinder) { 87 | dataBinder.setDisallowedFields("id"); 88 | } 89 | 90 | @InitBinder("pet") 91 | public void initPetBinder(WebDataBinder dataBinder) { 92 | dataBinder.setValidator(new PetValidator()); 93 | } 94 | 95 | @GetMapping("/pets/new") 96 | public String initCreationForm(Owner owner, ModelMap model) { 97 | Pet pet = new Pet(); 98 | owner.addPet(pet); 99 | return VIEWS_PETS_CREATE_OR_UPDATE_FORM; 100 | } 101 | 102 | @PostMapping("/pets/new") 103 | public String processCreationForm(Owner owner, @Valid Pet pet, BindingResult result, 104 | RedirectAttributes redirectAttributes) { 105 | 106 | if (StringUtils.hasText(pet.getName()) && pet.isNew() && owner.getPet(pet.getName(), true) != null) 107 | result.rejectValue("name", "duplicate", "already exists"); 108 | 109 | LocalDate currentDate = LocalDate.now(); 110 | if (pet.getBirthDate() != null && pet.getBirthDate().isAfter(currentDate)) { 111 | result.rejectValue("birthDate", "typeMismatch.birthDate"); 112 | } 113 | 114 | if (result.hasErrors()) { 115 | return VIEWS_PETS_CREATE_OR_UPDATE_FORM; 116 | } 117 | 118 | owner.addPet(pet); 119 | this.owners.save(owner); 120 | redirectAttributes.addFlashAttribute("message", "New Pet has been Added"); 121 | return "redirect:/owners/{ownerId}"; 122 | } 123 | 124 | @GetMapping("/pets/{petId}/edit") 125 | public String initUpdateForm() { 126 | return VIEWS_PETS_CREATE_OR_UPDATE_FORM; 127 | } 128 | 129 | @PostMapping("/pets/{petId}/edit") 130 | public String processUpdateForm(Owner owner, @Valid Pet pet, BindingResult result, 131 | RedirectAttributes redirectAttributes) { 132 | 133 | String petName = pet.getName(); 134 | 135 | // checking if the pet name already exists for the owner 136 | if (StringUtils.hasText(petName)) { 137 | Pet existingPet = owner.getPet(petName, false); 138 | if (existingPet != null && !existingPet.getId().equals(pet.getId())) { 139 | result.rejectValue("name", "duplicate", "already exists"); 140 | } 141 | } 142 | 143 | LocalDate currentDate = LocalDate.now(); 144 | if (pet.getBirthDate() != null && pet.getBirthDate().isAfter(currentDate)) { 145 | result.rejectValue("birthDate", "typeMismatch.birthDate"); 146 | } 147 | 148 | if (result.hasErrors()) { 149 | return VIEWS_PETS_CREATE_OR_UPDATE_FORM; 150 | } 151 | 152 | updatePetDetails(owner, pet); 153 | redirectAttributes.addFlashAttribute("message", "Pet details has been edited"); 154 | return "redirect:/owners/{ownerId}"; 155 | } 156 | 157 | /** 158 | * Updates the pet details if it exists or adds a new pet to the owner. 159 | * @param owner The owner of the pet 160 | * @param pet The pet with updated details 161 | */ 162 | private void updatePetDetails(Owner owner, Pet pet) { 163 | Pet existingPet = owner.getPet(pet.getId()); 164 | if (existingPet != null) { 165 | // Update existing pet's properties 166 | existingPet.setName(pet.getName()); 167 | existingPet.setBirthDate(pet.getBirthDate()); 168 | existingPet.setType(pet.getType()); 169 | } 170 | else { 171 | owner.addPet(pet); 172 | } 173 | this.owners.save(owner); 174 | } 175 | 176 | } 177 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/owner/PetType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.samples.petclinic.owner; 17 | 18 | import org.springframework.samples.petclinic.model.NamedEntity; 19 | 20 | import jakarta.persistence.Entity; 21 | import jakarta.persistence.Table; 22 | 23 | /** 24 | * @author Juergen Hoeller Can be Cat, Dog, Hamster... 25 | */ 26 | @Entity 27 | @Table(name = "types") 28 | public class PetType extends NamedEntity { 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/owner/PetTypeFormatter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.samples.petclinic.owner; 17 | 18 | import org.springframework.format.Formatter; 19 | import org.springframework.stereotype.Component; 20 | 21 | import java.text.ParseException; 22 | import java.util.Collection; 23 | import java.util.Locale; 24 | 25 | /** 26 | * Instructs Spring MVC on how to parse and print elements of type 'PetType'. Starting 27 | * from Spring 3.0, Formatters have come as an improvement in comparison to legacy 28 | * PropertyEditors. See the following links for more details: - The Spring ref doc: 29 | * https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#format 30 | * 31 | * @author Mark Fisher 32 | * @author Juergen Hoeller 33 | * @author Michael Isvy 34 | */ 35 | @Component 36 | public class PetTypeFormatter implements Formatter { 37 | 38 | private final PetTypeRepository types; 39 | 40 | public PetTypeFormatter(PetTypeRepository types) { 41 | this.types = types; 42 | } 43 | 44 | @Override 45 | public String print(PetType petType, Locale locale) { 46 | return petType.getName(); 47 | } 48 | 49 | @Override 50 | public PetType parse(String text, Locale locale) throws ParseException { 51 | Collection findPetTypes = this.types.findPetTypes(); 52 | for (PetType type : findPetTypes) { 53 | if (type.getName().equals(text)) { 54 | return type; 55 | } 56 | } 57 | throw new ParseException("type not found: " + text, 0); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/owner/PetTypeRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.samples.petclinic.owner; 18 | 19 | import java.util.List; 20 | import java.util.Optional; 21 | 22 | import jakarta.annotation.Nonnull; 23 | import org.springframework.data.domain.Page; 24 | import org.springframework.data.domain.Pageable; 25 | import org.springframework.data.jpa.repository.JpaRepository; 26 | import org.springframework.data.jpa.repository.Query; 27 | 28 | /** 29 | * Repository class for PetType domain objects. 30 | * 31 | * @author Patrick Baumgartner 32 | */ 33 | 34 | public interface PetTypeRepository extends JpaRepository { 35 | 36 | /** 37 | * Retrieve all {@link PetType}s from the data store. 38 | * @return a Collection of {@link PetType}s. 39 | */ 40 | @Query("SELECT ptype FROM PetType ptype ORDER BY ptype.name") 41 | List findPetTypes(); 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/owner/PetValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.samples.petclinic.owner; 17 | 18 | import org.springframework.util.StringUtils; 19 | import org.springframework.validation.Errors; 20 | import org.springframework.validation.Validator; 21 | 22 | /** 23 | * Validator for Pet forms. 24 | *

25 | * We're not using Bean Validation annotations here because it is easier to define such 26 | * validation rule in Java. 27 | *

28 | * 29 | * @author Ken Krebs 30 | * @author Juergen Hoeller 31 | */ 32 | public class PetValidator implements Validator { 33 | 34 | private static final String REQUIRED = "required"; 35 | 36 | @Override 37 | public void validate(Object obj, Errors errors) { 38 | Pet pet = (Pet) obj; 39 | String name = pet.getName(); 40 | // name validation 41 | if (!StringUtils.hasText(name)) { 42 | errors.rejectValue("name", REQUIRED, REQUIRED); 43 | } 44 | 45 | // type validation 46 | if (pet.isNew() && pet.getType() == null) { 47 | errors.rejectValue("type", REQUIRED, REQUIRED); 48 | } 49 | 50 | // birth date validation 51 | if (pet.getBirthDate() == null) { 52 | errors.rejectValue("birthDate", REQUIRED, REQUIRED); 53 | } 54 | } 55 | 56 | /** 57 | * This Validator validates *just* Pet instances 58 | */ 59 | @Override 60 | public boolean supports(Class clazz) { 61 | return Pet.class.isAssignableFrom(clazz); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/owner/Visit.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.samples.petclinic.owner; 17 | 18 | import java.time.LocalDate; 19 | 20 | import org.springframework.format.annotation.DateTimeFormat; 21 | import org.springframework.samples.petclinic.model.BaseEntity; 22 | 23 | import jakarta.persistence.Column; 24 | import jakarta.persistence.Entity; 25 | import jakarta.persistence.Table; 26 | import jakarta.validation.constraints.NotBlank; 27 | 28 | /** 29 | * Simple JavaBean domain object representing a visit. 30 | * 31 | * @author Ken Krebs 32 | * @author Dave Syer 33 | */ 34 | @Entity 35 | @Table(name = "visits") 36 | public class Visit extends BaseEntity { 37 | 38 | @Column(name = "visit_date") 39 | @DateTimeFormat(pattern = "yyyy-MM-dd") 40 | private LocalDate date; 41 | 42 | @NotBlank 43 | private String description; 44 | 45 | /** 46 | * Creates a new instance of Visit for the current date 47 | */ 48 | public Visit() { 49 | this.date = LocalDate.now(); 50 | } 51 | 52 | public LocalDate getDate() { 53 | return this.date; 54 | } 55 | 56 | public void setDate(LocalDate date) { 57 | this.date = date; 58 | } 59 | 60 | public String getDescription() { 61 | return this.description; 62 | } 63 | 64 | public void setDescription(String description) { 65 | this.description = description; 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/owner/VisitController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.samples.petclinic.owner; 17 | 18 | import java.util.Map; 19 | import java.util.Optional; 20 | 21 | import org.springframework.stereotype.Controller; 22 | import org.springframework.validation.BindingResult; 23 | import org.springframework.web.bind.WebDataBinder; 24 | import org.springframework.web.bind.annotation.GetMapping; 25 | import org.springframework.web.bind.annotation.InitBinder; 26 | import org.springframework.web.bind.annotation.ModelAttribute; 27 | import org.springframework.web.bind.annotation.PathVariable; 28 | import org.springframework.web.bind.annotation.PostMapping; 29 | 30 | import jakarta.validation.Valid; 31 | import org.springframework.web.servlet.mvc.support.RedirectAttributes; 32 | 33 | /** 34 | * @author Juergen Hoeller 35 | * @author Ken Krebs 36 | * @author Arjen Poutsma 37 | * @author Michael Isvy 38 | * @author Dave Syer 39 | * @author Wick Dynex 40 | */ 41 | @Controller 42 | class VisitController { 43 | 44 | private final OwnerRepository owners; 45 | 46 | public VisitController(OwnerRepository owners) { 47 | this.owners = owners; 48 | } 49 | 50 | @InitBinder 51 | public void setAllowedFields(WebDataBinder dataBinder) { 52 | dataBinder.setDisallowedFields("id"); 53 | } 54 | 55 | /** 56 | * Called before each and every @RequestMapping annotated method. 2 goals: - Make sure 57 | * we always have fresh data - Since we do not use the session scope, make sure that 58 | * Pet object always has an id (Even though id is not part of the form fields) 59 | * @param petId 60 | * @return Pet 61 | */ 62 | @ModelAttribute("visit") 63 | public Visit loadPetWithVisit(@PathVariable("ownerId") int ownerId, @PathVariable("petId") int petId, 64 | Map model) { 65 | Optional optionalOwner = owners.findById(ownerId); 66 | Owner owner = optionalOwner.orElseThrow(() -> new IllegalArgumentException( 67 | "Owner not found with id: " + ownerId + ". Please ensure the ID is correct ")); 68 | 69 | Pet pet = owner.getPet(petId); 70 | model.put("pet", pet); 71 | model.put("owner", owner); 72 | 73 | Visit visit = new Visit(); 74 | pet.addVisit(visit); 75 | return visit; 76 | } 77 | 78 | // Spring MVC calls method loadPetWithVisit(...) before initNewVisitForm is 79 | // called 80 | @GetMapping("/owners/{ownerId}/pets/{petId}/visits/new") 81 | public String initNewVisitForm() { 82 | return "pets/createOrUpdateVisitForm"; 83 | } 84 | 85 | // Spring MVC calls method loadPetWithVisit(...) before processNewVisitForm is 86 | // called 87 | @PostMapping("/owners/{ownerId}/pets/{petId}/visits/new") 88 | public String processNewVisitForm(@ModelAttribute Owner owner, @PathVariable int petId, @Valid Visit visit, 89 | BindingResult result, RedirectAttributes redirectAttributes) { 90 | if (result.hasErrors()) { 91 | return "pets/createOrUpdateVisitForm"; 92 | } 93 | 94 | owner.addVisit(petId, visit); 95 | this.owners.save(owner); 96 | redirectAttributes.addFlashAttribute("message", "Your visit has been booked"); 97 | return "redirect:/owners/{ownerId}"; 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/system/CacheConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.samples.petclinic.system; 18 | 19 | import org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer; 20 | import org.springframework.cache.annotation.EnableCaching; 21 | import org.springframework.context.annotation.Bean; 22 | import org.springframework.context.annotation.Configuration; 23 | 24 | import javax.cache.configuration.MutableConfiguration; 25 | 26 | /** 27 | * Cache configuration intended for caches providing the JCache API. This configuration 28 | * creates the used cache for the application and enables statistics that become 29 | * accessible via JMX. 30 | */ 31 | @Configuration(proxyBeanMethods = false) 32 | @EnableCaching 33 | class CacheConfiguration { 34 | 35 | @Bean 36 | public JCacheManagerCustomizer petclinicCacheConfigurationCustomizer() { 37 | return cm -> cm.createCache("vets", cacheConfiguration()); 38 | } 39 | 40 | /** 41 | * Create a simple configuration that enable statistics via the JCache programmatic 42 | * configuration API. 43 | *

44 | * Within the configuration object that is provided by the JCache API standard, there 45 | * is only a very limited set of configuration options. The really relevant 46 | * configuration options (like the size limit) must be set via a configuration 47 | * mechanism that is provided by the selected JCache implementation. 48 | */ 49 | private javax.cache.configuration.Configuration cacheConfiguration() { 50 | return new MutableConfiguration<>().setStatisticsEnabled(true); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/system/CrashController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.samples.petclinic.system; 17 | 18 | import org.springframework.stereotype.Controller; 19 | import org.springframework.web.bind.annotation.GetMapping; 20 | 21 | /** 22 | * Controller used to showcase what happens when an exception is thrown 23 | * 24 | * @author Michael Isvy 25 | *

26 | * Also see how a view that resolves to "error" has been added ("error.html"). 27 | */ 28 | @Controller 29 | class CrashController { 30 | 31 | @GetMapping("/oups") 32 | public String triggerException() { 33 | throw new RuntimeException( 34 | "Expected: controller used to showcase what " + "happens when an exception is thrown"); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/system/WebConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.springframework.samples.petclinic.system; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.servlet.LocaleResolver; 6 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 7 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 8 | import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; 9 | import org.springframework.web.servlet.i18n.SessionLocaleResolver; 10 | 11 | import java.util.Locale; 12 | 13 | /** 14 | * Configures internationalization (i18n) support for the application. 15 | * 16 | *

17 | * Handles loading language-specific messages, tracking the user's language, and allowing 18 | * language changes via the URL parameter (e.g., ?lang=de). 19 | *

20 | * 21 | * @author Anuj Ashok Potdar 22 | */ 23 | @Configuration 24 | @SuppressWarnings("unused") 25 | public class WebConfiguration implements WebMvcConfigurer { 26 | 27 | /** 28 | * Uses session storage to remember the user’s language setting across requests. 29 | * Defaults to English if nothing is specified. 30 | * @return session-based {@link LocaleResolver} 31 | */ 32 | @Bean 33 | public LocaleResolver localeResolver() { 34 | SessionLocaleResolver resolver = new SessionLocaleResolver(); 35 | resolver.setDefaultLocale(Locale.ENGLISH); 36 | return resolver; 37 | } 38 | 39 | /** 40 | * Allows the app to switch languages using a URL parameter like 41 | * ?lang=es. 42 | * @return a {@link LocaleChangeInterceptor} that handles the change 43 | */ 44 | @Bean 45 | public LocaleChangeInterceptor localeChangeInterceptor() { 46 | LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor(); 47 | interceptor.setParamName("lang"); 48 | return interceptor; 49 | } 50 | 51 | /** 52 | * Registers the locale change interceptor so it can run on each request. 53 | * @param registry where interceptors are added 54 | */ 55 | @Override 56 | public void addInterceptors(InterceptorRegistry registry) { 57 | registry.addInterceptor(localeChangeInterceptor()); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/system/WelcomeController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.samples.petclinic.system; 18 | 19 | import org.springframework.stereotype.Controller; 20 | import org.springframework.web.bind.annotation.GetMapping; 21 | 22 | @Controller 23 | class WelcomeController { 24 | 25 | @GetMapping("/") 26 | public String welcome() { 27 | return "welcome"; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/vet/Specialty.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.samples.petclinic.vet; 17 | 18 | import org.springframework.samples.petclinic.model.NamedEntity; 19 | 20 | import jakarta.persistence.Entity; 21 | import jakarta.persistence.Table; 22 | 23 | /** 24 | * Models a {@link Vet Vet's} specialty (for example, dentistry). 25 | * 26 | * @author Juergen Hoeller 27 | */ 28 | @Entity 29 | @Table(name = "specialties") 30 | public class Specialty extends NamedEntity { 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/vet/Vet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.samples.petclinic.vet; 17 | 18 | import java.util.Comparator; 19 | import java.util.HashSet; 20 | import java.util.List; 21 | import java.util.Set; 22 | import java.util.stream.Collectors; 23 | 24 | import org.springframework.samples.petclinic.model.NamedEntity; 25 | import org.springframework.samples.petclinic.model.Person; 26 | 27 | import jakarta.persistence.Entity; 28 | import jakarta.persistence.FetchType; 29 | import jakarta.persistence.JoinColumn; 30 | import jakarta.persistence.JoinTable; 31 | import jakarta.persistence.ManyToMany; 32 | import jakarta.persistence.Table; 33 | import jakarta.xml.bind.annotation.XmlElement; 34 | 35 | /** 36 | * Simple JavaBean domain object representing a veterinarian. 37 | * 38 | * @author Ken Krebs 39 | * @author Juergen Hoeller 40 | * @author Sam Brannen 41 | * @author Arjen Poutsma 42 | */ 43 | @Entity 44 | @Table(name = "vets") 45 | public class Vet extends Person { 46 | 47 | @ManyToMany(fetch = FetchType.EAGER) 48 | @JoinTable(name = "vet_specialties", joinColumns = @JoinColumn(name = "vet_id"), 49 | inverseJoinColumns = @JoinColumn(name = "specialty_id")) 50 | private Set specialties; 51 | 52 | protected Set getSpecialtiesInternal() { 53 | if (this.specialties == null) { 54 | this.specialties = new HashSet<>(); 55 | } 56 | return this.specialties; 57 | } 58 | 59 | @XmlElement 60 | public List getSpecialties() { 61 | return getSpecialtiesInternal().stream() 62 | .sorted(Comparator.comparing(NamedEntity::getName)) 63 | .collect(Collectors.toList()); 64 | } 65 | 66 | public int getNrOfSpecialties() { 67 | return getSpecialtiesInternal().size(); 68 | } 69 | 70 | public void addSpecialty(Specialty specialty) { 71 | getSpecialtiesInternal().add(specialty); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/vet/VetController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.samples.petclinic.vet; 17 | 18 | import java.util.List; 19 | 20 | import org.springframework.data.domain.Page; 21 | import org.springframework.data.domain.PageRequest; 22 | import org.springframework.data.domain.Pageable; 23 | import org.springframework.stereotype.Controller; 24 | import org.springframework.ui.Model; 25 | import org.springframework.web.bind.annotation.GetMapping; 26 | import org.springframework.web.bind.annotation.RequestParam; 27 | import org.springframework.web.bind.annotation.ResponseBody; 28 | 29 | /** 30 | * @author Juergen Hoeller 31 | * @author Mark Fisher 32 | * @author Ken Krebs 33 | * @author Arjen Poutsma 34 | */ 35 | @Controller 36 | class VetController { 37 | 38 | private final VetRepository vetRepository; 39 | 40 | public VetController(VetRepository vetRepository) { 41 | this.vetRepository = vetRepository; 42 | } 43 | 44 | @GetMapping("/vets.html") 45 | public String showVetList(@RequestParam(defaultValue = "1") int page, Model model) { 46 | // Here we are returning an object of type 'Vets' rather than a collection of Vet 47 | // objects so it is simpler for Object-Xml mapping 48 | Vets vets = new Vets(); 49 | Page paginated = findPaginated(page); 50 | vets.getVetList().addAll(paginated.toList()); 51 | return addPaginationModel(page, paginated, model); 52 | } 53 | 54 | private String addPaginationModel(int page, Page paginated, Model model) { 55 | List listVets = paginated.getContent(); 56 | model.addAttribute("currentPage", page); 57 | model.addAttribute("totalPages", paginated.getTotalPages()); 58 | model.addAttribute("totalItems", paginated.getTotalElements()); 59 | model.addAttribute("listVets", listVets); 60 | return "vets/vetList"; 61 | } 62 | 63 | private Page findPaginated(int page) { 64 | int pageSize = 5; 65 | Pageable pageable = PageRequest.of(page - 1, pageSize); 66 | return vetRepository.findAll(pageable); 67 | } 68 | 69 | @GetMapping({ "/vets" }) 70 | public @ResponseBody Vets showResourcesVetList() { 71 | // Here we are returning an object of type 'Vets' rather than a collection of Vet 72 | // objects so it is simpler for JSon/Object mapping 73 | Vets vets = new Vets(); 74 | vets.getVetList().addAll(this.vetRepository.findAll()); 75 | return vets; 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/vet/VetRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.samples.petclinic.vet; 17 | 18 | import org.springframework.cache.annotation.Cacheable; 19 | import org.springframework.dao.DataAccessException; 20 | import org.springframework.data.domain.Page; 21 | import org.springframework.data.domain.Pageable; 22 | import org.springframework.data.repository.Repository; 23 | import org.springframework.transaction.annotation.Transactional; 24 | 25 | import java.util.Collection; 26 | 27 | /** 28 | * Repository class for Vet domain objects All method names are compliant 29 | * with Spring Data naming conventions so this interface can easily be extended for Spring 30 | * Data. See: 31 | * https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-creation 32 | * 33 | * @author Ken Krebs 34 | * @author Juergen Hoeller 35 | * @author Sam Brannen 36 | * @author Michael Isvy 37 | */ 38 | public interface VetRepository extends Repository { 39 | 40 | /** 41 | * Retrieve all Vets from the data store. 42 | * @return a Collection of Vets 43 | */ 44 | @Transactional(readOnly = true) 45 | @Cacheable("vets") 46 | Collection findAll() throws DataAccessException; 47 | 48 | /** 49 | * Retrieve all Vets from data store in Pages 50 | * @param pageable 51 | * @return 52 | * @throws DataAccessException 53 | */ 54 | @Transactional(readOnly = true) 55 | @Cacheable("vets") 56 | Page findAll(Pageable pageable) throws DataAccessException; 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/samples/petclinic/vet/Vets.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.samples.petclinic.vet; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | import jakarta.xml.bind.annotation.XmlElement; 22 | import jakarta.xml.bind.annotation.XmlRootElement; 23 | 24 | /** 25 | * Simple domain object representing a list of veterinarians. Mostly here to be used for 26 | * the 'vets' {@link org.springframework.web.servlet.view.xml.MarshallingView}. 27 | * 28 | * @author Arjen Poutsma 29 | */ 30 | @XmlRootElement 31 | public class Vets { 32 | 33 | private List vets; 34 | 35 | @XmlElement 36 | public List getVetList() { 37 | if (vets == null) { 38 | vets = new ArrayList<>(); 39 | } 40 | return vets; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/resources/application-mysql.properties: -------------------------------------------------------------------------------- 1 | # database init, supports mysql too 2 | database=mysql 3 | spring.datasource.url=${MYSQL_URL:jdbc:mysql://localhost/petclinic} 4 | spring.datasource.username=${MYSQL_USER:petclinic} 5 | spring.datasource.password=${MYSQL_PASS:petclinic} 6 | # SQL is written to be idempotent so this is safe 7 | spring.sql.init.mode=always 8 | -------------------------------------------------------------------------------- /src/main/resources/application-postgres.properties: -------------------------------------------------------------------------------- 1 | # database init, supports postgres too 2 | database=postgres 3 | spring.datasource.url=${POSTGRES_URL:jdbc:postgresql://localhost/petclinic} 4 | spring.datasource.username=${POSTGRES_USER:petclinic} 5 | spring.datasource.password=${POSTGRES_PASS:petclinic} 6 | # SQL is written to be idempotent so this is safe 7 | spring.sql.init.mode=always 8 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # database init, supports mysql too 2 | database=h2 3 | spring.sql.init.schema-locations=classpath*:db/${database}/schema.sql 4 | spring.sql.init.data-locations=classpath*:db/${database}/data.sql 5 | 6 | # Web 7 | spring.thymeleaf.mode=HTML 8 | 9 | # JPA 10 | spring.jpa.hibernate.ddl-auto=none 11 | spring.jpa.open-in-view=false 12 | 13 | # Internationalization 14 | spring.messages.basename=messages/messages 15 | 16 | # Actuator 17 | management.endpoints.web.exposure.include=* 18 | 19 | # Logging 20 | logging.level.org.springframework=INFO 21 | # logging.level.org.springframework.web=DEBUG 22 | # logging.level.org.springframework.context.annotation=TRACE 23 | 24 | # Maximum time static resources should be cached 25 | spring.web.resources.cache.cachecontrol.max-age=12h 26 | -------------------------------------------------------------------------------- /src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | |\ _,,,--,,_ 4 | /,`.-'`' ._ \-;;,_ 5 | _______ __|,4- ) )_ .;.(__`'-'__ ___ __ _ ___ _______ 6 | | | '---''(_/._)-'(_\_) | | | | | | | | | 7 | | _ | ___|_ _| | | | | |_| | | | __ _ _ 8 | | |_| | |___ | | | | | | | | | | \ \ \ \ 9 | | ___| ___| | | | _| |___| | _ | | _| \ \ \ \ 10 | | | | |___ | | | |_| | | | | | | |_ ) ) ) ) 11 | |___| |_______| |___| |_______|_______|___|_| |__|___|_______| / / / / 12 | ==================================================================/_/_/_/ 13 | 14 | :: Built with Spring Boot :: ${spring-boot.version} 15 | 16 | -------------------------------------------------------------------------------- /src/main/resources/db/h2/data.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO vets VALUES (default, 'James', 'Carter'); 2 | INSERT INTO vets VALUES (default, 'Helen', 'Leary'); 3 | INSERT INTO vets VALUES (default, 'Linda', 'Douglas'); 4 | INSERT INTO vets VALUES (default, 'Rafael', 'Ortega'); 5 | INSERT INTO vets VALUES (default, 'Henry', 'Stevens'); 6 | INSERT INTO vets VALUES (default, 'Sharon', 'Jenkins'); 7 | 8 | INSERT INTO specialties VALUES (default, 'radiology'); 9 | INSERT INTO specialties VALUES (default, 'surgery'); 10 | INSERT INTO specialties VALUES (default, 'dentistry'); 11 | 12 | INSERT INTO vet_specialties VALUES (2, 1); 13 | INSERT INTO vet_specialties VALUES (3, 2); 14 | INSERT INTO vet_specialties VALUES (3, 3); 15 | INSERT INTO vet_specialties VALUES (4, 2); 16 | INSERT INTO vet_specialties VALUES (5, 1); 17 | 18 | INSERT INTO types VALUES (default, 'cat'); 19 | INSERT INTO types VALUES (default, 'dog'); 20 | INSERT INTO types VALUES (default, 'lizard'); 21 | INSERT INTO types VALUES (default, 'snake'); 22 | INSERT INTO types VALUES (default, 'bird'); 23 | INSERT INTO types VALUES (default, 'hamster'); 24 | 25 | INSERT INTO owners VALUES (default, 'George', 'Franklin', '110 W. Liberty St.', 'Madison', '6085551023'); 26 | INSERT INTO owners VALUES (default, 'Betty', 'Davis', '638 Cardinal Ave.', 'Sun Prairie', '6085551749'); 27 | INSERT INTO owners VALUES (default, 'Eduardo', 'Rodriquez', '2693 Commerce St.', 'McFarland', '6085558763'); 28 | INSERT INTO owners VALUES (default, 'Harold', 'Davis', '563 Friendly St.', 'Windsor', '6085553198'); 29 | INSERT INTO owners VALUES (default, 'Peter', 'McTavish', '2387 S. Fair Way', 'Madison', '6085552765'); 30 | INSERT INTO owners VALUES (default, 'Jean', 'Coleman', '105 N. Lake St.', 'Monona', '6085552654'); 31 | INSERT INTO owners VALUES (default, 'Jeff', 'Black', '1450 Oak Blvd.', 'Monona', '6085555387'); 32 | INSERT INTO owners VALUES (default, 'Maria', 'Escobito', '345 Maple St.', 'Madison', '6085557683'); 33 | INSERT INTO owners VALUES (default, 'David', 'Schroeder', '2749 Blackhawk Trail', 'Madison', '6085559435'); 34 | INSERT INTO owners VALUES (default, 'Carlos', 'Estaban', '2335 Independence La.', 'Waunakee', '6085555487'); 35 | 36 | INSERT INTO pets VALUES (default, 'Leo', '2010-09-07', 1, 1); 37 | INSERT INTO pets VALUES (default, 'Basil', '2012-08-06', 6, 2); 38 | INSERT INTO pets VALUES (default, 'Rosy', '2011-04-17', 2, 3); 39 | INSERT INTO pets VALUES (default, 'Jewel', '2010-03-07', 2, 3); 40 | INSERT INTO pets VALUES (default, 'Iggy', '2010-11-30', 3, 4); 41 | INSERT INTO pets VALUES (default, 'George', '2010-01-20', 4, 5); 42 | INSERT INTO pets VALUES (default, 'Samantha', '2012-09-04', 1, 6); 43 | INSERT INTO pets VALUES (default, 'Max', '2012-09-04', 1, 6); 44 | INSERT INTO pets VALUES (default, 'Lucky', '2011-08-06', 5, 7); 45 | INSERT INTO pets VALUES (default, 'Mulligan', '2007-02-24', 2, 8); 46 | INSERT INTO pets VALUES (default, 'Freddy', '2010-03-09', 5, 9); 47 | INSERT INTO pets VALUES (default, 'Lucky', '2010-06-24', 2, 10); 48 | INSERT INTO pets VALUES (default, 'Sly', '2012-06-08', 1, 10); 49 | 50 | INSERT INTO visits VALUES (default, 7, '2013-01-01', 'rabies shot'); 51 | INSERT INTO visits VALUES (default, 8, '2013-01-02', 'rabies shot'); 52 | INSERT INTO visits VALUES (default, 8, '2013-01-03', 'neutered'); 53 | INSERT INTO visits VALUES (default, 7, '2013-01-04', 'spayed'); 54 | -------------------------------------------------------------------------------- /src/main/resources/db/h2/schema.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE vet_specialties IF EXISTS; 2 | DROP TABLE vets IF EXISTS; 3 | DROP TABLE specialties IF EXISTS; 4 | DROP TABLE visits IF EXISTS; 5 | DROP TABLE pets IF EXISTS; 6 | DROP TABLE types IF EXISTS; 7 | DROP TABLE owners IF EXISTS; 8 | 9 | 10 | CREATE TABLE vets ( 11 | id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, 12 | first_name VARCHAR(30), 13 | last_name VARCHAR(30) 14 | ); 15 | CREATE INDEX vets_last_name ON vets (last_name); 16 | 17 | CREATE TABLE specialties ( 18 | id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, 19 | name VARCHAR(80) 20 | ); 21 | CREATE INDEX specialties_name ON specialties (name); 22 | 23 | CREATE TABLE vet_specialties ( 24 | vet_id INTEGER NOT NULL, 25 | specialty_id INTEGER NOT NULL 26 | ); 27 | ALTER TABLE vet_specialties ADD CONSTRAINT fk_vet_specialties_vets FOREIGN KEY (vet_id) REFERENCES vets (id); 28 | ALTER TABLE vet_specialties ADD CONSTRAINT fk_vet_specialties_specialties FOREIGN KEY (specialty_id) REFERENCES specialties (id); 29 | 30 | CREATE TABLE types ( 31 | id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, 32 | name VARCHAR(80) 33 | ); 34 | CREATE INDEX types_name ON types (name); 35 | 36 | CREATE TABLE owners ( 37 | id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, 38 | first_name VARCHAR(30), 39 | last_name VARCHAR_IGNORECASE(30), 40 | address VARCHAR(255), 41 | city VARCHAR(80), 42 | telephone VARCHAR(20) 43 | ); 44 | CREATE INDEX owners_last_name ON owners (last_name); 45 | 46 | CREATE TABLE pets ( 47 | id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, 48 | name VARCHAR(30), 49 | birth_date DATE, 50 | type_id INTEGER NOT NULL, 51 | owner_id INTEGER 52 | ); 53 | ALTER TABLE pets ADD CONSTRAINT fk_pets_owners FOREIGN KEY (owner_id) REFERENCES owners (id); 54 | ALTER TABLE pets ADD CONSTRAINT fk_pets_types FOREIGN KEY (type_id) REFERENCES types (id); 55 | CREATE INDEX pets_name ON pets (name); 56 | 57 | CREATE TABLE visits ( 58 | id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, 59 | pet_id INTEGER, 60 | visit_date DATE, 61 | description VARCHAR(255) 62 | ); 63 | ALTER TABLE visits ADD CONSTRAINT fk_visits_pets FOREIGN KEY (pet_id) REFERENCES pets (id); 64 | CREATE INDEX visits_pet_id ON visits (pet_id); 65 | -------------------------------------------------------------------------------- /src/main/resources/db/hsqldb/data.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO vets VALUES (1, 'James', 'Carter'); 2 | INSERT INTO vets VALUES (2, 'Helen', 'Leary'); 3 | INSERT INTO vets VALUES (3, 'Linda', 'Douglas'); 4 | INSERT INTO vets VALUES (4, 'Rafael', 'Ortega'); 5 | INSERT INTO vets VALUES (5, 'Henry', 'Stevens'); 6 | INSERT INTO vets VALUES (6, 'Sharon', 'Jenkins'); 7 | 8 | INSERT INTO specialties VALUES (1, 'radiology'); 9 | INSERT INTO specialties VALUES (2, 'surgery'); 10 | INSERT INTO specialties VALUES (3, 'dentistry'); 11 | 12 | INSERT INTO vet_specialties VALUES (2, 1); 13 | INSERT INTO vet_specialties VALUES (3, 2); 14 | INSERT INTO vet_specialties VALUES (3, 3); 15 | INSERT INTO vet_specialties VALUES (4, 2); 16 | INSERT INTO vet_specialties VALUES (5, 1); 17 | 18 | INSERT INTO types VALUES (1, 'cat'); 19 | INSERT INTO types VALUES (2, 'dog'); 20 | INSERT INTO types VALUES (3, 'lizard'); 21 | INSERT INTO types VALUES (4, 'snake'); 22 | INSERT INTO types VALUES (5, 'bird'); 23 | INSERT INTO types VALUES (6, 'hamster'); 24 | 25 | INSERT INTO owners VALUES (1, 'George', 'Franklin', '110 W. Liberty St.', 'Madison', '6085551023'); 26 | INSERT INTO owners VALUES (2, 'Betty', 'Davis', '638 Cardinal Ave.', 'Sun Prairie', '6085551749'); 27 | INSERT INTO owners VALUES (3, 'Eduardo', 'Rodriquez', '2693 Commerce St.', 'McFarland', '6085558763'); 28 | INSERT INTO owners VALUES (4, 'Harold', 'Davis', '563 Friendly St.', 'Windsor', '6085553198'); 29 | INSERT INTO owners VALUES (5, 'Peter', 'McTavish', '2387 S. Fair Way', 'Madison', '6085552765'); 30 | INSERT INTO owners VALUES (6, 'Jean', 'Coleman', '105 N. Lake St.', 'Monona', '6085552654'); 31 | INSERT INTO owners VALUES (7, 'Jeff', 'Black', '1450 Oak Blvd.', 'Monona', '6085555387'); 32 | INSERT INTO owners VALUES (8, 'Maria', 'Escobito', '345 Maple St.', 'Madison', '6085557683'); 33 | INSERT INTO owners VALUES (9, 'David', 'Schroeder', '2749 Blackhawk Trail', 'Madison', '6085559435'); 34 | INSERT INTO owners VALUES (10, 'Carlos', 'Estaban', '2335 Independence La.', 'Waunakee', '6085555487'); 35 | 36 | INSERT INTO pets VALUES (1, 'Leo', '2010-09-07', 1, 1); 37 | INSERT INTO pets VALUES (2, 'Basil', '2012-08-06', 6, 2); 38 | INSERT INTO pets VALUES (3, 'Rosy', '2011-04-17', 2, 3); 39 | INSERT INTO pets VALUES (4, 'Jewel', '2010-03-07', 2, 3); 40 | INSERT INTO pets VALUES (5, 'Iggy', '2010-11-30', 3, 4); 41 | INSERT INTO pets VALUES (6, 'George', '2010-01-20', 4, 5); 42 | INSERT INTO pets VALUES (7, 'Samantha', '2012-09-04', 1, 6); 43 | INSERT INTO pets VALUES (8, 'Max', '2012-09-04', 1, 6); 44 | INSERT INTO pets VALUES (9, 'Lucky', '2011-08-06', 5, 7); 45 | INSERT INTO pets VALUES (10, 'Mulligan', '2007-02-24', 2, 8); 46 | INSERT INTO pets VALUES (11, 'Freddy', '2010-03-09', 5, 9); 47 | INSERT INTO pets VALUES (12, 'Lucky', '2010-06-24', 2, 10); 48 | INSERT INTO pets VALUES (13, 'Sly', '2012-06-08', 1, 10); 49 | 50 | INSERT INTO visits VALUES (1, 7, '2013-01-01', 'rabies shot'); 51 | INSERT INTO visits VALUES (2, 8, '2013-01-02', 'rabies shot'); 52 | INSERT INTO visits VALUES (3, 8, '2013-01-03', 'neutered'); 53 | INSERT INTO visits VALUES (4, 7, '2013-01-04', 'spayed'); 54 | -------------------------------------------------------------------------------- /src/main/resources/db/hsqldb/schema.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE vet_specialties IF EXISTS; 2 | DROP TABLE vets IF EXISTS; 3 | DROP TABLE specialties IF EXISTS; 4 | DROP TABLE visits IF EXISTS; 5 | DROP TABLE pets IF EXISTS; 6 | DROP TABLE types IF EXISTS; 7 | DROP TABLE owners IF EXISTS; 8 | 9 | 10 | CREATE TABLE vets ( 11 | id INTEGER IDENTITY PRIMARY KEY, 12 | first_name VARCHAR(30), 13 | last_name VARCHAR(30) 14 | ); 15 | CREATE INDEX vets_last_name ON vets (last_name); 16 | 17 | CREATE TABLE specialties ( 18 | id INTEGER IDENTITY PRIMARY KEY, 19 | name VARCHAR(80) 20 | ); 21 | CREATE INDEX specialties_name ON specialties (name); 22 | 23 | CREATE TABLE vet_specialties ( 24 | vet_id INTEGER NOT NULL, 25 | specialty_id INTEGER NOT NULL 26 | ); 27 | ALTER TABLE vet_specialties ADD CONSTRAINT fk_vet_specialties_vets FOREIGN KEY (vet_id) REFERENCES vets (id); 28 | ALTER TABLE vet_specialties ADD CONSTRAINT fk_vet_specialties_specialties FOREIGN KEY (specialty_id) REFERENCES specialties (id); 29 | 30 | CREATE TABLE types ( 31 | id INTEGER IDENTITY PRIMARY KEY, 32 | name VARCHAR(80) 33 | ); 34 | CREATE INDEX types_name ON types (name); 35 | 36 | CREATE TABLE owners ( 37 | id INTEGER IDENTITY PRIMARY KEY, 38 | first_name VARCHAR(30), 39 | last_name VARCHAR_IGNORECASE(30), 40 | address VARCHAR(255), 41 | city VARCHAR(80), 42 | telephone VARCHAR(20) 43 | ); 44 | CREATE INDEX owners_last_name ON owners (last_name); 45 | 46 | CREATE TABLE pets ( 47 | id INTEGER IDENTITY PRIMARY KEY, 48 | name VARCHAR(30), 49 | birth_date DATE, 50 | type_id INTEGER NOT NULL, 51 | owner_id INTEGER 52 | ); 53 | ALTER TABLE pets ADD CONSTRAINT fk_pets_owners FOREIGN KEY (owner_id) REFERENCES owners (id); 54 | ALTER TABLE pets ADD CONSTRAINT fk_pets_types FOREIGN KEY (type_id) REFERENCES types (id); 55 | CREATE INDEX pets_name ON pets (name); 56 | 57 | CREATE TABLE visits ( 58 | id INTEGER IDENTITY PRIMARY KEY, 59 | pet_id INTEGER, 60 | visit_date DATE, 61 | description VARCHAR(255) 62 | ); 63 | ALTER TABLE visits ADD CONSTRAINT fk_visits_pets FOREIGN KEY (pet_id) REFERENCES pets (id); 64 | CREATE INDEX visits_pet_id ON visits (pet_id); 65 | -------------------------------------------------------------------------------- /src/main/resources/db/mysql/data.sql: -------------------------------------------------------------------------------- 1 | INSERT IGNORE INTO vets VALUES (1, 'James', 'Carter'); 2 | INSERT IGNORE INTO vets VALUES (2, 'Helen', 'Leary'); 3 | INSERT IGNORE INTO vets VALUES (3, 'Linda', 'Douglas'); 4 | INSERT IGNORE INTO vets VALUES (4, 'Rafael', 'Ortega'); 5 | INSERT IGNORE INTO vets VALUES (5, 'Henry', 'Stevens'); 6 | INSERT IGNORE INTO vets VALUES (6, 'Sharon', 'Jenkins'); 7 | 8 | INSERT IGNORE INTO specialties VALUES (1, 'radiology'); 9 | INSERT IGNORE INTO specialties VALUES (2, 'surgery'); 10 | INSERT IGNORE INTO specialties VALUES (3, 'dentistry'); 11 | 12 | INSERT IGNORE INTO vet_specialties VALUES (2, 1); 13 | INSERT IGNORE INTO vet_specialties VALUES (3, 2); 14 | INSERT IGNORE INTO vet_specialties VALUES (3, 3); 15 | INSERT IGNORE INTO vet_specialties VALUES (4, 2); 16 | INSERT IGNORE INTO vet_specialties VALUES (5, 1); 17 | 18 | INSERT IGNORE INTO types VALUES (1, 'cat'); 19 | INSERT IGNORE INTO types VALUES (2, 'dog'); 20 | INSERT IGNORE INTO types VALUES (3, 'lizard'); 21 | INSERT IGNORE INTO types VALUES (4, 'snake'); 22 | INSERT IGNORE INTO types VALUES (5, 'bird'); 23 | INSERT IGNORE INTO types VALUES (6, 'hamster'); 24 | 25 | INSERT IGNORE INTO owners VALUES (1, 'George', 'Franklin', '110 W. Liberty St.', 'Madison', '6085551023'); 26 | INSERT IGNORE INTO owners VALUES (2, 'Betty', 'Davis', '638 Cardinal Ave.', 'Sun Prairie', '6085551749'); 27 | INSERT IGNORE INTO owners VALUES (3, 'Eduardo', 'Rodriquez', '2693 Commerce St.', 'McFarland', '6085558763'); 28 | INSERT IGNORE INTO owners VALUES (4, 'Harold', 'Davis', '563 Friendly St.', 'Windsor', '6085553198'); 29 | INSERT IGNORE INTO owners VALUES (5, 'Peter', 'McTavish', '2387 S. Fair Way', 'Madison', '6085552765'); 30 | INSERT IGNORE INTO owners VALUES (6, 'Jean', 'Coleman', '105 N. Lake St.', 'Monona', '6085552654'); 31 | INSERT IGNORE INTO owners VALUES (7, 'Jeff', 'Black', '1450 Oak Blvd.', 'Monona', '6085555387'); 32 | INSERT IGNORE INTO owners VALUES (8, 'Maria', 'Escobito', '345 Maple St.', 'Madison', '6085557683'); 33 | INSERT IGNORE INTO owners VALUES (9, 'David', 'Schroeder', '2749 Blackhawk Trail', 'Madison', '6085559435'); 34 | INSERT IGNORE INTO owners VALUES (10, 'Carlos', 'Estaban', '2335 Independence La.', 'Waunakee', '6085555487'); 35 | 36 | INSERT IGNORE INTO pets VALUES (1, 'Leo', '2000-09-07', 1, 1); 37 | INSERT IGNORE INTO pets VALUES (2, 'Basil', '2002-08-06', 6, 2); 38 | INSERT IGNORE INTO pets VALUES (3, 'Rosy', '2001-04-17', 2, 3); 39 | INSERT IGNORE INTO pets VALUES (4, 'Jewel', '2000-03-07', 2, 3); 40 | INSERT IGNORE INTO pets VALUES (5, 'Iggy', '2000-11-30', 3, 4); 41 | INSERT IGNORE INTO pets VALUES (6, 'George', '2000-01-20', 4, 5); 42 | INSERT IGNORE INTO pets VALUES (7, 'Samantha', '1995-09-04', 1, 6); 43 | INSERT IGNORE INTO pets VALUES (8, 'Max', '1995-09-04', 1, 6); 44 | INSERT IGNORE INTO pets VALUES (9, 'Lucky', '1999-08-06', 5, 7); 45 | INSERT IGNORE INTO pets VALUES (10, 'Mulligan', '1997-02-24', 2, 8); 46 | INSERT IGNORE INTO pets VALUES (11, 'Freddy', '2000-03-09', 5, 9); 47 | INSERT IGNORE INTO pets VALUES (12, 'Lucky', '2000-06-24', 2, 10); 48 | INSERT IGNORE INTO pets VALUES (13, 'Sly', '2002-06-08', 1, 10); 49 | 50 | INSERT IGNORE INTO visits VALUES (1, 7, '2010-03-04', 'rabies shot'); 51 | INSERT IGNORE INTO visits VALUES (2, 8, '2011-03-04', 'rabies shot'); 52 | INSERT IGNORE INTO visits VALUES (3, 8, '2009-06-04', 'neutered'); 53 | INSERT IGNORE INTO visits VALUES (4, 7, '2008-09-04', 'spayed'); 54 | -------------------------------------------------------------------------------- /src/main/resources/db/mysql/petclinic_db_setup_mysql.txt: -------------------------------------------------------------------------------- 1 | ================================================================================ 2 | === Spring PetClinic sample application - MySQL Configuration === 3 | ================================================================================ 4 | 5 | @author Sam Brannen 6 | @author Costin Leau 7 | @author Dave Syer 8 | 9 | -------------------------------------------------------------------------------- 10 | 11 | 1) Download and install the MySQL database (e.g., MySQL Community Server 5.1.x), 12 | which can be found here: https://dev.mysql.com/downloads/. Or run the 13 | "docker-compose.yml" from the root of the project (if you have docker installed 14 | locally): 15 | 16 | $ docker-compose up 17 | ... 18 | mysql_1_eedb4818d817 | MySQL init process done. Ready for start up. 19 | ... 20 | 21 | 2) (Once only) create the PetClinic database and user by executing the "db/mysql/user.sql" 22 | scripts. You can connect to the database running in the docker container using 23 | `mysql -u root -h localhost --protocol tcp`, but you don't need to run the script there 24 | because the petclinic user is already set up if you use the provided `docker-compose.yml`. 25 | 26 | 3) Run the app with `spring.profiles.active=mysql` (e.g. as a System property via the command 27 | line, but any way that sets that property in a Spring Boot app should work). For example use 28 | 29 | mvn spring-boot:run -Dspring-boot.run.profiles=mysql 30 | 31 | To activate the profile on the command line. 32 | 33 | N.B. the "petclinic" database has to exist for the app to work with the JDBC URL value 34 | as it is configured by default. This condition is taken care of automatically by the 35 | docker-compose configuration provided, or by the `user.sql` script if you run that as 36 | root. 37 | -------------------------------------------------------------------------------- /src/main/resources/db/mysql/schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS vets ( 2 | id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 | first_name VARCHAR(30), 4 | last_name VARCHAR(30), 5 | INDEX(last_name) 6 | ) engine=InnoDB; 7 | 8 | CREATE TABLE IF NOT EXISTS specialties ( 9 | id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 10 | name VARCHAR(80), 11 | INDEX(name) 12 | ) engine=InnoDB; 13 | 14 | CREATE TABLE IF NOT EXISTS vet_specialties ( 15 | vet_id INT(4) UNSIGNED NOT NULL, 16 | specialty_id INT(4) UNSIGNED NOT NULL, 17 | FOREIGN KEY (vet_id) REFERENCES vets(id), 18 | FOREIGN KEY (specialty_id) REFERENCES specialties(id), 19 | UNIQUE (vet_id,specialty_id) 20 | ) engine=InnoDB; 21 | 22 | CREATE TABLE IF NOT EXISTS types ( 23 | id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 24 | name VARCHAR(80), 25 | INDEX(name) 26 | ) engine=InnoDB; 27 | 28 | CREATE TABLE IF NOT EXISTS owners ( 29 | id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 30 | first_name VARCHAR(30), 31 | last_name VARCHAR(30), 32 | address VARCHAR(255), 33 | city VARCHAR(80), 34 | telephone VARCHAR(20), 35 | INDEX(last_name) 36 | ) engine=InnoDB; 37 | 38 | CREATE TABLE IF NOT EXISTS pets ( 39 | id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 40 | name VARCHAR(30), 41 | birth_date DATE, 42 | type_id INT(4) UNSIGNED NOT NULL, 43 | owner_id INT(4) UNSIGNED, 44 | INDEX(name), 45 | FOREIGN KEY (owner_id) REFERENCES owners(id), 46 | FOREIGN KEY (type_id) REFERENCES types(id) 47 | ) engine=InnoDB; 48 | 49 | CREATE TABLE IF NOT EXISTS visits ( 50 | id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 51 | pet_id INT(4) UNSIGNED, 52 | visit_date DATE, 53 | description VARCHAR(255), 54 | FOREIGN KEY (pet_id) REFERENCES pets(id) 55 | ) engine=InnoDB; 56 | -------------------------------------------------------------------------------- /src/main/resources/db/mysql/user.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE IF NOT EXISTS petclinic; 2 | 3 | ALTER DATABASE petclinic 4 | DEFAULT CHARACTER SET utf8 5 | DEFAULT COLLATE utf8_general_ci; 6 | 7 | GRANT ALL PRIVILEGES ON petclinic.* TO 'petclinic'@'%' IDENTIFIED BY 'petclinic'; 8 | -------------------------------------------------------------------------------- /src/main/resources/db/postgres/data.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO vets (first_name, last_name) SELECT 'James', 'Carter' WHERE NOT EXISTS (SELECT * FROM vets WHERE id=1); 2 | INSERT INTO vets (first_name, last_name) SELECT 'Helen', 'Leary' WHERE NOT EXISTS (SELECT * FROM vets WHERE id=2); 3 | INSERT INTO vets (first_name, last_name) SELECT 'Linda', 'Douglas' WHERE NOT EXISTS (SELECT * FROM vets WHERE id=3); 4 | INSERT INTO vets (first_name, last_name) SELECT 'Rafael', 'Ortega' WHERE NOT EXISTS (SELECT * FROM vets WHERE id=4); 5 | INSERT INTO vets (first_name, last_name) SELECT 'Henry', 'Stevens' WHERE NOT EXISTS (SELECT * FROM vets WHERE id=5); 6 | INSERT INTO vets (first_name, last_name) SELECT 'Sharon', 'Jenkins' WHERE NOT EXISTS (SELECT * FROM vets WHERE id=6); 7 | 8 | INSERT INTO specialties (name) SELECT 'radiology' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='radiology'); 9 | INSERT INTO specialties (name) SELECT 'surgery' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='surgery'); 10 | INSERT INTO specialties (name) SELECT 'dentistry' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='dentistry'); 11 | 12 | INSERT INTO vet_specialties VALUES (2, 1) ON CONFLICT (vet_id, specialty_id) DO NOTHING; 13 | INSERT INTO vet_specialties VALUES (3, 2) ON CONFLICT (vet_id, specialty_id) DO NOTHING; 14 | INSERT INTO vet_specialties VALUES (3, 3) ON CONFLICT (vet_id, specialty_id) DO NOTHING; 15 | INSERT INTO vet_specialties VALUES (4, 2) ON CONFLICT (vet_id, specialty_id) DO NOTHING; 16 | INSERT INTO vet_specialties VALUES (5, 1) ON CONFLICT (vet_id, specialty_id) DO NOTHING; 17 | 18 | INSERT INTO types (name) SELECT 'cat' WHERE NOT EXISTS (SELECT * FROM types WHERE name='cat'); 19 | INSERT INTO types (name) SELECT 'dog' WHERE NOT EXISTS (SELECT * FROM types WHERE name='dog'); 20 | INSERT INTO types (name) SELECT 'lizard' WHERE NOT EXISTS (SELECT * FROM types WHERE name='lizard'); 21 | INSERT INTO types (name) SELECT 'snake' WHERE NOT EXISTS (SELECT * FROM types WHERE name='snake'); 22 | INSERT INTO types (name) SELECT 'bird' WHERE NOT EXISTS (SELECT * FROM types WHERE name='bird'); 23 | INSERT INTO types (name) SELECT 'hamster' WHERE NOT EXISTS (SELECT * FROM types WHERE name='hamster'); 24 | 25 | INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'George', 'Franklin', '110 W. Liberty St.', 'Madison', '6085551023' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=1); 26 | INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Betty', 'Davis', '638 Cardinal Ave.', 'Sun Prairie', '6085551749' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=2); 27 | INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Eduardo', 'Rodriquez', '2693 Commerce St.', 'McFarland', '6085558763' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=3); 28 | INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Harold', 'Davis', '563 Friendly St.', 'Windsor', '6085553198' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=4); 29 | INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Peter', 'McTavish', '2387 S. Fair Way', 'Madison', '6085552765' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=5); 30 | INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Jean', 'Coleman', '105 N. Lake St.', 'Monona', '6085552654' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=6); 31 | INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Jeff', 'Black', '1450 Oak Blvd.', 'Monona', '6085555387' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=7); 32 | INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Maria', 'Escobito', '345 Maple St.', 'Madison', '6085557683' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=8); 33 | INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'David', 'Schroeder', '2749 Blackhawk Trail', 'Madison', '6085559435' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=9); 34 | INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Carlos', 'Estaban', '2335 Independence La.', 'Waunakee', '6085555487' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=10); 35 | 36 | INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Leo', '2000-09-07', 1, 1 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=1); 37 | INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Basil', '2002-08-06', 6, 2 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=2); 38 | INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Rosy', '2001-04-17', 2, 3 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=3); 39 | INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Jewel', '2000-03-07', 2, 3 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=4); 40 | INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Iggy', '2000-11-30', 3, 4 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=5); 41 | INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'George', '2000-01-20', 4, 5 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=6); 42 | INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Samantha', '1995-09-04', 1, 6 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=7); 43 | INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Max', '1995-09-04', 1, 6 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=8); 44 | INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Lucky', '1999-08-06', 5, 7 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=9); 45 | INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Mulligan', '1997-02-24', 2, 8 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=10); 46 | INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Freddy', '2000-03-09', 5, 9 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=11); 47 | INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Lucky', '2000-06-24', 2, 10 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=12); 48 | INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Sly', '2002-06-08', 1, 10 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=13); 49 | 50 | INSERT INTO visits (pet_id, visit_date, description) SELECT 7, '2010-03-04', 'rabies shot' WHERE NOT EXISTS (SELECT * FROM visits WHERE id=1); 51 | INSERT INTO visits (pet_id, visit_date, description) SELECT 8, '2011-03-04', 'rabies shot' WHERE NOT EXISTS (SELECT * FROM visits WHERE id=2); 52 | INSERT INTO visits (pet_id, visit_date, description) SELECT 8, '2009-06-04', 'neutered' WHERE NOT EXISTS (SELECT * FROM visits WHERE id=3); 53 | INSERT INTO visits (pet_id, visit_date, description) SELECT 7, '2008-09-04', 'spayed' WHERE NOT EXISTS (SELECT * FROM visits WHERE id=4); 54 | -------------------------------------------------------------------------------- /src/main/resources/db/postgres/petclinic_db_setup_postgres.txt: -------------------------------------------------------------------------------- 1 | =============================================================================== 2 | === Spring PetClinic sample application - PostgreSQL Configuration === 3 | =============================================================================== 4 | 5 | -------------------------------------------------------------------------------- 6 | 7 | 1) Run the "docker-compose.yml" from the root of the project: 8 | 9 | $ docker-compose up 10 | ... 11 | spring-petclinic-postgres-1 | The files belonging to this database system will be owned by user "postgres". 12 | ... 13 | 14 | 2) Run the app with `spring.profiles.active=postgres` (e.g. as a System property via the command 15 | line, but any way that sets that property in a Spring Boot app should work). For example use 16 | 17 | mvn spring-boot:run -Dspring-boot.run.profiles=postgres 18 | 19 | To activate the profile on the command line. 20 | -------------------------------------------------------------------------------- /src/main/resources/db/postgres/schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS vets ( 2 | id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, 3 | first_name TEXT, 4 | last_name TEXT 5 | ); 6 | CREATE INDEX ON vets (last_name); 7 | 8 | CREATE TABLE IF NOT EXISTS specialties ( 9 | id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, 10 | name TEXT 11 | ); 12 | CREATE INDEX ON specialties (name); 13 | 14 | CREATE TABLE IF NOT EXISTS vet_specialties ( 15 | vet_id INT NOT NULL REFERENCES vets (id), 16 | specialty_id INT NOT NULL REFERENCES specialties (id), 17 | UNIQUE (vet_id, specialty_id) 18 | ); 19 | 20 | CREATE TABLE IF NOT EXISTS types ( 21 | id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, 22 | name TEXT 23 | ); 24 | CREATE INDEX ON types (name); 25 | 26 | CREATE TABLE IF NOT EXISTS owners ( 27 | id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, 28 | first_name TEXT, 29 | last_name TEXT, 30 | address TEXT, 31 | city TEXT, 32 | telephone TEXT 33 | ); 34 | CREATE INDEX ON owners (last_name); 35 | 36 | CREATE TABLE IF NOT EXISTS pets ( 37 | id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, 38 | name TEXT, 39 | birth_date DATE, 40 | type_id INT NOT NULL REFERENCES types (id), 41 | owner_id INT REFERENCES owners (id) 42 | ); 43 | CREATE INDEX ON pets (name); 44 | CREATE INDEX ON pets (owner_id); 45 | 46 | CREATE TABLE IF NOT EXISTS visits ( 47 | id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, 48 | pet_id INT REFERENCES pets (id), 49 | visit_date DATE, 50 | description TEXT 51 | ); 52 | CREATE INDEX ON visits (pet_id); 53 | -------------------------------------------------------------------------------- /src/main/resources/messages/messages.properties: -------------------------------------------------------------------------------- 1 | welcome=Welcome 2 | required=is required 3 | notFound=has not been found 4 | duplicate=is already in use 5 | nonNumeric=must be all numeric 6 | duplicateFormSubmission=Duplicate form submission is not allowed 7 | typeMismatch.date=invalid date 8 | typeMismatch.birthDate=invalid date 9 | owner=Owner 10 | firstName=First Name 11 | lastName=Last Name 12 | address=Address 13 | city=City 14 | telephone=Telephone 15 | owners=Owners 16 | addOwner=Add Owner 17 | findOwner=Find Owner 18 | findOwners=Find Owners 19 | updateOwner=Update Owner 20 | vets=Veterinarians 21 | name=Name 22 | specialties=Specialties 23 | none=none 24 | pages=pages 25 | first=First 26 | next=Next 27 | previous=Previous 28 | last=Last 29 | somethingHappened=Something happened... 30 | pets=Pets 31 | home=Home 32 | error=Error 33 | telephone.invalid=Telephone must be a 10-digit number 34 | layoutTitle=PetClinic :: a Spring Framework demonstration 35 | pet=Pet 36 | birthDate=Birth Date 37 | type=Type 38 | previousVisits=Previous Visits 39 | date=Date 40 | description=Description 41 | new=New 42 | addVisit=Add Visit 43 | editPet=Edit Pet 44 | ownerInformation=Owner Information 45 | visitDate=Visit Date 46 | editOwner=Edit Owner 47 | addNewPet=Add New Pet 48 | petsAndVisits=Pets and Visits 49 | -------------------------------------------------------------------------------- /src/main/resources/messages/messages_de.properties: -------------------------------------------------------------------------------- 1 | welcome=Willkommen 2 | required=muss angegeben werden 3 | notFound=wurde nicht gefunden 4 | duplicate=ist bereits vergeben 5 | nonNumeric=darf nur numerisch sein 6 | duplicateFormSubmission=Wiederholtes Absenden des Formulars ist nicht erlaubt 7 | typeMismatch.date=ung�ltiges Datum 8 | typeMismatch.birthDate=ung�ltiges Datum 9 | owner=Besitzer 10 | firstName=Vorname 11 | lastName=Nachname 12 | address=Adresse 13 | city=Stadt 14 | telephone=Telefon 15 | owners=Besitzer 16 | addOwner=Besitzer hinzufügen 17 | findOwner=Besitzer finden 18 | findOwners=Besitzer suchen 19 | updateOwner=Besitzer aktualisieren 20 | vets=Tierärzte 21 | name=Name 22 | specialties=Fachgebiete 23 | none=keine 24 | pages=Seiten 25 | first=Erste 26 | next=Nächste 27 | previous=Vorherige 28 | last=Letzte 29 | somethingHappened=Etwas ist passiert... 30 | pets=Haustiere 31 | home=Startseite 32 | error=Fehler 33 | telephone.invalid=Telefonnummer muss aus 10 Ziffern bestehen 34 | layoutTitle=PetClinic :: eine Demonstration des Spring Frameworks 35 | pet=Haustier 36 | birthDate=Geburtsdatum 37 | type=Typ 38 | previousVisits=Frühere Besuche 39 | date=Datum 40 | description=Beschreibung 41 | new=Neu 42 | addVisit=Besuch hinzufügen 43 | editPet=Haustier bearbeiten 44 | ownerInformation=Besitzerinformationen 45 | visitDate=Besuchsdatum 46 | editOwner=Besitzer bearbeiten 47 | addNewPet=Neues Haustier hinzufügen 48 | petsAndVisits=Haustiere und Besuche 49 | -------------------------------------------------------------------------------- /src/main/resources/messages/messages_en.properties: -------------------------------------------------------------------------------- 1 | # This file is intentionally empty. Message look-ups will fall back to the default "messages.properties" file. -------------------------------------------------------------------------------- /src/main/resources/messages/messages_es.properties: -------------------------------------------------------------------------------- 1 | welcome=Bienvenido 2 | required=Es requerido 3 | notFound=No ha sido encontrado 4 | duplicate=Ya se encuentra en uso 5 | nonNumeric=Sólo debe contener numeros 6 | duplicateFormSubmission=No se permite el envío de formularios duplicados 7 | typeMismatch.date=Fecha invalida 8 | typeMismatch.birthDate=Fecha invalida 9 | owner=Propietario 10 | firstName=Nombre 11 | lastName=Apellido 12 | address=Dirección 13 | city=Ciudad 14 | telephone=Teléfono 15 | owners=Propietarios 16 | addOwner=Añadir propietario 17 | findOwner=Buscar propietario 18 | findOwners=Buscar propietarios 19 | updateOwner=Actualizar propietario 20 | vets=Veterinarios 21 | name=Nombre 22 | specialties=Especialidades 23 | none=ninguno 24 | pages=páginas 25 | first=Primero 26 | next=Siguiente 27 | previous=Anterior 28 | last=Último 29 | somethingHappened=Algo pasó... 30 | pets=Mascotas 31 | home=Inicio 32 | error=Error 33 | telephone.invalid=El número de teléfono debe tener 10 dígitos 34 | layoutTitle=PetClinic :: una demostración de Spring Framework 35 | pet=Mascota 36 | birthDate=Fecha de nacimiento 37 | type=Tipo 38 | previousVisits=Visitas anteriores 39 | date=Fecha 40 | description=Descripción 41 | new=Nuevo 42 | addVisit=Agregar visita 43 | editPet=Editar mascota 44 | ownerInformation=Información del propietario 45 | visitDate=Fecha de visita 46 | editOwner=Editar propietario 47 | addNewPet=Agregar nueva mascota 48 | petsAndVisits=Mascotas y visitas 49 | -------------------------------------------------------------------------------- /src/main/resources/messages/messages_fa.properties: -------------------------------------------------------------------------------- 1 | welcome=خوش آمدید 2 | required=الزامی 3 | notFound=یافت نشد 4 | duplicate=قبلا استفاده شده 5 | nonNumeric=باید عددی باشد 6 | duplicateFormSubmission=ارسال تکراری فرم مجاز نیست 7 | typeMismatch.date=تاریخ نامعتبر 8 | typeMismatch.birthDate=تاریخ تولد نامعتبر 9 | owner=مالک 10 | firstName=نام 11 | lastName=نام خانوادگی 12 | address=آدرس 13 | city=شهر 14 | telephone=تلفن 15 | owners=مالکان 16 | addOwner=افزودن مالک 17 | findOwner=یافتن مالک 18 | findOwners=یافتن مالکان 19 | updateOwner=ویرایش مالک 20 | vets=دامپزشکان 21 | name=نام 22 | specialties=تخصص‌ها 23 | none=هیچ‌کدام 24 | pages=صفحات 25 | first=اول 26 | next=بعدی 27 | previous=قبلی 28 | last=آخر 29 | somethingHappened=مشکلی پیش آمد... 30 | pets=حیوانات خانگی 31 | home=خانه 32 | error=خطا 33 | telephone.invalid=شماره تلفن باید ۱۰ رقمی باشد 34 | layoutTitle=PetClinic :: یک نمایش از Spring Framework 35 | pet=حیوان خانگی 36 | birthDate=تاریخ تولد 37 | type=نوع 38 | previousVisits=ویزیت‌های قبلی 39 | date=تاریخ 40 | description=توضیحات 41 | new=جدید 42 | addVisit=افزودن ویزیت 43 | editPet=ویرایش حیوان خانگی 44 | ownerInformation=اطلاعات مالک 45 | visitDate=تاریخ ویزیت 46 | editOwner=ویرایش مالک 47 | addNewPet=افزودن حیوان خانگی جدید 48 | petsAndVisits=حیوانات و ویزیت‌ها 49 | -------------------------------------------------------------------------------- /src/main/resources/messages/messages_ko.properties: -------------------------------------------------------------------------------- 1 | welcome=환영합니다 2 | required=입력이 필요합니다 3 | notFound=찾을 수 없습니다 4 | duplicate=이미 존재합니다 5 | nonNumeric=모두 숫자로 입력해야 합니다 6 | duplicateFormSubmission=중복 제출은 허용되지 않습니다 7 | typeMismatch.date=잘못된 날짜입니다 8 | typeMismatch.birthDate=잘못된 날짜입니다 9 | owner=소유자 10 | firstName=이름 11 | lastName=성 12 | address=주소 13 | city=도시 14 | telephone=전화번호 15 | owners=소유자 목록 16 | addOwner=소유자 추가 17 | findOwner=소유자 찾기 18 | findOwners=소유자들 찾기 19 | updateOwner=소유자 수정 20 | vets=수의사 21 | name=이름 22 | specialties=전문 분야 23 | none=없음 24 | pages=페이지 25 | first=첫 번째 26 | next=다음 27 | previous=이전 28 | last=마지막 29 | somethingHappened=문제가 발생했습니다... 30 | pets=반려동물 31 | home=홈 32 | error=오류 33 | telephone.invalid=전화번호는 10자리 숫자여야 합니다 34 | layoutTitle=PetClinic :: Spring Framework 데모 35 | pet=반려동물 36 | birthDate=생년월일 37 | type=종류 38 | previousVisits=이전 방문 39 | date=날짜 40 | description=설명 41 | new=새로운 42 | addVisit=방문 추가 43 | editPet=반려동물 수정 44 | ownerInformation=소유자 정보 45 | visitDate=방문 날짜 46 | editOwner=소유자 수정 47 | addNewPet=새 반려동물 추가 48 | petsAndVisits=반려동물 및 방문 49 | -------------------------------------------------------------------------------- /src/main/resources/messages/messages_pt.properties: -------------------------------------------------------------------------------- 1 | welcome=Bem-vindo 2 | required=E necessario 3 | notFound=Nao foi encontrado 4 | duplicate=Ja esta em uso 5 | nonNumeric=Deve ser tudo numerico 6 | duplicateFormSubmission=O envio duplicado de formulario nao e permitido 7 | typeMismatch.date=Data invalida 8 | typeMismatch.birthDate=Data de nascimento invalida 9 | owner=Proprietário 10 | firstName=Primeiro Nome 11 | lastName=Sobrenome 12 | address=Endereço 13 | city=Cidade 14 | telephone=Telefone 15 | owners=Proprietários 16 | addOwner=Adicionar proprietário 17 | findOwner=Encontrar proprietário 18 | findOwners=Encontrar proprietários 19 | updateOwner=Atualizar proprietário 20 | vets=Veterinários 21 | name=Nome 22 | specialties=Especialidades 23 | none=nenhum 24 | pages=páginas 25 | first=Primeiro 26 | next=Próximo 27 | previous=Anterior 28 | last=Último 29 | somethingHappened=Algo aconteceu... 30 | pets=Animais de estimação 31 | home=Início 32 | error=Erro 33 | telephone.invalid=O número de telefone deve conter 10 dígitos 34 | layoutTitle=PetClinic :: uma demonstração do Spring Framework 35 | pet=Animal de estimação 36 | birthDate=Data de nascimento 37 | type=Tipo 38 | previousVisits=Visitas anteriores 39 | date=Data 40 | description=Descrição 41 | new=Novo 42 | addVisit=Adicionar visita 43 | editPet=Editar animal 44 | ownerInformation=Informações do proprietário 45 | visitDate=Data da visita 46 | editOwner=Editar proprietário 47 | addNewPet=Adicionar novo animal 48 | petsAndVisits=Animais e visitas 49 | -------------------------------------------------------------------------------- /src/main/resources/messages/messages_ru.properties: -------------------------------------------------------------------------------- 1 | welcome=Добро пожаловать 2 | required=необходимо 3 | notFound=не найдено 4 | duplicate=уже используется 5 | nonNumeric=должно быть все числовое значение 6 | duplicateFormSubmission=Дублирование формы не допускается 7 | typeMismatch.date=неправильная даные 8 | typeMismatch.birthDate=неправильная дата 9 | owner=Владелец 10 | firstName=Имя 11 | lastName=Фамилия 12 | address=Адрес 13 | city=Город 14 | telephone=Телефон 15 | owners=Владельцы 16 | addOwner=Добавить владельца 17 | findOwner=Найти владельца 18 | findOwners=Найти владельцев 19 | updateOwner=Обновить владельца 20 | vets=Ветеринары 21 | name=Имя 22 | specialties=Специальности 23 | none=нет 24 | pages=страницы 25 | first=Первый 26 | next=Следующий 27 | previous=Предыдущий 28 | last=Последний 29 | somethingHappened=Что-то пошло не так... 30 | pets=Питомцы 31 | home=Главная 32 | error=Ошибка 33 | telephone.invalid=Телефон должен содержать 10 цифр 34 | layoutTitle=PetClinic :: демонстрация Spring Framework 35 | pet=Питомец 36 | birthDate=Дата рождения 37 | type=Тип 38 | previousVisits=Предыдущие визиты 39 | date=Дата 40 | description=Описание 41 | new=Новый 42 | addVisit=Добавить визит 43 | editPet=Редактировать питомца 44 | ownerInformation=Информация о владельце 45 | visitDate=Дата визита 46 | editOwner=Редактировать владельца 47 | addNewPet=Добавить нового питомца 48 | petsAndVisits=Питомцы и визиты 49 | -------------------------------------------------------------------------------- /src/main/resources/messages/messages_tr.properties: -------------------------------------------------------------------------------- 1 | welcome=hoş geldiniz 2 | required=gerekli 3 | notFound=bulunamadı 4 | duplicate=zaten kullanılıyor 5 | nonNumeric=sadece sayısal olmalıdır 6 | duplicateFormSubmission=Formun tekrar gönderilmesine izin verilmez 7 | typeMismatch.date=geçersiz tarih 8 | typeMismatch.birthDate=geçersiz tarih 9 | owner=Sahip 10 | firstName=Ad 11 | lastName=Soyad 12 | address=Adres 13 | city=Şehir 14 | telephone=Telefon 15 | owners=Sahipler 16 | addOwner=Sahip Ekle 17 | findOwner=Sahip Bul 18 | findOwners=Sahipleri Bul 19 | updateOwner=Sahip Güncelle 20 | vets=Veterinerler 21 | name=İsim 22 | specialties=Uzmanlıklar 23 | none=yok 24 | pages=sayfalar 25 | first=İlk 26 | next=Sonraki 27 | previous=Önceki 28 | last=Son 29 | somethingHappened=Bir şey oldu... 30 | pets=Evcil Hayvanlar 31 | home=Ana Sayfa 32 | error=Hata 33 | telephone.invalid=Telefon numarası 10 basamaklı olmalıdır 34 | layoutTitle=PetClinic :: bir Spring Framework demosu 35 | pet=Evcil Hayvan 36 | birthDate=Doğum Tarihi 37 | type=Tür 38 | previousVisits=Önceki Ziyaretler 39 | date=Tarih 40 | description=Açıklama 41 | new=Yeni 42 | addVisit=Ziyaret Ekle 43 | editPet=Evcil Hayvanı Düzenle 44 | ownerInformation=Sahip Bilgileri 45 | visitDate=Ziyaret Tarihi 46 | editOwner=Sahibi Düzenle 47 | addNewPet=Yeni Evcil Hayvan Ekle 48 | petsAndVisits=Evcil Hayvanlar ve Ziyaretler 49 | -------------------------------------------------------------------------------- /src/main/resources/static/resources/fonts/montserrat-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-projects/spring-petclinic/cefaf55dd124d0635abfe857c3c99a3d3ea62017/src/main/resources/static/resources/fonts/montserrat-webfont.eot -------------------------------------------------------------------------------- /src/main/resources/static/resources/fonts/montserrat-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-projects/spring-petclinic/cefaf55dd124d0635abfe857c3c99a3d3ea62017/src/main/resources/static/resources/fonts/montserrat-webfont.ttf -------------------------------------------------------------------------------- /src/main/resources/static/resources/fonts/montserrat-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-projects/spring-petclinic/cefaf55dd124d0635abfe857c3c99a3d3ea62017/src/main/resources/static/resources/fonts/montserrat-webfont.woff -------------------------------------------------------------------------------- /src/main/resources/static/resources/fonts/varela_round-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-projects/spring-petclinic/cefaf55dd124d0635abfe857c3c99a3d3ea62017/src/main/resources/static/resources/fonts/varela_round-webfont.eot -------------------------------------------------------------------------------- /src/main/resources/static/resources/fonts/varela_round-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-projects/spring-petclinic/cefaf55dd124d0635abfe857c3c99a3d3ea62017/src/main/resources/static/resources/fonts/varela_round-webfont.ttf -------------------------------------------------------------------------------- /src/main/resources/static/resources/fonts/varela_round-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-projects/spring-petclinic/cefaf55dd124d0635abfe857c3c99a3d3ea62017/src/main/resources/static/resources/fonts/varela_round-webfont.woff -------------------------------------------------------------------------------- /src/main/resources/static/resources/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-projects/spring-petclinic/cefaf55dd124d0635abfe857c3c99a3d3ea62017/src/main/resources/static/resources/images/favicon.png -------------------------------------------------------------------------------- /src/main/resources/static/resources/images/pets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-projects/spring-petclinic/cefaf55dd124d0635abfe857c3c99a3d3ea62017/src/main/resources/static/resources/images/pets.png -------------------------------------------------------------------------------- /src/main/resources/static/resources/images/spring-logo-dataflow-mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-projects/spring-petclinic/cefaf55dd124d0635abfe857c3c99a3d3ea62017/src/main/resources/static/resources/images/spring-logo-dataflow-mobile.png -------------------------------------------------------------------------------- /src/main/resources/static/resources/images/spring-logo-dataflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-projects/spring-petclinic/cefaf55dd124d0635abfe857c3c99a3d3ea62017/src/main/resources/static/resources/images/spring-logo-dataflow.png -------------------------------------------------------------------------------- /src/main/resources/static/resources/images/spring-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 13 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 29 | 30 | 32 | 33 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 61 | 62 | 64 | 66 | 67 | -------------------------------------------------------------------------------- /src/main/resources/templates/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Something happened...

8 |

Exception message

9 | 10 | 11 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/inputField.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 |
10 | 11 |
12 |
13 | 14 | 15 |
16 | 17 | 18 | 19 | Error 20 | 21 |
22 |
23 |
24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | PetClinic :: a Spring Framework demonstration 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 66 |
67 |
68 | 69 | 70 | 71 |
72 |
73 |
74 |
75 |
76 | 78 |
79 |
80 |
81 |
82 |
83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/selectField.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 |
10 | 11 | 12 |
13 | 16 | 17 | 18 | 19 | Error 20 | 21 |
22 |
23 |
24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /src/main/resources/templates/owners/createOrUpdateOwnerForm.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Owner

8 |
9 |
10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |
18 | 20 |
21 |
22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /src/main/resources/templates/owners/findOwners.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Find Owners

8 | 9 |
10 |
11 |
12 | 13 |
14 | 15 | 16 |
17 |

Error

18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | 26 |
27 |
28 | 29 | Add Owner 30 | 31 |
32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/main/resources/templates/owners/ownerDetails.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Owner Information

8 | 9 |
10 | 11 |
12 | 13 |
14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
Name
Address
City
Telephone
35 | 36 | Edit 37 | Owner 38 | Add 39 | New Pet 40 | 41 |
42 |
43 |
44 |

Pets and Visits

45 | 46 | 47 | 48 | 49 | 59 | 77 | 78 | 79 |
50 |
51 |
Name
52 |
53 |
Birth Date
54 |
55 |
Type
56 |
57 |
58 |
60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 |
Visit DateDescription
Edit PetAdd Visit
76 |
80 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /src/main/resources/templates/owners/ownersList.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Owners

8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 24 | 28 | 29 | 30 |
NameAddressCityTelephonePets
22 | 23 | 25 | 26 | 27 |
31 |
32 | Pages: 33 | [ 34 | 35 | [[${i}]] 36 | [[${i}]] 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 51 | 52 | 53 | 54 | 56 | 57 | 58 |
59 | 60 | 61 | -------------------------------------------------------------------------------- /src/main/resources/templates/pets/createOrUpdatePetForm.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

8 | New 9 | Pet 10 |

11 |
12 | 13 |
14 |
15 | 16 |
17 | 18 |
19 |
20 | 21 | 22 | 23 |
24 |
25 |
26 | 28 |
29 |
30 |
31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/main/resources/templates/pets/createOrUpdateVisitForm.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

8 | New 9 | Visit 10 |

11 | 12 | Pet 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
NameBirth DateTypeOwner
29 | 30 |
31 |
32 | 33 | 34 |
35 | 36 |
37 |
38 | 39 | 40 |
41 |
42 |
43 | 44 |
45 | Previous Visits 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
DateDescription
56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/main/resources/templates/vets/vetList.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Veterinarians

8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 23 | 24 | 25 |
NameSpecialties
20 | none 22 |
26 |
27 | Pages: 28 | [ 29 | 30 | [[${i}]] 31 | [[${i}]] 32 | 33 | 34 | 35 | 37 | 38 | 39 | 40 | 42 | 43 | 44 | 45 | 47 | 48 | 49 | 50 | 52 | 53 | 54 |
55 | 56 | 57 | -------------------------------------------------------------------------------- /src/main/resources/templates/welcome.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Welcome

8 |
9 |
10 | 11 |
12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/main/scss/header.scss: -------------------------------------------------------------------------------- 1 | .navbar { 2 | border-top: 4px solid #6db33f; 3 | background-color: #34302d; 4 | margin-bottom: 0px; 5 | border-bottom: 0; 6 | border-left: 0; 7 | border-right: 0; 8 | } 9 | 10 | .navbar a.navbar-brand { 11 | background: url("../images/spring-logo-dataflow.png") -1px -1px no-repeat; 12 | margin: 12px 0 6px; 13 | width: 229px; 14 | height: 46px; 15 | display: inline-block; 16 | text-decoration: none; 17 | padding: 0; 18 | } 19 | 20 | .navbar a.navbar-brand span { 21 | display: block; 22 | width: 229px; 23 | height: 46px; 24 | background: url("../images/spring-logo-dataflow.png") -1px -48px no-repeat; 25 | opacity: 0; 26 | -moz-transition: opacity 0.12s ease-in-out; 27 | -webkit-transition: opacity 0.12s ease-in-out; 28 | -o-transition: opacity 0.12s ease-in-out; 29 | } 30 | 31 | .navbar a:hover.navbar-brand span { 32 | opacity: 1; 33 | } 34 | 35 | .navbar li > a, .navbar-text { 36 | font-family: "montserratregular", sans-serif; 37 | text-shadow: none; 38 | font-size: 14px; 39 | 40 | /* line-height: 14px; */ 41 | padding: 28px 20px; 42 | transition: all 0.15s; 43 | -webkit-transition: all 0.15s; 44 | -moz-transition: all 0.15s; 45 | -o-transition: all 0.15s; 46 | -ms-transition: all 0.15s; 47 | } 48 | 49 | .navbar li > a { 50 | text-transform: uppercase; 51 | } 52 | 53 | .navbar .navbar-text { 54 | margin-top: 0; 55 | margin-bottom: 0; 56 | } 57 | .navbar li:hover > a { 58 | color: #eeeeee; 59 | background-color: #6db33f; 60 | } 61 | 62 | .navbar-toggle { 63 | border-width: 0; 64 | 65 | .icon-bar + .icon-bar { 66 | margin-top: 3px; 67 | } 68 | .icon-bar { 69 | width: 19px; 70 | height: 3px; 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/main/scss/petclinic.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 3 | * 4 | * You may obtain a copy of the License at 5 | * 6 | * https://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | @import "bootstrap"; 15 | 16 | $icon-font-path: "../../webjars/bootstrap/fonts/"; 17 | 18 | $spring-green: #6db33f; 19 | $spring-dark-green: #5fa134; 20 | $spring-brown: #34302D; 21 | $spring-grey: #838789; 22 | $spring-light-grey: #f1f1f1; 23 | 24 | $body-bg: $spring-light-grey; 25 | $text-color: $spring-brown; 26 | $link-color: $spring-dark-green; 27 | $link-hover-color: $spring-dark-green; 28 | 29 | $navbar-default-link-color: $spring-light-grey; 30 | $navbar-default-link-active-color: $spring-light-grey; 31 | $navbar-default-link-hover-color: $spring-light-grey; 32 | $navbar-default-link-hover-bg: $spring-green; 33 | $navbar-default-toggle-icon-bar-bg: $spring-light-grey; 34 | $navbar-default-toggle-hover-bg: transparent; 35 | $navbar-default-link-active-bg: $spring-green; 36 | 37 | $border-radius-base: 0; 38 | $border-radius-large: 0; 39 | $border-radius-small: 0; 40 | 41 | $nav-tabs-active-link-hover-color: $spring-light-grey; 42 | $nav-tabs-active-link-hover-bg: $spring-brown; 43 | $nav-tabs-active-link-hover-border-color: $spring-brown; 44 | $nav-tabs-border-color: $spring-brown; 45 | 46 | $pagination-active-bg: $spring-brown; 47 | $pagination-active-border: $spring-green; 48 | $table-border-color: $spring-brown; 49 | 50 | .table > thead > tr > th { 51 | background-color: lighten($spring-brown, 3%); 52 | color: $spring-light-grey; 53 | } 54 | 55 | .table-filter { 56 | background-color: $spring-brown; 57 | padding: 9px 12px; 58 | } 59 | 60 | .nav > li > a { 61 | color: $spring-grey; 62 | } 63 | 64 | .btn-primary { 65 | margin-top: 12px; 66 | border-width: 2px; 67 | transition: border 0.15s; 68 | color: $spring-light-grey; 69 | background: $spring-brown; 70 | border-color: $spring-green; 71 | -webkit-transition: border 0.15s; 72 | -moz-transition: border 0.15s; 73 | -o-transition: border 0.15s; 74 | -ms-transition: border 0.15s; 75 | 76 | &:hover, 77 | &:focus, 78 | &:active, 79 | &.active, 80 | .open .dropdown-toggle { 81 | background-color: $spring-brown; 82 | border-color: $spring-brown; 83 | } 84 | } 85 | 86 | 87 | .container .text-muted { 88 | margin: 20px 0; 89 | } 90 | 91 | code { 92 | font-size: 80%; 93 | } 94 | 95 | .xd-container { 96 | margin-top: 40px; 97 | margin-bottom: 100px; 98 | padding-left: 5px; 99 | padding-right: 5px; 100 | } 101 | 102 | h1 { 103 | margin-bottom: 15px 104 | } 105 | 106 | .index-page--subtitle { 107 | font-size: 16px; 108 | line-height: 24px; 109 | margin: 0 0 30px; 110 | } 111 | 112 | .form-horizontal button.btn-inverse { 113 | margin-left: 32px; 114 | } 115 | 116 | #job-params-modal .modal-dialog { 117 | width: 90%; 118 | margin-left:auto; 119 | margin-right:auto; 120 | } 121 | 122 | [ng-cloak].splash { 123 | display: block !important; 124 | } 125 | [ng-cloak] { 126 | display: none; 127 | } 128 | 129 | .splash { 130 | background: $spring-green; 131 | color: $spring-brown; 132 | display: none; 133 | } 134 | 135 | .error-page { 136 | margin-top: 100px; 137 | text-align: center; 138 | } 139 | 140 | .error-page .error-title { 141 | font-size: 24px; 142 | line-height: 24px; 143 | margin: 30px 0 0; 144 | } 145 | 146 | table td { 147 | vertical-align: middle; 148 | } 149 | 150 | table td .progress { 151 | margin-bottom: 0; 152 | } 153 | 154 | table td.action-column { 155 | width: 1px; 156 | } 157 | 158 | .help-block { 159 | color: lighten($text-color, 50%); // lighten the text some for contrast 160 | } 161 | 162 | .xd-containers { 163 | font-size: 15px; 164 | } 165 | 166 | .cluster-view > table td { 167 | vertical-align: top; 168 | } 169 | 170 | .cluster-view .label, .cluster-view .column-block { 171 | display: block; 172 | } 173 | 174 | .cluster-view .input-group-addon { 175 | width: 0%; 176 | } 177 | 178 | .cluster-view { 179 | margin-bottom: 0; 180 | } 181 | 182 | .container-details-table th { 183 | background-color: lighten($spring-brown, 3%); 184 | color: $spring-light-grey; 185 | } 186 | 187 | .status-help-content-table td { 188 | color: $spring-brown; 189 | } 190 | 191 | .logo { 192 | width: 200px; 193 | } 194 | 195 | .myspinner { 196 | animation-name: spinner; 197 | animation-duration: 2s; 198 | animation-iteration-count: infinite; 199 | animation-timing-function: linear; 200 | 201 | -webkit-transform-origin: 49% 50%; 202 | -webkit-animation-name: spinner; 203 | -webkit-animation-duration: 2s; 204 | -webkit-animation-iteration-count: infinite; 205 | -webkit-animation-timing-function: linear; 206 | } 207 | 208 | hr { 209 | border-top: 1px dotted $spring-brown; 210 | } 211 | 212 | @import "typography.scss"; 213 | @import "header.scss"; 214 | @import "responsive.scss"; 215 | -------------------------------------------------------------------------------- /src/main/scss/responsive.scss: -------------------------------------------------------------------------------- 1 | @media (max-width: 768px) { 2 | .navbar-toggle { 3 | position:absolute; 4 | z-index: 9999; 5 | left:0px; 6 | top:0px; 7 | } 8 | 9 | .navbar a.navbar-brand { 10 | display: block; 11 | margin: 0 auto 0 auto; 12 | width: 148px; 13 | height: 50px; 14 | float: none; 15 | background: url("../images/spring-logo-dataflow-mobile.png") 0 center no-repeat; 16 | } 17 | 18 | .homepage-billboard .homepage-subtitle { 19 | font-size: 21px; 20 | line-height: 21px; 21 | } 22 | 23 | .navbar a.navbar-brand span { 24 | display: none; 25 | } 26 | 27 | .navbar { 28 | border-top-width: 0; 29 | } 30 | 31 | .xd-container { 32 | margin-top: 20px; 33 | margin-bottom: 30px; 34 | } 35 | 36 | .index-page--subtitle { 37 | margin-top: 10px; 38 | margin-bottom: 30px; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/scss/typography.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'varela_roundregular'; 3 | 4 | src: url('../fonts/varela_round-webfont.eot'); 5 | src: url('../fonts/varela_round-webfont.eot?#iefix') format('embedded-opentype'), 6 | url('../fonts/varela_round-webfont.woff') format('woff'), 7 | url('../fonts/varela_round-webfont.ttf') format('truetype'), 8 | url('../fonts/varela_round-webfont.svg#varela_roundregular') format('svg'); 9 | font-weight: normal; 10 | font-style: normal; 11 | } 12 | 13 | @font-face { 14 | font-family: 'montserratregular'; 15 | src: url('../fonts/montserrat-webfont.eot'); 16 | src: url('../fonts/montserrat-webfont.eot?#iefix') format('embedded-opentype'), 17 | url('../fonts/montserrat-webfont.woff') format('woff'), 18 | url('../fonts/montserrat-webfont.ttf') format('truetype'), 19 | url('../fonts/montserrat-webfont.svg#montserratregular') format('svg'); 20 | font-weight: normal; 21 | font-style: normal; 22 | } 23 | 24 | body, h1, h2, h3, p, input { 25 | margin: 0; 26 | font-weight: 400; 27 | font-family: "varela_roundregular", sans-serif; 28 | color: #34302d; 29 | } 30 | 31 | h1 { 32 | font-size: 24px; 33 | line-height: 30px; 34 | font-family: "montserratregular", sans-serif; 35 | } 36 | 37 | h2 { 38 | font-size: 18px; 39 | font-weight: 700; 40 | line-height: 24px; 41 | margin-bottom: 10px; 42 | font-family: "montserratregular", sans-serif; 43 | } 44 | 45 | h3 { 46 | font-size: 16px; 47 | line-height: 24px; 48 | margin-bottom: 10px; 49 | font-weight: 700; 50 | } 51 | 52 | p { 53 | //font-size: 15px; 54 | //line-height: 24px; 55 | } 56 | 57 | strong { 58 | font-weight: 700; 59 | font-family: "montserratregular", sans-serif; 60 | } 61 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/samples/petclinic/MySqlIntegrationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.samples.petclinic; 18 | 19 | import static org.assertj.core.api.Assertions.assertThat; 20 | 21 | import org.junit.jupiter.api.Test; 22 | import org.junit.jupiter.api.condition.DisabledInNativeImage; 23 | import org.springframework.beans.factory.annotation.Autowired; 24 | import org.springframework.boot.test.context.SpringBootTest; 25 | import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; 26 | import org.springframework.boot.test.web.server.LocalServerPort; 27 | import org.springframework.boot.testcontainers.service.connection.ServiceConnection; 28 | import org.springframework.boot.web.client.RestTemplateBuilder; 29 | import org.springframework.http.HttpStatus; 30 | import org.springframework.http.RequestEntity; 31 | import org.springframework.http.ResponseEntity; 32 | import org.springframework.samples.petclinic.vet.VetRepository; 33 | import org.springframework.test.context.ActiveProfiles; 34 | import org.springframework.test.context.aot.DisabledInAotMode; 35 | import org.springframework.web.client.RestTemplate; 36 | import org.testcontainers.containers.MySQLContainer; 37 | import org.testcontainers.junit.jupiter.Container; 38 | import org.testcontainers.junit.jupiter.Testcontainers; 39 | import org.testcontainers.utility.DockerImageName; 40 | 41 | @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) 42 | @ActiveProfiles("mysql") 43 | @Testcontainers(disabledWithoutDocker = true) 44 | @DisabledInNativeImage 45 | @DisabledInAotMode 46 | class MySqlIntegrationTests { 47 | 48 | @ServiceConnection 49 | @Container 50 | static MySQLContainer container = new MySQLContainer<>(DockerImageName.parse("mysql:9.2")); 51 | 52 | @LocalServerPort 53 | int port; 54 | 55 | @Autowired 56 | private VetRepository vets; 57 | 58 | @Autowired 59 | private RestTemplateBuilder builder; 60 | 61 | @Test 62 | void testFindAll() { 63 | vets.findAll(); 64 | vets.findAll(); // served from cache 65 | } 66 | 67 | @Test 68 | void testOwnerDetails() { 69 | RestTemplate template = builder.rootUri("http://localhost:" + port).build(); 70 | ResponseEntity result = template.exchange(RequestEntity.get("/owners/1").build(), String.class); 71 | assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/samples/petclinic/MysqlTestApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.samples.petclinic; 18 | 19 | import org.springframework.boot.SpringApplication; 20 | import org.springframework.boot.testcontainers.service.connection.ServiceConnection; 21 | import org.springframework.context.annotation.Bean; 22 | import org.springframework.context.annotation.Configuration; 23 | import org.springframework.context.annotation.Profile; 24 | import org.testcontainers.containers.MySQLContainer; 25 | import org.testcontainers.utility.DockerImageName; 26 | 27 | /** 28 | * PetClinic Spring Boot Application. 29 | * 30 | * @author Dave Syer 31 | */ 32 | @Configuration 33 | public class MysqlTestApplication { 34 | 35 | @ServiceConnection 36 | @Profile("mysql") 37 | @Bean 38 | static MySQLContainer container() { 39 | return new MySQLContainer<>(DockerImageName.parse("mysql:9.2")); 40 | } 41 | 42 | public static void main(String[] args) { 43 | SpringApplication.run(PetClinicApplication.class, "--spring.profiles.active=mysql", 44 | "--spring.docker.compose.enabled=false"); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/samples/petclinic/PetClinicIntegrationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.samples.petclinic; 18 | 19 | import static org.assertj.core.api.Assertions.assertThat; 20 | 21 | import org.junit.jupiter.api.Test; 22 | import org.springframework.beans.factory.annotation.Autowired; 23 | import org.springframework.boot.SpringApplication; 24 | import org.springframework.boot.test.context.SpringBootTest; 25 | import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; 26 | import org.springframework.boot.test.web.server.LocalServerPort; 27 | import org.springframework.boot.web.client.RestTemplateBuilder; 28 | import org.springframework.http.HttpStatus; 29 | import org.springframework.http.RequestEntity; 30 | import org.springframework.http.ResponseEntity; 31 | import org.springframework.samples.petclinic.vet.VetRepository; 32 | import org.springframework.web.client.RestTemplate; 33 | 34 | @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) 35 | public class PetClinicIntegrationTests { 36 | 37 | @LocalServerPort 38 | int port; 39 | 40 | @Autowired 41 | private VetRepository vets; 42 | 43 | @Autowired 44 | private RestTemplateBuilder builder; 45 | 46 | @Test 47 | void testFindAll() { 48 | vets.findAll(); 49 | vets.findAll(); // served from cache 50 | } 51 | 52 | @Test 53 | void testOwnerDetails() { 54 | RestTemplate template = builder.rootUri("http://localhost:" + port).build(); 55 | ResponseEntity result = template.exchange(RequestEntity.get("/owners/1").build(), String.class); 56 | assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); 57 | } 58 | 59 | public static void main(String[] args) { 60 | SpringApplication.run(PetClinicApplication.class, args); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/samples/petclinic/PostgresIntegrationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.samples.petclinic; 18 | 19 | import static org.assertj.core.api.Assertions.assertThat; 20 | import static org.junit.jupiter.api.Assertions.assertNotNull; 21 | import static org.junit.jupiter.api.Assumptions.assumeTrue; 22 | 23 | import java.util.Arrays; 24 | import java.util.LinkedList; 25 | import java.util.List; 26 | 27 | import org.apache.commons.logging.Log; 28 | import org.apache.commons.logging.LogFactory; 29 | import org.junit.jupiter.api.BeforeAll; 30 | import org.junit.jupiter.api.Test; 31 | import org.junit.jupiter.api.condition.DisabledInNativeImage; 32 | import org.springframework.beans.factory.annotation.Autowired; 33 | import org.springframework.boot.builder.SpringApplicationBuilder; 34 | import org.springframework.boot.context.event.ApplicationPreparedEvent; 35 | import org.springframework.boot.test.context.SpringBootTest; 36 | import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; 37 | import org.springframework.boot.test.web.server.LocalServerPort; 38 | import org.springframework.boot.web.client.RestTemplateBuilder; 39 | import org.springframework.context.ApplicationListener; 40 | import org.springframework.core.env.ConfigurableEnvironment; 41 | import org.springframework.core.env.EnumerablePropertySource; 42 | import org.springframework.core.env.PropertySource; 43 | import org.springframework.http.HttpStatus; 44 | import org.springframework.http.RequestEntity; 45 | import org.springframework.http.ResponseEntity; 46 | import org.springframework.samples.petclinic.vet.VetRepository; 47 | import org.springframework.test.context.ActiveProfiles; 48 | import org.springframework.web.client.RestTemplate; 49 | import org.testcontainers.DockerClientFactory; 50 | 51 | @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = { "spring.docker.compose.skip.in-tests=false", // 52 | "spring.docker.compose.start.arguments=--force-recreate,--renew-anon-volumes,postgres" }) 53 | @ActiveProfiles("postgres") 54 | @DisabledInNativeImage 55 | public class PostgresIntegrationTests { 56 | 57 | @LocalServerPort 58 | int port; 59 | 60 | @Autowired 61 | private VetRepository vets; 62 | 63 | @Autowired 64 | private RestTemplateBuilder builder; 65 | 66 | @BeforeAll 67 | static void available() { 68 | assumeTrue(DockerClientFactory.instance().isDockerAvailable(), "Docker not available"); 69 | } 70 | 71 | public static void main(String[] args) { 72 | new SpringApplicationBuilder(PetClinicApplication.class) // 73 | .profiles("postgres") // 74 | .properties( // 75 | "spring.docker.compose.start.arguments=postgres" // 76 | ) // 77 | .listeners(new PropertiesLogger()) // 78 | .run(args); 79 | } 80 | 81 | @Test 82 | void testFindAll() throws Exception { 83 | vets.findAll(); 84 | vets.findAll(); // served from cache 85 | } 86 | 87 | @Test 88 | void testOwnerDetails() { 89 | RestTemplate template = builder.rootUri("http://localhost:" + port).build(); 90 | ResponseEntity result = template.exchange(RequestEntity.get("/owners/1").build(), String.class); 91 | assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); 92 | } 93 | 94 | static class PropertiesLogger implements ApplicationListener { 95 | 96 | private static final Log log = LogFactory.getLog(PropertiesLogger.class); 97 | 98 | private ConfigurableEnvironment environment; 99 | 100 | private boolean isFirstRun = true; 101 | 102 | @Override 103 | public void onApplicationEvent(ApplicationPreparedEvent event) { 104 | if (isFirstRun) { 105 | environment = event.getApplicationContext().getEnvironment(); 106 | printProperties(); 107 | } 108 | isFirstRun = false; 109 | } 110 | 111 | public void printProperties() { 112 | for (EnumerablePropertySource source : findPropertiesPropertySources()) { 113 | log.info("PropertySource: " + source.getName()); 114 | String[] names = source.getPropertyNames(); 115 | Arrays.sort(names); 116 | for (String name : names) { 117 | String resolved = environment.getProperty(name); 118 | 119 | assertNotNull(resolved, "resolved environment property: " + name + " is null."); 120 | 121 | Object sourceProperty = source.getProperty(name); 122 | 123 | assertNotNull(sourceProperty, "source property was expecting an object but is null."); 124 | 125 | assertNotNull(sourceProperty.toString(), "source property toString() returned null."); 126 | 127 | String value = sourceProperty.toString(); 128 | if (resolved.equals(value)) { 129 | log.info(name + "=" + resolved); 130 | } 131 | else { 132 | log.info(name + "=" + value + " OVERRIDDEN to " + resolved); 133 | } 134 | } 135 | } 136 | } 137 | 138 | private List> findPropertiesPropertySources() { 139 | List> sources = new LinkedList<>(); 140 | for (PropertySource source : environment.getPropertySources()) { 141 | if (source instanceof EnumerablePropertySource enumerable) { 142 | sources.add(enumerable); 143 | } 144 | } 145 | return sources; 146 | } 147 | 148 | } 149 | 150 | } 151 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/samples/petclinic/model/ValidatorTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.samples.petclinic.model; 18 | 19 | import static org.assertj.core.api.Assertions.assertThat; 20 | 21 | import java.util.Locale; 22 | import java.util.Set; 23 | 24 | import org.junit.jupiter.api.Test; 25 | import org.springframework.context.i18n.LocaleContextHolder; 26 | import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; 27 | 28 | import jakarta.validation.ConstraintViolation; 29 | import jakarta.validation.Validator; 30 | 31 | /** 32 | * @author Michael Isvy Simple test to make sure that Bean Validation is working (useful 33 | * when upgrading to a new version of Hibernate Validator/ Bean Validation) 34 | */ 35 | class ValidatorTests { 36 | 37 | private Validator createValidator() { 38 | LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean(); 39 | localValidatorFactoryBean.afterPropertiesSet(); 40 | return localValidatorFactoryBean; 41 | } 42 | 43 | @Test 44 | void shouldNotValidateWhenFirstNameEmpty() { 45 | 46 | LocaleContextHolder.setLocale(Locale.ENGLISH); 47 | Person person = new Person(); 48 | person.setFirstName(""); 49 | person.setLastName("smith"); 50 | 51 | Validator validator = createValidator(); 52 | Set> constraintViolations = validator.validate(person); 53 | 54 | assertThat(constraintViolations).hasSize(1); 55 | ConstraintViolation violation = constraintViolations.iterator().next(); 56 | assertThat(violation.getPropertyPath()).hasToString("firstName"); 57 | assertThat(violation.getMessage()).isEqualTo("must not be blank"); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/samples/petclinic/owner/PetTypeFormatterTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.samples.petclinic.owner; 18 | 19 | import static org.assertj.core.api.Assertions.assertThat; 20 | import static org.mockito.BDDMockito.given; 21 | 22 | import java.text.ParseException; 23 | import java.util.ArrayList; 24 | import java.util.Collection; 25 | import java.util.List; 26 | import java.util.Locale; 27 | 28 | import org.junit.jupiter.api.Assertions; 29 | import org.junit.jupiter.api.BeforeEach; 30 | import org.junit.jupiter.api.Test; 31 | import org.junit.jupiter.api.condition.DisabledInNativeImage; 32 | import org.junit.jupiter.api.extension.ExtendWith; 33 | import org.mockito.Mock; 34 | import org.mockito.junit.jupiter.MockitoExtension; 35 | 36 | /** 37 | * Test class for {@link PetTypeFormatter} 38 | * 39 | * @author Colin But 40 | */ 41 | @ExtendWith(MockitoExtension.class) 42 | @DisabledInNativeImage 43 | class PetTypeFormatterTests { 44 | 45 | @Mock 46 | private PetTypeRepository types; 47 | 48 | private PetTypeFormatter petTypeFormatter; 49 | 50 | @BeforeEach 51 | void setup() { 52 | this.petTypeFormatter = new PetTypeFormatter(types); 53 | } 54 | 55 | @Test 56 | void testPrint() { 57 | PetType petType = new PetType(); 58 | petType.setName("Hamster"); 59 | String petTypeName = this.petTypeFormatter.print(petType, Locale.ENGLISH); 60 | assertThat(petTypeName).isEqualTo("Hamster"); 61 | } 62 | 63 | @Test 64 | void shouldParse() throws ParseException { 65 | given(types.findPetTypes()).willReturn(makePetTypes()); 66 | PetType petType = petTypeFormatter.parse("Bird", Locale.ENGLISH); 67 | assertThat(petType.getName()).isEqualTo("Bird"); 68 | } 69 | 70 | @Test 71 | void shouldThrowParseException() { 72 | given(types.findPetTypes()).willReturn(makePetTypes()); 73 | Assertions.assertThrows(ParseException.class, () -> { 74 | petTypeFormatter.parse("Fish", Locale.ENGLISH); 75 | }); 76 | } 77 | 78 | /** 79 | * Helper method to produce some sample pet types just for test purpose 80 | * @return {@link Collection} of {@link PetType} 81 | */ 82 | private List makePetTypes() { 83 | List petTypes = new ArrayList<>(); 84 | petTypes.add(new PetType() { 85 | { 86 | setName("Dog"); 87 | } 88 | }); 89 | petTypes.add(new PetType() { 90 | { 91 | setName("Bird"); 92 | } 93 | }); 94 | return petTypes; 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/samples/petclinic/owner/PetValidatorTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.samples.petclinic.owner; 18 | 19 | import org.junit.jupiter.api.BeforeEach; 20 | import org.junit.jupiter.api.Nested; 21 | import org.junit.jupiter.api.Test; 22 | import org.junit.jupiter.api.condition.DisabledInNativeImage; 23 | import org.junit.jupiter.api.extension.ExtendWith; 24 | import org.mockito.junit.jupiter.MockitoExtension; 25 | import org.springframework.validation.Errors; 26 | import org.springframework.validation.MapBindingResult; 27 | 28 | import java.time.LocalDate; 29 | import java.util.HashMap; 30 | 31 | import static org.junit.jupiter.api.Assertions.assertFalse; 32 | import static org.junit.jupiter.api.Assertions.assertTrue; 33 | 34 | /** 35 | * Test class for {@link PetValidator} 36 | * 37 | * @author Wick Dynex 38 | */ 39 | @ExtendWith(MockitoExtension.class) 40 | @DisabledInNativeImage 41 | public class PetValidatorTests { 42 | 43 | private PetValidator petValidator; 44 | 45 | private Pet pet; 46 | 47 | private PetType petType; 48 | 49 | private Errors errors; 50 | 51 | private static final String petName = "Buddy"; 52 | 53 | private static final String petTypeName = "Dog"; 54 | 55 | private static final LocalDate petBirthDate = LocalDate.of(1990, 1, 1); 56 | 57 | @BeforeEach 58 | void setUp() { 59 | petValidator = new PetValidator(); 60 | pet = new Pet(); 61 | petType = new PetType(); 62 | errors = new MapBindingResult(new HashMap<>(), "pet"); 63 | } 64 | 65 | @Test 66 | void testValidate() { 67 | petType.setName(petTypeName); 68 | pet.setName(petName); 69 | pet.setType(petType); 70 | pet.setBirthDate(petBirthDate); 71 | 72 | petValidator.validate(pet, errors); 73 | 74 | assertFalse(errors.hasErrors()); 75 | } 76 | 77 | @Nested 78 | class ValidateHasErrors { 79 | 80 | @Test 81 | void testValidateWithInvalidPetName() { 82 | petType.setName(petTypeName); 83 | pet.setName(""); 84 | pet.setType(petType); 85 | pet.setBirthDate(petBirthDate); 86 | 87 | petValidator.validate(pet, errors); 88 | 89 | assertTrue(errors.hasFieldErrors("name")); 90 | } 91 | 92 | @Test 93 | void testValidateWithInvalidPetType() { 94 | pet.setName(petName); 95 | pet.setType(null); 96 | pet.setBirthDate(petBirthDate); 97 | 98 | petValidator.validate(pet, errors); 99 | 100 | assertTrue(errors.hasFieldErrors("type")); 101 | } 102 | 103 | @Test 104 | void testValidateWithInvalidBirthDate() { 105 | petType.setName(petTypeName); 106 | pet.setName(petName); 107 | pet.setType(petType); 108 | pet.setBirthDate(null); 109 | 110 | petValidator.validate(pet, errors); 111 | 112 | assertTrue(errors.hasFieldErrors("birthDate")); 113 | } 114 | 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/samples/petclinic/owner/VisitControllerTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.samples.petclinic.owner; 18 | 19 | import static org.mockito.BDDMockito.given; 20 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 21 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 22 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; 23 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 24 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view; 25 | 26 | import org.junit.jupiter.api.BeforeEach; 27 | import org.junit.jupiter.api.Test; 28 | import org.junit.jupiter.api.condition.DisabledInNativeImage; 29 | import org.springframework.beans.factory.annotation.Autowired; 30 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 31 | import org.springframework.test.context.aot.DisabledInAotMode; 32 | import org.springframework.test.context.bean.override.mockito.MockitoBean; 33 | import org.springframework.test.web.servlet.MockMvc; 34 | 35 | import java.util.Optional; 36 | 37 | /** 38 | * Test class for {@link VisitController} 39 | * 40 | * @author Colin But 41 | * @author Wick Dynex 42 | */ 43 | @WebMvcTest(VisitController.class) 44 | @DisabledInNativeImage 45 | @DisabledInAotMode 46 | class VisitControllerTests { 47 | 48 | private static final int TEST_OWNER_ID = 1; 49 | 50 | private static final int TEST_PET_ID = 1; 51 | 52 | @Autowired 53 | private MockMvc mockMvc; 54 | 55 | @MockitoBean 56 | private OwnerRepository owners; 57 | 58 | @BeforeEach 59 | void init() { 60 | Owner owner = new Owner(); 61 | Pet pet = new Pet(); 62 | owner.addPet(pet); 63 | pet.setId(TEST_PET_ID); 64 | given(this.owners.findById(TEST_OWNER_ID)).willReturn(Optional.of(owner)); 65 | } 66 | 67 | @Test 68 | void testInitNewVisitForm() throws Exception { 69 | mockMvc.perform(get("/owners/{ownerId}/pets/{petId}/visits/new", TEST_OWNER_ID, TEST_PET_ID)) 70 | .andExpect(status().isOk()) 71 | .andExpect(view().name("pets/createOrUpdateVisitForm")); 72 | } 73 | 74 | @Test 75 | void testProcessNewVisitFormSuccess() throws Exception { 76 | mockMvc 77 | .perform(post("/owners/{ownerId}/pets/{petId}/visits/new", TEST_OWNER_ID, TEST_PET_ID) 78 | .param("name", "George") 79 | .param("description", "Visit Description")) 80 | .andExpect(status().is3xxRedirection()) 81 | .andExpect(view().name("redirect:/owners/{ownerId}")); 82 | } 83 | 84 | @Test 85 | void testProcessNewVisitFormHasErrors() throws Exception { 86 | mockMvc 87 | .perform(post("/owners/{ownerId}/pets/{petId}/visits/new", TEST_OWNER_ID, TEST_PET_ID).param("name", 88 | "George")) 89 | .andExpect(model().attributeHasErrors("visit")) 90 | .andExpect(status().isOk()) 91 | .andExpect(view().name("pets/createOrUpdateVisitForm")); 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/samples/petclinic/service/EntityUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.samples.petclinic.service; 18 | 19 | import org.springframework.orm.ObjectRetrievalFailureException; 20 | import org.springframework.samples.petclinic.model.BaseEntity; 21 | 22 | import java.util.Collection; 23 | 24 | /** 25 | * Utility methods for handling entities. Separate from the BaseEntity class mainly 26 | * because of dependency on the ORM-associated ObjectRetrievalFailureException. 27 | * 28 | * @author Juergen Hoeller 29 | * @author Sam Brannen 30 | * @see org.springframework.samples.petclinic.model.BaseEntity 31 | * @since 29.10.2003 32 | */ 33 | public abstract class EntityUtils { 34 | 35 | /** 36 | * Look up the entity of the given class with the given id in the given collection. 37 | * @param entities the collection to search 38 | * @param entityClass the entity class to look up 39 | * @param entityId the entity id to look up 40 | * @return the found entity 41 | * @throws ObjectRetrievalFailureException if the entity was not found 42 | */ 43 | public static T getById(Collection entities, Class entityClass, int entityId) 44 | throws ObjectRetrievalFailureException { 45 | for (T entity : entities) { 46 | if (entity.getId() == entityId && entityClass.isInstance(entity)) { 47 | return entity; 48 | } 49 | } 50 | throw new ObjectRetrievalFailureException(entityClass, entityId); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/samples/petclinic/system/CrashControllerIntegrationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.samples.petclinic.system; 18 | 19 | import static org.assertj.core.api.Assertions.assertThat; 20 | import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; 21 | 22 | import java.util.List; 23 | import java.util.Map; 24 | 25 | import org.junit.jupiter.api.Test; 26 | import org.springframework.beans.factory.annotation.Autowired; 27 | import org.springframework.beans.factory.annotation.Value; 28 | import org.springframework.boot.autoconfigure.SpringBootApplication; 29 | import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; 30 | import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; 31 | import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; 32 | import org.springframework.boot.test.context.SpringBootTest; 33 | import org.springframework.boot.test.web.client.TestRestTemplate; 34 | import org.springframework.core.ParameterizedTypeReference; 35 | import org.springframework.http.HttpEntity; 36 | import org.springframework.http.HttpHeaders; 37 | import org.springframework.http.HttpMethod; 38 | import org.springframework.http.HttpStatus; 39 | import org.springframework.http.MediaType; 40 | import org.springframework.http.RequestEntity; 41 | import org.springframework.http.ResponseEntity; 42 | 43 | /** 44 | * Integration Test for {@link CrashController}. 45 | * 46 | * @author Alex Lutz 47 | */ 48 | // NOT Waiting https://github.com/spring-projects/spring-boot/issues/5574 49 | @SpringBootTest(webEnvironment = RANDOM_PORT, 50 | properties = { "server.error.include-message=ALWAYS", "management.endpoints.enabled-by-default=false" }) 51 | class CrashControllerIntegrationTests { 52 | 53 | @Value(value = "${local.server.port}") 54 | private int port; 55 | 56 | @Autowired 57 | private TestRestTemplate rest; 58 | 59 | @Test 60 | void testTriggerExceptionJson() { 61 | ResponseEntity> resp = rest.exchange( 62 | RequestEntity.get("http://localhost:" + port + "/oups").build(), 63 | new ParameterizedTypeReference>() { 64 | }); 65 | assertThat(resp).isNotNull(); 66 | assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); 67 | assertThat(resp.getBody()).containsKey("timestamp"); 68 | assertThat(resp.getBody()).containsKey("status"); 69 | assertThat(resp.getBody()).containsKey("error"); 70 | assertThat(resp.getBody()).containsEntry("message", 71 | "Expected: controller used to showcase what happens when an exception is thrown"); 72 | assertThat(resp.getBody()).containsEntry("path", "/oups"); 73 | } 74 | 75 | @Test 76 | void testTriggerExceptionHtml() { 77 | HttpHeaders headers = new HttpHeaders(); 78 | headers.setAccept(List.of(MediaType.TEXT_HTML)); 79 | ResponseEntity resp = rest.exchange("http://localhost:" + port + "/oups", HttpMethod.GET, 80 | new HttpEntity<>(headers), String.class); 81 | assertThat(resp).isNotNull(); 82 | assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); 83 | assertThat(resp.getBody()).isNotNull(); 84 | // html: 85 | assertThat(resp.getBody()).containsSubsequence("", "

", "Something happened...", "

", "

", 86 | "Expected:", "controller", "used", "to", "showcase", "what", "happens", "when", "an", "exception", "is", 87 | "thrown", "

", ""); 88 | // Not the whitelabel error page: 89 | assertThat(resp.getBody()).doesNotContain("Whitelabel Error Page", 90 | "This application has no explicit mapping for"); 91 | } 92 | 93 | @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, 94 | DataSourceTransactionManagerAutoConfiguration.class, HibernateJpaAutoConfiguration.class }) 95 | static class TestConfiguration { 96 | 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/samples/petclinic/system/CrashControllerTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.samples.petclinic.system; 18 | 19 | import org.junit.jupiter.api.Test; 20 | 21 | import static org.assertj.core.api.Assertions.assertThatExceptionOfType; 22 | 23 | /** 24 | * Test class for {@link CrashController} 25 | * 26 | * @author Colin But 27 | * @author Alex Lutz 28 | */ 29 | // Waiting https://github.com/spring-projects/spring-boot/issues/5574 ..good 30 | // luck ((plain(st) UNIT test)! :) 31 | class CrashControllerTests { 32 | 33 | final CrashController testee = new CrashController(); 34 | 35 | @Test 36 | void testTriggerException() { 37 | assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> testee.triggerException()) 38 | .withMessageContaining("Expected: controller used to showcase what happens when an exception is thrown"); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/samples/petclinic/system/I18nPropertiesSyncTest.java: -------------------------------------------------------------------------------- 1 | package org.springframework.samples.petclinic.system; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.io.IOException; 6 | import java.nio.file.*; 7 | import java.util.HashMap; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.Properties; 11 | import java.util.Set; 12 | import java.util.TreeSet; 13 | import java.util.regex.Pattern; 14 | import java.util.stream.Stream; 15 | 16 | import static org.junit.jupiter.api.Assertions.fail; 17 | 18 | /** 19 | * This test ensures that there are no hard-coded strings without internationalization in 20 | * any HTML files. Also ensures that a string is translated in every language to avoid 21 | * partial translations. 22 | * 23 | * @author Anuj Ashok Potdar 24 | */ 25 | public class I18nPropertiesSyncTest { 26 | 27 | private static final String I18N_DIR = "src/main/resources"; 28 | 29 | private static final String BASE_NAME = "messages"; 30 | 31 | public static final String PROPERTIES = ".properties"; 32 | 33 | private static final Pattern HTML_TEXT_LITERAL = Pattern.compile(">([^<>{}]+)<"); 34 | 35 | private static final Pattern BRACKET_ONLY = Pattern.compile("<[^>]*>\\s*[\\[\\]](?: )?\\s*]*>"); 36 | 37 | private static final Pattern HAS_TH_TEXT_ATTRIBUTE = Pattern.compile("th:(u)?text\\s*=\\s*\"[^\"]+\""); 38 | 39 | @Test 40 | public void checkNonInternationalizedStrings() throws IOException { 41 | Path root = Paths.get("src/main"); 42 | List files; 43 | 44 | try (Stream stream = Files.walk(root)) { 45 | files = stream.filter(p -> p.toString().endsWith(".java") || p.toString().endsWith(".html")) 46 | .filter(p -> !p.toString().contains("/test/")) 47 | .filter(p -> !p.getFileName().toString().endsWith("Test.java")) 48 | .toList(); 49 | } 50 | 51 | StringBuilder report = new StringBuilder(); 52 | 53 | for (Path file : files) { 54 | List lines = Files.readAllLines(file); 55 | for (int i = 0; i < lines.size(); i++) { 56 | String line = lines.get(i).trim(); 57 | 58 | if (line.startsWith("//") || line.startsWith("@") || line.contains("log.") 59 | || line.contains("System.out")) 60 | continue; 61 | 62 | if (file.toString().endsWith(".html")) { 63 | boolean hasLiteralText = HTML_TEXT_LITERAL.matcher(line).find(); 64 | boolean hasThTextAttribute = HAS_TH_TEXT_ATTRIBUTE.matcher(line).find(); 65 | boolean isBracketOnly = BRACKET_ONLY.matcher(line).find(); 66 | 67 | if (hasLiteralText && !line.contains("#{") && !hasThTextAttribute && !isBracketOnly) { 68 | report.append("HTML: ") 69 | .append(file) 70 | .append(" Line ") 71 | .append(i + 1) 72 | .append(": ") 73 | .append(line) 74 | .append("\n"); 75 | } 76 | } 77 | } 78 | } 79 | 80 | if (!report.isEmpty()) { 81 | fail("Hardcoded (non-internationalized) strings found:\n" + report); 82 | } 83 | } 84 | 85 | @Test 86 | public void checkI18nPropertyFilesAreInSync() throws IOException { 87 | List propertyFiles; 88 | try (Stream stream = Files.walk(Paths.get(I18N_DIR))) { 89 | propertyFiles = stream.filter(p -> p.getFileName().toString().startsWith(BASE_NAME)) 90 | .filter(p -> p.getFileName().toString().endsWith(PROPERTIES)) 91 | .toList(); 92 | } 93 | 94 | Map localeToProps = new HashMap<>(); 95 | 96 | for (Path path : propertyFiles) { 97 | Properties props = new Properties(); 98 | try (var reader = Files.newBufferedReader(path)) { 99 | props.load(reader); 100 | localeToProps.put(path.getFileName().toString(), props); 101 | } 102 | } 103 | 104 | String baseFile = BASE_NAME + PROPERTIES; 105 | Properties baseProps = localeToProps.get(baseFile); 106 | if (baseProps == null) { 107 | fail("Base properties file '" + baseFile + "' not found."); 108 | return; 109 | } 110 | 111 | Set baseKeys = baseProps.stringPropertyNames(); 112 | StringBuilder report = new StringBuilder(); 113 | 114 | for (Map.Entry entry : localeToProps.entrySet()) { 115 | String fileName = entry.getKey(); 116 | // We use fallback logic to include english strings, hence messages_en is not 117 | // populated. 118 | if (fileName.equals(baseFile) || fileName.equals("messages_en.properties")) 119 | continue; 120 | 121 | Properties props = entry.getValue(); 122 | Set missingKeys = new TreeSet<>(baseKeys); 123 | missingKeys.removeAll(props.stringPropertyNames()); 124 | 125 | if (!missingKeys.isEmpty()) { 126 | report.append("Missing keys in ").append(fileName).append(":\n"); 127 | missingKeys.forEach(k -> report.append(" ").append(k).append("\n")); 128 | } 129 | } 130 | 131 | if (!report.isEmpty()) { 132 | fail("Translation files are not in sync:\n" + report); 133 | } 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/samples/petclinic/vet/VetControllerTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.samples.petclinic.vet; 18 | 19 | import org.assertj.core.util.Lists; 20 | import org.junit.jupiter.api.BeforeEach; 21 | import org.junit.jupiter.api.Test; 22 | import org.junit.jupiter.api.condition.DisabledInNativeImage; 23 | import org.springframework.beans.factory.annotation.Autowired; 24 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 25 | import org.springframework.data.domain.PageImpl; 26 | import org.springframework.data.domain.Pageable; 27 | import org.springframework.http.MediaType; 28 | import org.springframework.test.context.aot.DisabledInAotMode; 29 | import org.springframework.test.context.bean.override.mockito.MockitoBean; 30 | import org.springframework.test.web.servlet.MockMvc; 31 | import org.springframework.test.web.servlet.ResultActions; 32 | import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; 33 | 34 | import static org.mockito.ArgumentMatchers.any; 35 | import static org.mockito.BDDMockito.given; 36 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 37 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; 38 | 39 | /** 40 | * Test class for the {@link VetController} 41 | */ 42 | 43 | @WebMvcTest(VetController.class) 44 | @DisabledInNativeImage 45 | @DisabledInAotMode 46 | class VetControllerTests { 47 | 48 | @Autowired 49 | private MockMvc mockMvc; 50 | 51 | @MockitoBean 52 | private VetRepository vets; 53 | 54 | private Vet james() { 55 | Vet james = new Vet(); 56 | james.setFirstName("James"); 57 | james.setLastName("Carter"); 58 | james.setId(1); 59 | return james; 60 | } 61 | 62 | private Vet helen() { 63 | Vet helen = new Vet(); 64 | helen.setFirstName("Helen"); 65 | helen.setLastName("Leary"); 66 | helen.setId(2); 67 | Specialty radiology = new Specialty(); 68 | radiology.setId(1); 69 | radiology.setName("radiology"); 70 | helen.addSpecialty(radiology); 71 | return helen; 72 | } 73 | 74 | @BeforeEach 75 | void setup() { 76 | given(this.vets.findAll()).willReturn(Lists.newArrayList(james(), helen())); 77 | given(this.vets.findAll(any(Pageable.class))) 78 | .willReturn(new PageImpl(Lists.newArrayList(james(), helen()))); 79 | 80 | } 81 | 82 | @Test 83 | void testShowVetListHtml() throws Exception { 84 | 85 | mockMvc.perform(MockMvcRequestBuilders.get("/vets.html?page=1")) 86 | .andExpect(status().isOk()) 87 | .andExpect(model().attributeExists("listVets")) 88 | .andExpect(view().name("vets/vetList")); 89 | 90 | } 91 | 92 | @Test 93 | void testShowResourcesVetList() throws Exception { 94 | ResultActions actions = mockMvc.perform(get("/vets").accept(MediaType.APPLICATION_JSON)) 95 | .andExpect(status().isOk()); 96 | actions.andExpect(content().contentType(MediaType.APPLICATION_JSON)) 97 | .andExpect(jsonPath("$.vetList[0].id").value(1)); 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/samples/petclinic/vet/VetTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.samples.petclinic.vet; 17 | 18 | import org.junit.jupiter.api.Test; 19 | import org.springframework.util.SerializationUtils; 20 | 21 | import static org.assertj.core.api.Assertions.assertThat; 22 | 23 | /** 24 | * @author Dave Syer 25 | */ 26 | class VetTests { 27 | 28 | @Test 29 | void testSerialization() { 30 | Vet vet = new Vet(); 31 | vet.setFirstName("Zaphod"); 32 | vet.setLastName("Beeblebrox"); 33 | vet.setId(123); 34 | @SuppressWarnings("deprecation") 35 | Vet other = (Vet) SerializationUtils.deserialize(SerializationUtils.serialize(vet)); 36 | assertThat(other.getFirstName()).isEqualTo(vet.getFirstName()); 37 | assertThat(other.getLastName()).isEqualTo(vet.getLastName()); 38 | assertThat(other.getId()).isEqualTo(vet.getId()); 39 | } 40 | 41 | } 42 | --------------------------------------------------------------------------------