FIX CONFLICT I18N

This commit is contained in:
AewInformatica 2021-01-24 23:02:11 -03:00
commit f0eb91826f
61 changed files with 1463 additions and 1329 deletions

View file

@ -10,3 +10,12 @@ indent_style = space
[*.{java,xml}] [*.{java,xml}]
indent_size = 4 indent_size = 4
trim_trailing_whitespace = true trim_trailing_whitespace = true
indent_style = tab
tab_width = 4
[{pom,wro}.xml]
indent_size = 2
indent_style = space
[*.{html,sql,less}]
indent_size = 2

6
.gitignore vendored
View file

@ -8,9 +8,5 @@ target/*
*.iml *.iml
/target /target
.sts4-cache/ .sts4-cache/
.vscode/* .vscode
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
_site/ _site/

View file

@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0'
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -20,7 +20,7 @@ import java.util.Properties;
public class MavenWrapperDownloader { public class MavenWrapperDownloader {
private static final String WRAPPER_VERSION = "0.5.5"; private static final String WRAPPER_VERSION = "0.5.6";
/** /**
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
*/ */

Binary file not shown.

View file

@ -1,2 +1,3 @@
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.2/apache-maven-3.6.2-bin.zip distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar

26
.vscode/launch.json vendored
View file

@ -1,26 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "java",
"name": "Debug (Launch)-PetClinicApplication<spring-petclinic>",
"request": "launch",
"cwd": "${workspaceFolder}",
"console": "internalConsole",
"stopOnEntry": false,
"mainClass": "org.springframework.samples.petclinic.PetClinicApplication",
"projectName": "spring-petclinic",
"args": ""
},
{
"type": "java",
"name": "Debug (Attach)",
"request": "attach",
"hostName": "localhost",
"port": 0
}
]
}

View file

@ -1,3 +0,0 @@
{
"java.configuration.updateBuildConfiguration": "interactive"
}

19
.vscode/tasks.json vendored
View file

@ -1,19 +0,0 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "verify",
"type": "shell",
"command": "mvn -B verify",
"group": "build"
},
{
"label": "test",
"type": "shell",
"command": "mvn -B test",
"group": "test"
}
]
}

View file

@ -3,7 +3,10 @@ mysql:
ports: ports:
- "3306:3306" - "3306:3306"
environment: environment:
- MYSQL_ROOT_PASSWORD=petclinic - MYSQL_ROOT_PASSWORD=
- MYSQL_ALLOW_EMPTY_PASSWORD=true
- MYSQL_USER=petclinic
- MYSQL_PASSWORD=petclinic
- MYSQL_DATABASE=petclinic - MYSQL_DATABASE=petclinic
volumes: volumes:
- "./conf.d:/etc/mysql/conf.d:ro" - "./conf.d:/etc/mysql/conf.d:ro"

8
mvnw vendored
View file

@ -8,7 +8,7 @@
# "License"); you may not use this file except in compliance # "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at # with the License. You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # https://www.apache.org/licenses/LICENSE-2.0'
# #
# Unless required by applicable law or agreed to in writing, # Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an # software distributed under the License is distributed on an
@ -19,7 +19,7 @@
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
# Maven2 Start Up Batch script # Maven Start Up Batch script
# #
# Required ENV vars: # Required ENV vars:
# ------------------ # ------------------
@ -212,9 +212,9 @@ else
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi fi
if [ -n "$MVNW_REPOURL" ]; then if [ -n "$MVNW_REPOURL" ]; then
jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
else else
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
fi fi
while IFS="=" read key value; do while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;; case "$key" in (wrapperUrl) jarUrl="$value"; break ;;

8
mvnw.cmd vendored
View file

@ -7,7 +7,7 @@
@REM "License"); you may not use this file except in compliance @REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at @REM with the License. You may obtain a copy of the License at
@REM @REM
@REM http://www.apache.org/licenses/LICENSE-2.0 @REM https://www.apache.org/licenses/LICENSE-2.0'
@REM @REM
@REM Unless required by applicable law or agreed to in writing, @REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an @REM software distributed under the License is distributed on an
@ -18,7 +18,7 @@
@REM ---------------------------------------------------------------------------- @REM ----------------------------------------------------------------------------
@REM ---------------------------------------------------------------------------- @REM ----------------------------------------------------------------------------
@REM Maven2 Start Up Batch script @REM Maven Start Up Batch script
@REM @REM
@REM Required ENV vars: @REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir @REM JAVA_HOME - location of a JDK home dir
@ -120,7 +120,7 @@ SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
@ -134,7 +134,7 @@ if exist %WRAPPER_JAR% (
) )
) else ( ) else (
if not "%MVNW_REPOURL%" == "" ( if not "%MVNW_REPOURL%" == "" (
SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
) )
if "%MVNW_VERBOSE%" == "true" ( if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ... echo Couldn't find %WRAPPER_JAR%, downloading it ...

135
pom.xml
View file

@ -1,16 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.samples</groupId> <groupId>org.springframework.samples</groupId>
<artifactId>spring-petclinic</artifactId> <artifactId>spring-petclinic</artifactId>
<version>2.2.0.BUILD-SNAPSHOT</version> <version>2.4.0.BUILD-SNAPSHOT</version>
<parent> <parent>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId> <artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version> <version>2.4.1</version>
</parent> </parent>
<name>petclinic</name> <name>petclinic</name>
@ -28,7 +28,8 @@
<wro4j.version>1.8.0</wro4j.version> <wro4j.version>1.8.0</wro4j.version>
<jacoco.version>0.8.5</jacoco.version> <jacoco.version>0.8.5</jacoco.version>
<nohttp-checkstyle.version>0.0.4.RELEASE</nohttp-checkstyle.version>
<spring-format.version>0.0.25</spring-format.version>
</properties> </properties>
<dependencies> <dependencies>
@ -49,6 +50,10 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-web</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId> <artifactId>spring-boot-starter-thymeleaf</artifactId>
@ -65,10 +70,10 @@
</exclusions> </exclusions>
</dependency> </dependency>
<!-- Databases - Uses HSQL by default --> <!-- Databases - Uses H2 by default -->
<dependency> <dependency>
<groupId>org.hsqldb</groupId> <groupId>com.h2database</groupId>
<artifactId>hsqldb</artifactId> <artifactId>h2</artifactId>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
<dependency> <dependency>
@ -109,18 +114,6 @@
</dependency> </dependency>
<!-- end of webjars --> <!-- end of webjars -->
<!-- Testing -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId> <artifactId>spring-boot-devtools</artifactId>
@ -130,6 +123,53 @@
<build> <build>
<plugins> <plugins>
<plugin>
<groupId>io.spring.javaformat</groupId>
<artifactId>spring-javaformat-maven-plugin</artifactId>
<version>${spring-format.version}</version>
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>validate</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.1.1</version>
<dependencies>
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>8.32</version>
</dependency>
<dependency>
<groupId>io.spring.nohttp</groupId>
<artifactId>nohttp-checkstyle</artifactId>
<version>${nohttp-checkstyle.version}</version>
</dependency>
</dependencies>
<executions>
<execution>
<id>nohttp-checkstyle-validation</id>
<phase>validate</phase>
<configuration>
<configLocation>src/checkstyle/nohttp-checkstyle.xml</configLocation>
<suppressionsLocation>src/checkstyle/nohttp-checkstyle-suppressions.xml</suppressionsLocation>
<encoding>UTF-8</encoding>
<sourceDirectories>${basedir}</sourceDirectories>
<includes>**/*</includes>
<excludes>**/.git/**/*,**/.idea/**/*,**/target/**/,**/.flattened-pom.xml,**/*.class</excludes>
</configuration>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin> <plugin>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId> <artifactId>spring-boot-maven-plugin</artifactId>
@ -228,7 +268,6 @@
</plugins> </plugins>
</build> </build>
<!-- Apache 2 license -->
<licenses> <licenses>
<license> <license>
<name>Apache License, Version 2.0</name> <name>Apache License, Version 2.0</name>
@ -274,4 +313,60 @@
</pluginRepository> </pluginRepository>
</pluginRepositories> </pluginRepositories>
<profiles>
<profile>
<id>m2e</id>
<activation>
<property>
<name>m2e.version</name>
</property>
</activation>
<build>
<pluginManagement>
<plugins>
<!-- This plugin's configuration is used to store Eclipse m2e settings
only. It has no influence on the Maven build itself. -->
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<versionRange>[1,)</versionRange>
<goals>
<goal>check</goal>
</goals>
</pluginExecutionFilter>
<action>
<ignore/>
</action>
</pluginExecution>
<pluginExecution>
<pluginExecutionFilter>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<versionRange>[1,)</versionRange>
<goals>
<goal>build-info</goal>
</goals>
</pluginExecutionFilter>
<action>
<ignore/>
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</profile>
</profiles>
</project> </project>

View file

@ -1 +0,0 @@
repo: https://github.com/spring-projects/spring-petclinic.git

View file

@ -1,9 +1,4 @@
# Spring PetClinic Sample Application [![Build Status](https://travis-ci.org/spring-projects/spring-petclinic.png?branch=master)](https://travis-ci.org/spring-projects/spring-petclinic/) # Spring PetClinic Sample Application [![Build Status](https://travis-ci.org/spring-projects/spring-petclinic.png?branch=main)](https://travis-ci.org/spring-projects/spring-petclinic/)
Deploy this sample application to Pivotal Web Services:
<a href="https://push-to.cfapps.io?repo=https%3A%2F%2Fgithub.com%2Fspring-projects%2Fspring-petclinic.git">
<img src="https://push-to.cfapps.io/ui/assets/images/Push-to-Pivotal-Light-with-Shadow.svg" width="180" alt="Push" align="center">
</a>
## Understanding the Spring Petclinic application with a few diagrams ## Understanding the Spring Petclinic application with a few diagrams
<a href="https://speakerdeck.com/michaelisvy/spring-petclinic-sample-application">See the presentation here</a> <a href="https://speakerdeck.com/michaelisvy/spring-petclinic-sample-application">See the presentation here</a>
@ -35,17 +30,19 @@ Our issue tracker is available here: https://github.com/spring-projects/spring-p
## Database configuration ## Database configuration
In its default configuration, Petclinic uses an in-memory database (HSQLDB) which In its default configuration, Petclinic uses an in-memory database (H2) which
gets populated at startup with data. A similar setup is provided for MySql in case a persistent database configuration is needed. gets populated at startup with data. The h2 console is automatically exposed at `http://localhost:8080/h2-console`
Note that whenever the database type is changed, the app needs to be run with a different profile: `spring.profiles.active=mysql` for MySql. and it is possible to inspect the content of the database using the `jdbc:h2:mem:testdb` url.
A similar setup is provided for MySql in case a persistent database configuration is needed. Note that whenever the database type is changed, the app needs to be run with a different profile: `spring.profiles.active=mysql` for MySql.
You could start MySql locally with whatever installer works for your OS, or with docker: You could start MySql locally with whatever installer works for your OS, or with docker:
``` ```
docker run -e MYSQL_ROOT_PASSWORD=petclinic -e MYSQL_DATABASE=petclinic -p 3306:3306 mysql:5.7.8 docker run -e MYSQL_USER=petclinic -e MYSQL_PASSWORD=petclinic -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=petclinic -p 3306:3306 mysql:5.7.8
``` ```
Further documentation is provided [here](https://github.com/spring-projects/spring-petclinic/blob/master/src/main/resources/db/mysql/petclinic_db_setup_mysql.txt). Further documentation is provided [here](https://github.com/spring-projects/spring-petclinic/blob/main/src/main/resources/db/mysql/petclinic_db_setup_mysql.txt).
## Working with Petclinic in your IDE ## Working with Petclinic in your IDE
@ -74,15 +71,11 @@ File -> Import -> Maven -> Existing Maven project
Then either build on the command line `./mvnw generate-resources` or using the Eclipse launcher (right click on project and `Run As -> Maven install`) to generate the css. Run the application main method by right clicking on it and choosing `Run As -> Java Application`. Then either build on the command line `./mvnw generate-resources` or using the Eclipse launcher (right click on project and `Run As -> Maven install`) to generate the css. Run the application main method by right clicking on it and choosing `Run As -> Java Application`.
3) Inside IntelliJ IDEA 3) Inside IntelliJ IDEA
In the main menu, choose `File -> Open` and select the Petclinic [pom.xml](pom.xml). Click on the `Open` button. In the main menu, choose `File -> Open` and select the Petclinic [pom.xml](pom.xml). Click on the `Open` button.
CSS files are generated from the Maven build. You can either build them on the command line `./mvnw generate-resources` CSS files are generated from the Maven build. You can either build them on the command line `./mvnw generate-resources` or right click on the `spring-petclinic` project then `Maven -> Generates sources and Update Folders`.
or right click on the `spring-petclinic` project then `Maven -> Generates sources and Update Folders`.
A run configuration named `PetClinicApplication` should have been created for you if you're using a recent Ultimate A run configuration named `PetClinicApplication` should have been created for you if you're using a recent Ultimate version. Otherwise, run the application by right clicking on the `PetClinicApplication` main class and choosing `Run 'PetClinicApplication'`.
version. Otherwise, run the application by right clicking on the `PetClinicApplication` main class and choosing
`Run 'PetClinicApplication'`.
4) Navigate to Petclinic 4) Navigate to Petclinic
@ -93,13 +86,13 @@ Visit [http://localhost:8080](http://localhost:8080) in your browser.
|Spring Boot Configuration | Class or Java property files | |Spring Boot Configuration | Class or Java property files |
|--------------------------|---| |--------------------------|---|
|The Main Class | [PetClinicApplication](https://github.com/spring-projects/spring-petclinic/blob/master/src/main/java/org/springframework/samples/petclinic/PetClinicApplication.java) | |The Main Class | [PetClinicApplication](https://github.com/spring-projects/spring-petclinic/blob/main/src/main/java/org/springframework/samples/petclinic/PetClinicApplication.java) |
|Properties Files | [application.properties](https://github.com/spring-projects/spring-petclinic/blob/master/src/main/resources) | |Properties Files | [application.properties](https://github.com/spring-projects/spring-petclinic/blob/main/src/main/resources) |
|Caching | [CacheConfiguration](https://github.com/spring-projects/spring-petclinic/blob/master/src/main/java/org/springframework/samples/petclinic/system/CacheConfiguration.java) | |Caching | [CacheConfiguration](https://github.com/spring-projects/spring-petclinic/blob/main/src/main/java/org/springframework/samples/petclinic/system/CacheConfiguration.java) |
## Interesting Spring Petclinic branches and forks ## Interesting Spring Petclinic branches and forks
The Spring Petclinic master branch in the main [spring-projects](https://github.com/spring-projects/spring-petclinic) The Spring Petclinic "main" branch in the [spring-projects](https://github.com/spring-projects/spring-petclinic)
GitHub org is the "canonical" implementation, currently based on Spring Boot and Thymeleaf. There are GitHub org is the "canonical" implementation, currently based on Spring Boot and Thymeleaf. There are
[quite a few forks](https://spring-petclinic.github.io/docs/forks.html) in a special GitHub org [quite a few forks](https://spring-petclinic.github.io/docs/forks.html) in a special GitHub org
[spring-petclinic](https://github.com/spring-petclinic). If you have a special interest in a different technology stack [spring-petclinic](https://github.com/spring-petclinic). If you have a special interest in a different technology stack

View file

@ -0,0 +1,8 @@
<?xml version="1.0"?>
<!DOCTYPE suppressions PUBLIC
"-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN"
"https://checkstyle.org/dtds/suppressions_1_2.dtd">
<suppressions>
<suppress files="[\\/]build.log" checks="NoHttp"/>
<suppress files=".+\.(jar|git|ico|p12|gif|jks|jpg|svg)" checks="NoHttp"/>
</suppressions>

View file

@ -0,0 +1,7 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Puppy Crawl//DTD Check Configuration 1.2//EN"
"https://checkstyle.org/dtds/configuration_1_2.dtd">
<module name="com.puppycrawl.tools.checkstyle.Checker">
<module name="io.spring.nohttp.checkstyle.check.NoHttpCheck"/>
</module>

View file

@ -31,6 +31,7 @@ import javax.persistence.MappedSuperclass;
*/ */
@MappedSuperclass @MappedSuperclass
public class BaseEntity implements Serializable { public class BaseEntity implements Serializable {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id; private Integer id;

View file

@ -18,10 +18,9 @@ package org.springframework.samples.petclinic.model;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.MappedSuperclass; import javax.persistence.MappedSuperclass;
/** /**
* Simple JavaBean domain object adds a name property to <code>BaseEntity</code>. Used as a base class for objects * Simple JavaBean domain object adds a name property to <code>BaseEntity</code>. Used as
* needing these properties. * a base class for objects needing these properties.
* *
* @author Ken Krebs * @author Ken Krebs
* @author Juergen Hoeller * @author Juergen Hoeller

View file

@ -18,4 +18,3 @@
* The classes in this package represent utilities used by the domain. * The classes in this package represent utilities used by the domain.
*/ */
package org.springframework.samples.petclinic.model; package org.springframework.samples.petclinic.model;

View file

@ -45,6 +45,7 @@ import org.springframework.samples.petclinic.model.Person;
@Entity @Entity
@Table(name = "owners") @Table(name = "owners")
public class Owner extends Person { public class Owner extends Person {
@Column(name = "address") @Column(name = "address")
@NotEmpty @NotEmpty
private String address; private String address;
@ -98,8 +99,7 @@ public class Owner extends Person {
public List<Pet> getPets() { public List<Pet> getPets() {
List<Pet> sortedPets = new ArrayList<>(getPetsInternal()); List<Pet> sortedPets = new ArrayList<>(getPetsInternal());
PropertyComparator.sort(sortedPets, PropertyComparator.sort(sortedPets, new MutableSortDefinition("name", true, true));
new MutableSortDefinition("name", true, true));
return Collections.unmodifiableList(sortedPets); return Collections.unmodifiableList(sortedPets);
} }
@ -112,7 +112,6 @@ public class Owner extends Person {
/** /**
* Return the Pet with the given name, or null if none found for this Owner. * Return the Pet with the given name, or null if none found for this Owner.
*
* @param name to test * @param name to test
* @return true if pet name is already in use * @return true if pet name is already in use
*/ */
@ -122,7 +121,6 @@ public class Owner extends Person {
/** /**
* Return the Pet with the given name, or null if none found for this Owner. * Return the Pet with the given name, or null if none found for this Owner.
*
* @param name to test * @param name to test
* @return true if pet name is already in use * @return true if pet name is already in use
*/ */
@ -144,9 +142,9 @@ public class Owner extends Person {
public String toString() { public String toString() {
return new ToStringCreator(this) return new ToStringCreator(this)
.append("id", this.getId()).append("new", this.isNew()) .append("id", this.getId()).append("new", this.isNew()).append("lastName", this.getLastName())
.append("lastName", this.getLastName()) .append("firstName", this.getFirstName()).append("address", this.address).append("city", this.city)
.append("firstName", this.getFirstName()).append("address", this.address) .append("telephone", this.telephone).toString();
.append("city", this.city).append("telephone", this.telephone).toString();
} }
} }

View file

@ -40,9 +40,10 @@ import java.util.Map;
class OwnerController { class OwnerController {
private static final String VIEWS_OWNER_CREATE_OR_UPDATE_FORM = "owners/createOrUpdateOwnerForm"; private static final String VIEWS_OWNER_CREATE_OR_UPDATE_FORM = "owners/createOrUpdateOwnerForm";
private final OwnerRepository owners;
private VisitRepository visits;
private final OwnerRepository owners;
private VisitRepository visits;
public OwnerController(OwnerRepository clinicService, VisitRepository visits) { public OwnerController(OwnerRepository clinicService, VisitRepository visits) {
this.owners = clinicService; this.owners = clinicService;
@ -65,7 +66,8 @@ class OwnerController {
public String processCreationForm(@Valid Owner owner, BindingResult result) { public String processCreationForm(@Valid Owner owner, BindingResult result) {
if (result.hasErrors()) { if (result.hasErrors()) {
return VIEWS_OWNER_CREATE_OR_UPDATE_FORM; return VIEWS_OWNER_CREATE_OR_UPDATE_FORM;
} else { }
else {
this.owners.save(owner); this.owners.save(owner);
return "redirect:/owners/" + owner.getId(); return "redirect:/owners/" + owner.getId();
} }
@ -91,11 +93,13 @@ class OwnerController {
// no owners found // no owners found
result.rejectValue("lastName", "notFound", "not found"); result.rejectValue("lastName", "notFound", "not found");
return "owners/findOwners"; return "owners/findOwners";
} else if (results.size() == 1) { }
else if (results.size() == 1) {
// 1 owner found // 1 owner found
owner = results.iterator().next(); owner = results.iterator().next();
return "redirect:/owners/" + owner.getId(); return "redirect:/owners/" + owner.getId();
} else { }
else {
// multiple owners found // multiple owners found
model.put("selections", results); model.put("selections", results);
return "owners/ownersList"; return "owners/ownersList";
@ -110,10 +114,12 @@ class OwnerController {
} }
@PostMapping("/owners/{ownerId}/edit") @PostMapping("/owners/{ownerId}/edit")
public String processUpdateOwnerForm(@Valid Owner owner, BindingResult result, @PathVariable("ownerId") int ownerId) { public String processUpdateOwnerForm(@Valid Owner owner, BindingResult result,
@PathVariable("ownerId") int ownerId) {
if (result.hasErrors()) { if (result.hasErrors()) {
return VIEWS_OWNER_CREATE_OR_UPDATE_FORM; return VIEWS_OWNER_CREATE_OR_UPDATE_FORM;
} else { }
else {
owner.setId(ownerId); owner.setId(ownerId);
this.owners.save(owner); this.owners.save(owner);
return "redirect:/owners/{ownerId}"; return "redirect:/owners/{ownerId}";
@ -122,7 +128,6 @@ class OwnerController {
/** /**
* Custom handler for displaying an owner. * Custom handler for displaying an owner.
*
* @param ownerId the ID of the owner to display * @param ownerId the ID of the owner to display
* @return a ModelMap with the model attributes for the view * @return a ModelMap with the model attributes for the view
*/ */

View file

@ -23,9 +23,10 @@ import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
/** /**
* Repository class for <code>Owner</code> domain objects All method names are compliant with Spring Data naming * Repository class for <code>Owner</code> domain objects All method names are compliant
* conventions so this interface can easily be extended for Spring Data. * with Spring Data naming conventions so this interface can easily be extended for Spring
* See: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-creation * Data. See:
* https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-creation
* *
* @author Ken Krebs * @author Ken Krebs
* @author Juergen Hoeller * @author Juergen Hoeller
@ -60,5 +61,4 @@ public interface OwnerRepository extends Repository<Owner, Integer> {
*/ */
void save(Owner owner); void save(Owner owner);
} }

View file

@ -100,8 +100,7 @@ public class Pet extends NamedEntity {
public List<Visit> getVisits() { public List<Visit> getVisits() {
List<Visit> sortedVisits = new ArrayList<>(getVisitsInternal()); List<Visit> sortedVisits = new ArrayList<>(getVisitsInternal());
PropertyComparator.sort(sortedVisits, PropertyComparator.sort(sortedVisits, new MutableSortDefinition("date", false, false));
new MutableSortDefinition("date", false, false));
return Collections.unmodifiableList(sortedVisits); return Collections.unmodifiableList(sortedVisits);
} }

View file

@ -35,7 +35,9 @@ import java.util.Collection;
class PetController { class PetController {
private static final String VIEWS_PETS_CREATE_OR_UPDATE_FORM = "pets/createOrUpdatePetForm"; private static final String VIEWS_PETS_CREATE_OR_UPDATE_FORM = "pets/createOrUpdatePetForm";
private final PetRepository pets; private final PetRepository pets;
private final OwnerRepository owners; private final OwnerRepository owners;
public PetController(PetRepository pets, OwnerRepository owners) { public PetController(PetRepository pets, OwnerRepository owners) {
@ -80,7 +82,8 @@ class PetController {
if (result.hasErrors()) { if (result.hasErrors()) {
model.put("pet", pet); model.put("pet", pet);
return VIEWS_PETS_CREATE_OR_UPDATE_FORM; return VIEWS_PETS_CREATE_OR_UPDATE_FORM;
} else { }
else {
this.pets.save(pet); this.pets.save(pet);
return "redirect:/owners/{ownerId}"; return "redirect:/owners/{ownerId}";
} }
@ -99,7 +102,8 @@ class PetController {
pet.setOwner(owner); pet.setOwner(owner);
model.put("pet", pet); model.put("pet", pet);
return VIEWS_PETS_CREATE_OR_UPDATE_FORM; return VIEWS_PETS_CREATE_OR_UPDATE_FORM;
} else { }
else {
owner.addPet(pet); owner.addPet(pet);
this.pets.save(pet); this.pets.save(pet);
return "redirect:/owners/{ownerId}"; return "redirect:/owners/{ownerId}";

View file

@ -22,9 +22,10 @@ import org.springframework.data.repository.Repository;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
/** /**
* Repository class for <code>Pet</code> domain objects All method names are compliant with Spring Data naming * Repository class for <code>Pet</code> domain objects All method names are compliant
* conventions so this interface can easily be extended for Spring Data. * with Spring Data naming conventions so this interface can easily be extended for Spring
* See: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-creation * Data. See:
* https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-creation
* *
* @author Ken Krebs * @author Ken Krebs
* @author Juergen Hoeller * @author Juergen Hoeller
@ -56,4 +57,3 @@ public interface PetRepository extends Repository<Pet, Integer> {
void save(Pet pet); void save(Pet pet);
} }

View file

@ -21,8 +21,7 @@ import javax.persistence.Table;
import org.springframework.samples.petclinic.model.NamedEntity; import org.springframework.samples.petclinic.model.NamedEntity;
/** /**
* @author Juergen Hoeller * @author Juergen Hoeller Can be Cat, Dog, Hamster...
* Can be Cat, Dog, Hamster...
*/ */
@Entity @Entity
@Table(name = "types") @Table(name = "types")

View file

@ -24,9 +24,10 @@ import org.springframework.format.Formatter;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
* Instructs Spring MVC on how to parse and print elements of type 'PetType'. Starting from Spring 3.0, Formatters have * Instructs Spring MVC on how to parse and print elements of type 'PetType'. Starting
* come as an improvement in comparison to legacy PropertyEditors. See the following links for more details: - The * from Spring 3.0, Formatters have come as an improvement in comparison to legacy
* Spring ref doc: https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#format * PropertyEditors. See the following links for more details: - The Spring ref doc:
* https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#format
* *
* @author Mark Fisher * @author Mark Fisher
* @author Juergen Hoeller * @author Juergen Hoeller
@ -37,7 +38,6 @@ public class PetTypeFormatter implements Formatter<PetType> {
private final PetRepository pets; private final PetRepository pets;
@Autowired @Autowired
public PetTypeFormatter(PetRepository pets) { public PetTypeFormatter(PetRepository pets) {
this.pets = pets; this.pets = pets;

View file

@ -22,7 +22,8 @@ import org.springframework.validation.Validator;
/** /**
* <code>Validator</code> for <code>Pet</code> forms. * <code>Validator</code> for <code>Pet</code> forms.
* <p> * <p>
* We're not using Bean Validation annotations here because it is easier to define such validation rule in Java. * We're not using Bean Validation annotations here because it is easier to define such
* validation rule in Java.
* </p> * </p>
* *
* @author Ken Krebs * @author Ken Krebs
@ -60,5 +61,4 @@ public class PetValidator implements Validator {
return Pet.class.isAssignableFrom(clazz); return Pet.class.isAssignableFrom(clazz);
} }
} }

View file

@ -41,8 +41,8 @@ import org.springframework.web.bind.annotation.PostMapping;
class VisitController { class VisitController {
private final VisitRepository visits; private final VisitRepository visits;
private final PetRepository pets;
private final PetRepository pets;
public VisitController(VisitRepository visits, PetRepository pets) { public VisitController(VisitRepository visits, PetRepository pets) {
this.visits = visits; this.visits = visits;
@ -55,12 +55,9 @@ class VisitController {
} }
/** /**
* Called before each and every @RequestMapping annotated method. * Called before each and every @RequestMapping annotated method. 2 goals: - Make sure
* 2 goals: * we always have fresh data - Since we do not use the session scope, make sure that
* - Make sure we always have fresh data * Pet object always has an id (Even though id is not part of the form fields)
* - Since we do not use the session scope, make sure that Pet object always has an id
* (Even though id is not part of the form fields)
*
* @param petId * @param petId
* @return Pet * @return Pet
*/ */
@ -85,7 +82,8 @@ class VisitController {
public String processNewVisitForm(@Valid Visit visit, BindingResult result) { public String processNewVisitForm(@Valid Visit visit, BindingResult result) {
if (result.hasErrors()) { if (result.hasErrors()) {
return "pets/createOrUpdateVisitForm"; return "pets/createOrUpdateVisitForm";
} else { }
else {
this.visits.save(visit); this.visits.save(visit);
return "redirect:/owners/{ownerId}"; return "redirect:/owners/{ownerId}";
} }

View file

@ -30,8 +30,8 @@ class CrashController {
@GetMapping("/oups") @GetMapping("/oups")
public String triggerException() { public String triggerException() {
throw new RuntimeException("Expected: controller used to showcase what " throw new RuntimeException(
+ "happens when an exception is thrown"); "Expected: controller used to showcase what " + "happens when an exception is thrown");
} }
} }

View file

@ -16,7 +16,6 @@
package org.springframework.samples.petclinic.system; package org.springframework.samples.petclinic.system;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
@ -27,4 +26,5 @@ class WelcomeController {
public String welcome() { public String welcome() {
return "welcome"; return "welcome";
} }
} }

View file

@ -46,7 +46,8 @@ import org.springframework.samples.petclinic.model.Person;
public class Vet extends Person { public class Vet extends Person {
@ManyToMany(fetch = FetchType.EAGER) @ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "vet_specialties", joinColumns = @JoinColumn(name = "vet_id"), inverseJoinColumns = @JoinColumn(name = "specialty_id")) @JoinTable(name = "vet_specialties", joinColumns = @JoinColumn(name = "vet_id"),
inverseJoinColumns = @JoinColumn(name = "specialty_id"))
private Set<Specialty> specialties; private Set<Specialty> specialties;
protected Set<Specialty> getSpecialtiesInternal() { protected Set<Specialty> getSpecialtiesInternal() {
@ -63,8 +64,7 @@ public class Vet extends Person {
@XmlElement @XmlElement
public List<Specialty> getSpecialties() { public List<Specialty> getSpecialties() {
List<Specialty> sortedSpecs = new ArrayList<>(getSpecialtiesInternal()); List<Specialty> sortedSpecs = new ArrayList<>(getSpecialtiesInternal());
PropertyComparator.sort(sortedSpecs, PropertyComparator.sort(sortedSpecs, new MutableSortDefinition("name", true, true));
new MutableSortDefinition("name", true, true));
return Collections.unmodifiableList(sortedSpecs); return Collections.unmodifiableList(sortedSpecs);
} }

View file

@ -23,9 +23,10 @@ import org.springframework.data.repository.Repository;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
/** /**
* Repository class for <code>Vet</code> domain objects All method names are compliant with Spring Data naming * Repository class for <code>Vet</code> domain objects All method names are compliant
* conventions so this interface can easily be extended for Spring Data. * with Spring Data naming conventions so this interface can easily be extended for Spring
* See: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-creation * Data. See:
* https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-creation
* *
* @author Ken Krebs * @author Ken Krebs
* @author Juergen Hoeller * @author Juergen Hoeller
@ -36,12 +37,10 @@ public interface VetRepository extends Repository<Vet, Integer> {
/** /**
* Retrieve all <code>Vet</code>s from the data store. * Retrieve all <code>Vet</code>s from the data store.
*
* @return a <code>Collection</code> of <code>Vet</code>s * @return a <code>Collection</code> of <code>Vet</code>s
*/ */
@Transactional(readOnly = true) @Transactional(readOnly = true)
@Cacheable("vets") @Cacheable("vets")
Collection<Vet> findAll() throws DataAccessException; Collection<Vet> findAll() throws DataAccessException;
} }

View file

@ -22,8 +22,8 @@ import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlRootElement;
/** /**
* Simple domain object representing a list of veterinarians. Mostly here to be used for the 'vets' {@link * Simple domain object representing a list of veterinarians. Mostly here to be used for
* org.springframework.web.servlet.view.xml.MarshallingView}. * the 'vets' {@link org.springframework.web.servlet.view.xml.MarshallingView}.
* *
* @author Arjen Poutsma * @author Arjen Poutsma
*/ */

View file

@ -22,9 +22,10 @@ import org.springframework.data.repository.Repository;
import org.springframework.samples.petclinic.model.BaseEntity; import org.springframework.samples.petclinic.model.BaseEntity;
/** /**
* Repository class for <code>Visit</code> domain objects All method names are compliant with Spring Data naming * Repository class for <code>Visit</code> domain objects All method names are compliant
* conventions so this interface can easily be extended for Spring Data. * with Spring Data naming conventions so this interface can easily be extended for Spring
* See: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-creation * Data. See:
* https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-creation
* *
* @author Ken Krebs * @author Ken Krebs
* @author Juergen Hoeller * @author Juergen Hoeller
@ -35,7 +36,6 @@ public interface VisitRepository extends Repository<Visit, Integer> {
/** /**
* Save a <code>Visit</code> to the data store, either inserting or updating it. * Save a <code>Visit</code> to the data store, either inserting or updating it.
*
* @param visit the <code>Visit</code> to save * @param visit the <code>Visit</code> to save
* @see BaseEntity#isNew * @see BaseEntity#isNew
*/ */

View file

@ -1,7 +1,7 @@
# database init, supports mysql too # database init, supports mysql too
database=mysql database=mysql
spring.datasource.url=${MYSQL_DB:jdbc:mysql://localhost/petclinic} spring.datasource.url=${MYSQL_URL:jdbc:mysql://localhost/petclinic}
spring.datasource.username=${MYSQL_USER:petclinic@petclinicdb2} spring.datasource.username=${MYSQL_USER:petclinic}
spring.datasource.password=${MYSQL_PASS:petclinic} spring.datasource.password=${MYSQL_PASS:petclinic}
# Uncomment this the first time the app runs # SQL is written to be idempotent so this is safe
spring.datasource.initialization-mode=always spring.datasource.initialization-mode=always

View file

@ -1,5 +1,5 @@
# database init, supports mysql too # database init, supports mysql too
database=hsqldb database=h2
spring.datasource.schema=classpath*:db/${database}/schema.sql spring.datasource.schema=classpath*:db/${database}/schema.sql
spring.datasource.data=classpath*:db/${database}/data.sql spring.datasource.data=classpath*:db/${database}/data.sql
@ -8,12 +8,12 @@ spring.thymeleaf.mode=HTML
# JPA # JPA
spring.jpa.hibernate.ddl-auto=none spring.jpa.hibernate.ddl-auto=none
spring.jpa.open-in-view=false
# Internationalization # Internationalization
spring.messages.basename=messages/messages spring.messages.basename=messages/messages
# Actuator / Management # Actuator
management.endpoints.web.base-path=/manage
management.endpoints.web.exposure.include=* management.endpoints.web.exposure.include=*
# Logging # Logging

View file

@ -0,0 +1,53 @@
INSERT INTO vets VALUES (1, 'James', 'Carter');
INSERT INTO vets VALUES (2, 'Helen', 'Leary');
INSERT INTO vets VALUES (3, 'Linda', 'Douglas');
INSERT INTO vets VALUES (4, 'Rafael', 'Ortega');
INSERT INTO vets VALUES (5, 'Henry', 'Stevens');
INSERT INTO vets VALUES (6, 'Sharon', 'Jenkins');
INSERT INTO specialties VALUES (1, 'radiology');
INSERT INTO specialties VALUES (2, 'surgery');
INSERT INTO specialties VALUES (3, 'dentistry');
INSERT INTO vet_specialties VALUES (2, 1);
INSERT INTO vet_specialties VALUES (3, 2);
INSERT INTO vet_specialties VALUES (3, 3);
INSERT INTO vet_specialties VALUES (4, 2);
INSERT INTO vet_specialties VALUES (5, 1);
INSERT INTO types VALUES (1, 'cat');
INSERT INTO types VALUES (2, 'dog');
INSERT INTO types VALUES (3, 'lizard');
INSERT INTO types VALUES (4, 'snake');
INSERT INTO types VALUES (5, 'bird');
INSERT INTO types VALUES (6, 'hamster');
INSERT INTO owners VALUES (1, 'George', 'Franklin', '110 W. Liberty St.', 'Madison', '6085551023');
INSERT INTO owners VALUES (2, 'Betty', 'Davis', '638 Cardinal Ave.', 'Sun Prairie', '6085551749');
INSERT INTO owners VALUES (3, 'Eduardo', 'Rodriquez', '2693 Commerce St.', 'McFarland', '6085558763');
INSERT INTO owners VALUES (4, 'Harold', 'Davis', '563 Friendly St.', 'Windsor', '6085553198');
INSERT INTO owners VALUES (5, 'Peter', 'McTavish', '2387 S. Fair Way', 'Madison', '6085552765');
INSERT INTO owners VALUES (6, 'Jean', 'Coleman', '105 N. Lake St.', 'Monona', '6085552654');
INSERT INTO owners VALUES (7, 'Jeff', 'Black', '1450 Oak Blvd.', 'Monona', '6085555387');
INSERT INTO owners VALUES (8, 'Maria', 'Escobito', '345 Maple St.', 'Madison', '6085557683');
INSERT INTO owners VALUES (9, 'David', 'Schroeder', '2749 Blackhawk Trail', 'Madison', '6085559435');
INSERT INTO owners VALUES (10, 'Carlos', 'Estaban', '2335 Independence La.', 'Waunakee', '6085555487');
INSERT INTO pets VALUES (1, 'Leo', '2010-09-07', 1, 1);
INSERT INTO pets VALUES (2, 'Basil', '2012-08-06', 6, 2);
INSERT INTO pets VALUES (3, 'Rosy', '2011-04-17', 2, 3);
INSERT INTO pets VALUES (4, 'Jewel', '2010-03-07', 2, 3);
INSERT INTO pets VALUES (5, 'Iggy', '2010-11-30', 3, 4);
INSERT INTO pets VALUES (6, 'George', '2010-01-20', 4, 5);
INSERT INTO pets VALUES (7, 'Samantha', '2012-09-04', 1, 6);
INSERT INTO pets VALUES (8, 'Max', '2012-09-04', 1, 6);
INSERT INTO pets VALUES (9, 'Lucky', '2011-08-06', 5, 7);
INSERT INTO pets VALUES (10, 'Mulligan', '2007-02-24', 2, 8);
INSERT INTO pets VALUES (11, 'Freddy', '2010-03-09', 5, 9);
INSERT INTO pets VALUES (12, 'Lucky', '2010-06-24', 2, 10);
INSERT INTO pets VALUES (13, 'Sly', '2012-06-08', 1, 10);
INSERT INTO visits VALUES (1, 7, '2013-01-01', 'rabies shot');
INSERT INTO visits VALUES (2, 8, '2013-01-02', 'rabies shot');
INSERT INTO visits VALUES (3, 8, '2013-01-03', 'neutered');
INSERT INTO visits VALUES (4, 7, '2013-01-04', 'spayed');

View file

@ -0,0 +1,64 @@
DROP TABLE vet_specialties IF EXISTS;
DROP TABLE vets IF EXISTS;
DROP TABLE specialties IF EXISTS;
DROP TABLE visits IF EXISTS;
DROP TABLE pets IF EXISTS;
DROP TABLE types IF EXISTS;
DROP TABLE owners IF EXISTS;
CREATE TABLE vets (
id INTEGER IDENTITY PRIMARY KEY,
first_name VARCHAR(30),
last_name VARCHAR(30)
);
CREATE INDEX vets_last_name ON vets (last_name);
CREATE TABLE specialties (
id INTEGER IDENTITY PRIMARY KEY,
name VARCHAR(80)
);
CREATE INDEX specialties_name ON specialties (name);
CREATE TABLE vet_specialties (
vet_id INTEGER NOT NULL,
specialty_id INTEGER NOT NULL
);
ALTER TABLE vet_specialties ADD CONSTRAINT fk_vet_specialties_vets FOREIGN KEY (vet_id) REFERENCES vets (id);
ALTER TABLE vet_specialties ADD CONSTRAINT fk_vet_specialties_specialties FOREIGN KEY (specialty_id) REFERENCES specialties (id);
CREATE TABLE types (
id INTEGER IDENTITY PRIMARY KEY,
name VARCHAR(80)
);
CREATE INDEX types_name ON types (name);
CREATE TABLE owners (
id INTEGER IDENTITY PRIMARY KEY,
first_name VARCHAR(30),
last_name VARCHAR_IGNORECASE(30),
address VARCHAR(255),
city VARCHAR(80),
telephone VARCHAR(20)
);
CREATE INDEX owners_last_name ON owners (last_name);
CREATE TABLE pets (
id INTEGER IDENTITY PRIMARY KEY,
name VARCHAR(30),
birth_date DATE,
type_id INTEGER NOT NULL,
owner_id INTEGER NOT NULL
);
ALTER TABLE pets ADD CONSTRAINT fk_pets_owners FOREIGN KEY (owner_id) REFERENCES owners (id);
ALTER TABLE pets ADD CONSTRAINT fk_pets_types FOREIGN KEY (type_id) REFERENCES types (id);
CREATE INDEX pets_name ON pets (name);
CREATE TABLE visits (
id INTEGER IDENTITY PRIMARY KEY,
pet_id INTEGER NOT NULL,
visit_date DATE,
description VARCHAR(255)
);
ALTER TABLE visits ADD CONSTRAINT fk_visits_pets FOREIGN KEY (pet_id) REFERENCES pets (id);
CREATE INDEX visits_pet_id ON visits (pet_id);

View file

@ -18,8 +18,10 @@
mysql_1_eedb4818d817 | MySQL init process done. Ready for start up. mysql_1_eedb4818d817 | MySQL init process done. Ready for start up.
... ...
2) Create the PetClinic database and user by executing the "db/mysql/{schema,data}.sql" 2) (Once only) create the PetClinic database and user by executing the "db/mysql/user.sql"
scripts (or set "spring.datasource.initialization-mode=always" the first time you run the app). scripts. You can connect to the database running in the docker container using
`mysql -u root -h localhost --protocol tcp`, but you don't need to run the script there
because the petclinic user is already set up if you use the provided `docker-compose.yaml`.
3) Run the app with `spring.profiles.active=mysql` (e.g. as a System property via the command 3) Run the app with `spring.profiles.active=mysql` (e.g. as a System property via the command
line, but any way that sets that property in a Spring Boot app should work). line, but any way that sets that property in a Spring Boot app should work).
@ -33,5 +35,6 @@
To set profiles & properties. To set profiles & properties.
N.B. the "petclinic" database has to exist for the app to work with the JDBC URL value N.B. the "petclinic" database has to exist for the app to work with the JDBC URL value
as it is configured by default. This condition is taken care of by the docker-compose as it is configured by default. This condition is taken care of automatically by the
configuration provided, or by the `schema.sql` if you can run that as root. docker-compose configuration provided, or by the `user.sql` script if you run that as
root.

View file

@ -1,13 +1,3 @@
CREATE DATABASE IF NOT EXISTS petclinic;
ALTER DATABASE petclinic
DEFAULT CHARACTER SET utf8
DEFAULT COLLATE utf8_general_ci;
GRANT ALL PRIVILEGES ON petclinic.* TO pc@localhost IDENTIFIED BY 'pc';
USE petclinic;
CREATE TABLE IF NOT EXISTS vets ( CREATE TABLE IF NOT EXISTS vets (
id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
first_name VARCHAR(30), first_name VARCHAR(30),

View file

@ -0,0 +1,7 @@
CREATE DATABASE IF NOT EXISTS petclinic;
ALTER DATABASE petclinic
DEFAULT CHARACTER SET utf8
DEFAULT COLLATE utf8_general_ci;
GRANT ALL PRIVILEGES ON petclinic.* TO 'petclinic'@'%' IDENTIFIED BY 'petclinic';

View file

@ -0,0 +1,8 @@
welcome=Bienvenido
required=Es requerido
notFound=No ha sido encontrado
duplicate=Ya se encuentra en uso
nonNumeric=Sólo debe contener numeros
duplicateFormSubmission=No se permite el envío de formularios duplicados
typeMismatch.date=Fecha invalida
typeMismatch.birthDate=Fecha invalida

View file

@ -67,6 +67,7 @@
</ul> </ul>
</div> </div>
</div> </div>
</div>
</nav> </nav>
<div class="container-fluid"> <div class="container-fluid">
<div class="container xd-container"> <div class="container xd-container">
@ -78,8 +79,8 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-12 text-center"> <div class="col-12 text-center">
<img src="../static/resources/images/spring-pivotal-logo.png" th:src="@{/resources/images/spring-pivotal-logo.png}" <img src="../static/resources/images/spring-pivotal-logo.png"
alt="Sponsored by Pivotal"/></div> th:src="@{/resources/images/spring-pivotal-logo.png}" alt="Sponsored by Pivotal" /></div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -24,7 +24,7 @@
th:text="${#temporals.format(pet.birthDate, dateFormat)}" /></td> th:text="${#temporals.format(pet.birthDate, dateFormat)}" /></td>
<td th:text="${pet.type}" /></td> <td th:text="${pet.type}" /></td>
<td <td
th:text="${pet.owner?.firstName + ' ' + pet.owner?.lastName}" /></td> th:text="${pet.owner?.firstName + ' ' + pet.owner?.lastName}"></td>
</tr> </tr>
</table> </table>

View file

@ -32,4 +32,5 @@ class PetclinicIntegrationTests {
vets.findAll(); vets.findAll();
vets.findAll(); // served from cache vets.findAll(); // served from cache
} }
} }

View file

@ -49,8 +49,7 @@ class ValidatorTests {
person.setLastName("smith"); person.setLastName("smith");
Validator validator = createValidator(); Validator validator = createValidator();
Set<ConstraintViolation<Person>> constraintViolations = validator Set<ConstraintViolation<Person>> constraintViolations = validator.validate(person);
.validate(person);
assertThat(constraintViolations).hasSize(1); assertThat(constraintViolations).hasSize(1);
ConstraintViolation<Person> violation = constraintViolations.iterator().next(); ConstraintViolation<Person> violation = constraintViolations.iterator().next();

View file

@ -89,33 +89,22 @@ class OwnerControllerTests {
@Test @Test
void testInitCreationForm() throws Exception { void testInitCreationForm() throws Exception {
mockMvc.perform(get("/owners/new")) mockMvc.perform(get("/owners/new")).andExpect(status().isOk()).andExpect(model().attributeExists("owner"))
.andExpect(status().isOk())
.andExpect(model().attributeExists("owner"))
.andExpect(view().name("owners/createOrUpdateOwnerForm")); .andExpect(view().name("owners/createOrUpdateOwnerForm"));
} }
@Test @Test
void testProcessCreationFormSuccess() throws Exception { void testProcessCreationFormSuccess() throws Exception {
mockMvc.perform(post("/owners/new") mockMvc.perform(post("/owners/new").param("firstName", "Joe").param("lastName", "Bloggs")
.param("firstName", "Joe") .param("address", "123 Caramel Street").param("city", "London").param("telephone", "01316761638"))
.param("lastName", "Bloggs")
.param("address", "123 Caramel Street")
.param("city", "London")
.param("telephone", "01316761638")
)
.andExpect(status().is3xxRedirection()); .andExpect(status().is3xxRedirection());
} }
@Test @Test
void testProcessCreationFormHasErrors() throws Exception { void testProcessCreationFormHasErrors() throws Exception {
mockMvc.perform(post("/owners/new") mockMvc.perform(
.param("firstName", "Joe") post("/owners/new").param("firstName", "Joe").param("lastName", "Bloggs").param("city", "London"))
.param("lastName", "Bloggs") .andExpect(status().isOk()).andExpect(model().attributeHasErrors("owner"))
.param("city", "London")
)
.andExpect(status().isOk())
.andExpect(model().attributeHasErrors("owner"))
.andExpect(model().attributeHasFieldErrors("owner", "address")) .andExpect(model().attributeHasFieldErrors("owner", "address"))
.andExpect(model().attributeHasFieldErrors("owner", "telephone")) .andExpect(model().attributeHasFieldErrors("owner", "telephone"))
.andExpect(view().name("owners/createOrUpdateOwnerForm")); .andExpect(view().name("owners/createOrUpdateOwnerForm"));
@ -123,36 +112,26 @@ class OwnerControllerTests {
@Test @Test
void testInitFindForm() throws Exception { void testInitFindForm() throws Exception {
mockMvc.perform(get("/owners/find")) mockMvc.perform(get("/owners/find")).andExpect(status().isOk()).andExpect(model().attributeExists("owner"))
.andExpect(status().isOk())
.andExpect(model().attributeExists("owner"))
.andExpect(view().name("owners/findOwners")); .andExpect(view().name("owners/findOwners"));
} }
@Test @Test
void testProcessFindFormSuccess() throws Exception { void testProcessFindFormSuccess() throws Exception {
given(this.owners.findByLastName("")).willReturn(Lists.newArrayList(george, new Owner())); given(this.owners.findByLastName("")).willReturn(Lists.newArrayList(george, new Owner()));
mockMvc.perform(get("/owners")) mockMvc.perform(get("/owners")).andExpect(status().isOk()).andExpect(view().name("owners/ownersList"));
.andExpect(status().isOk())
.andExpect(view().name("owners/ownersList"));
} }
@Test @Test
void testProcessFindFormByLastName() throws Exception { void testProcessFindFormByLastName() throws Exception {
given(this.owners.findByLastName(george.getLastName())).willReturn(Lists.newArrayList(george)); given(this.owners.findByLastName(george.getLastName())).willReturn(Lists.newArrayList(george));
mockMvc.perform(get("/owners") mockMvc.perform(get("/owners").param("lastName", "Franklin")).andExpect(status().is3xxRedirection())
.param("lastName", "Franklin")
)
.andExpect(status().is3xxRedirection())
.andExpect(view().name("redirect:/owners/" + TEST_OWNER_ID)); .andExpect(view().name("redirect:/owners/" + TEST_OWNER_ID));
} }
@Test @Test
void testProcessFindFormNoOwnersFound() throws Exception { void testProcessFindFormNoOwnersFound() throws Exception {
mockMvc.perform(get("/owners") mockMvc.perform(get("/owners").param("lastName", "Unknown Surname")).andExpect(status().isOk())
.param("lastName", "Unknown Surname")
)
.andExpect(status().isOk())
.andExpect(model().attributeHasFieldErrors("owner", "lastName")) .andExpect(model().attributeHasFieldErrors("owner", "lastName"))
.andExpect(model().attributeHasFieldErrorCode("owner", "lastName", "notFound")) .andExpect(model().attributeHasFieldErrorCode("owner", "lastName", "notFound"))
.andExpect(view().name("owners/findOwners")); .andExpect(view().name("owners/findOwners"));
@ -160,8 +139,7 @@ class OwnerControllerTests {
@Test @Test
void testInitUpdateOwnerForm() throws Exception { void testInitUpdateOwnerForm() throws Exception {
mockMvc.perform(get("/owners/{ownerId}/edit", TEST_OWNER_ID)) mockMvc.perform(get("/owners/{ownerId}/edit", TEST_OWNER_ID)).andExpect(status().isOk())
.andExpect(status().isOk())
.andExpect(model().attributeExists("owner")) .andExpect(model().attributeExists("owner"))
.andExpect(model().attribute("owner", hasProperty("lastName", is("Franklin")))) .andExpect(model().attribute("owner", hasProperty("lastName", is("Franklin"))))
.andExpect(model().attribute("owner", hasProperty("firstName", is("George")))) .andExpect(model().attribute("owner", hasProperty("firstName", is("George"))))
@ -173,25 +151,16 @@ class OwnerControllerTests {
@Test @Test
void testProcessUpdateOwnerFormSuccess() throws Exception { void testProcessUpdateOwnerFormSuccess() throws Exception {
mockMvc.perform(post("/owners/{ownerId}/edit", TEST_OWNER_ID) mockMvc.perform(post("/owners/{ownerId}/edit", TEST_OWNER_ID).param("firstName", "Joe")
.param("firstName", "Joe") .param("lastName", "Bloggs").param("address", "123 Caramel Street").param("city", "London")
.param("lastName", "Bloggs") .param("telephone", "01616291589")).andExpect(status().is3xxRedirection())
.param("address", "123 Caramel Street")
.param("city", "London")
.param("telephone", "01616291589")
)
.andExpect(status().is3xxRedirection())
.andExpect(view().name("redirect:/owners/{ownerId}")); .andExpect(view().name("redirect:/owners/{ownerId}"));
} }
@Test @Test
void testProcessUpdateOwnerFormHasErrors() throws Exception { void testProcessUpdateOwnerFormHasErrors() throws Exception {
mockMvc.perform(post("/owners/{ownerId}/edit", TEST_OWNER_ID) mockMvc.perform(post("/owners/{ownerId}/edit", TEST_OWNER_ID).param("firstName", "Joe")
.param("firstName", "Joe") .param("lastName", "Bloggs").param("city", "London")).andExpect(status().isOk())
.param("lastName", "Bloggs")
.param("city", "London")
)
.andExpect(status().isOk())
.andExpect(model().attributeHasErrors("owner")) .andExpect(model().attributeHasErrors("owner"))
.andExpect(model().attributeHasFieldErrors("owner", "address")) .andExpect(model().attributeHasFieldErrors("owner", "address"))
.andExpect(model().attributeHasFieldErrors("owner", "telephone")) .andExpect(model().attributeHasFieldErrors("owner", "telephone"))
@ -200,8 +169,7 @@ class OwnerControllerTests {
@Test @Test
void testShowOwner() throws Exception { void testShowOwner() throws Exception {
mockMvc.perform(get("/owners/{ownerId}", TEST_OWNER_ID)) mockMvc.perform(get("/owners/{ownerId}", TEST_OWNER_ID)).andExpect(status().isOk())
.andExpect(status().isOk())
.andExpect(model().attribute("owner", hasProperty("lastName", is("Franklin")))) .andExpect(model().attribute("owner", hasProperty("lastName", is("Franklin"))))
.andExpect(model().attribute("owner", hasProperty("firstName", is("George")))) .andExpect(model().attribute("owner", hasProperty("firstName", is("George"))))
.andExpect(model().attribute("owner", hasProperty("address", is("110 W. Liberty St.")))) .andExpect(model().attribute("owner", hasProperty("address", is("110 W. Liberty St."))))
@ -224,8 +192,8 @@ class OwnerControllerTests {
@Override @Override
public void describeTo(Description description) { public void describeTo(Description description) {
description.appendText("Max did not have any visits"); description.appendText("Max did not have any visits");
}}))) }
.andExpect(view().name("owners/ownerDetails")); }))).andExpect(view().name("owners/ownerDetails"));
} }
} }

View file

@ -39,14 +39,12 @@ import org.springframework.test.web.servlet.MockMvc;
* @author Colin But * @author Colin But
*/ */
@WebMvcTest(value = PetController.class, @WebMvcTest(value = PetController.class,
includeFilters = @ComponentScan.Filter( includeFilters = @ComponentScan.Filter(value = PetTypeFormatter.class, type = FilterType.ASSIGNABLE_TYPE))
value = PetTypeFormatter.class,
type = FilterType.ASSIGNABLE_TYPE))
class PetControllerTests { class PetControllerTests {
private static final int TEST_OWNER_ID = 1; private static final int TEST_OWNER_ID = 1;
private static final int TEST_PET_ID = 1;
private static final int TEST_PET_ID = 1;
@Autowired @Autowired
private MockMvc mockMvc; private MockMvc mockMvc;
@ -70,65 +68,45 @@ class PetControllerTests {
@Test @Test
void testInitCreationForm() throws Exception { void testInitCreationForm() throws Exception {
mockMvc.perform(get("/owners/{ownerId}/pets/new", TEST_OWNER_ID)) mockMvc.perform(get("/owners/{ownerId}/pets/new", TEST_OWNER_ID)).andExpect(status().isOk())
.andExpect(status().isOk()) .andExpect(view().name("pets/createOrUpdatePetForm")).andExpect(model().attributeExists("pet"));
.andExpect(view().name("pets/createOrUpdatePetForm"))
.andExpect(model().attributeExists("pet"));
} }
@Test @Test
void testProcessCreationFormSuccess() throws Exception { void testProcessCreationFormSuccess() throws Exception {
mockMvc.perform(post("/owners/{ownerId}/pets/new", TEST_OWNER_ID) mockMvc.perform(post("/owners/{ownerId}/pets/new", TEST_OWNER_ID).param("name", "Betty")
.param("name", "Betty") .param("type", "hamster").param("birthDate", "2015-02-12")).andExpect(status().is3xxRedirection())
.param("type", "hamster")
.param("birthDate", "2015-02-12")
)
.andExpect(status().is3xxRedirection())
.andExpect(view().name("redirect:/owners/{ownerId}")); .andExpect(view().name("redirect:/owners/{ownerId}"));
} }
@Test @Test
void testProcessCreationFormHasErrors() throws Exception { void testProcessCreationFormHasErrors() throws Exception {
mockMvc.perform(post("/owners/{ownerId}/pets/new", TEST_OWNER_ID) mockMvc.perform(post("/owners/{ownerId}/pets/new", TEST_OWNER_ID).param("name", "Betty").param("birthDate",
.param("name", "Betty") "2015-02-12")).andExpect(model().attributeHasNoErrors("owner"))
.param("birthDate", "2015-02-12") .andExpect(model().attributeHasErrors("pet")).andExpect(model().attributeHasFieldErrors("pet", "type"))
) .andExpect(model().attributeHasFieldErrorCode("pet", "type", "required")).andExpect(status().isOk())
.andExpect(model().attributeHasNoErrors("owner"))
.andExpect(model().attributeHasErrors("pet"))
.andExpect(model().attributeHasFieldErrors("pet", "type"))
.andExpect(model().attributeHasFieldErrorCode("pet", "type", "required"))
.andExpect(status().isOk())
.andExpect(view().name("pets/createOrUpdatePetForm")); .andExpect(view().name("pets/createOrUpdatePetForm"));
} }
@Test @Test
void testInitUpdateForm() throws Exception { void testInitUpdateForm() throws Exception {
mockMvc.perform(get("/owners/{ownerId}/pets/{petId}/edit", TEST_OWNER_ID, TEST_PET_ID)) mockMvc.perform(get("/owners/{ownerId}/pets/{petId}/edit", TEST_OWNER_ID, TEST_PET_ID))
.andExpect(status().isOk()) .andExpect(status().isOk()).andExpect(model().attributeExists("pet"))
.andExpect(model().attributeExists("pet"))
.andExpect(view().name("pets/createOrUpdatePetForm")); .andExpect(view().name("pets/createOrUpdatePetForm"));
} }
@Test @Test
void testProcessUpdateFormSuccess() throws Exception { void testProcessUpdateFormSuccess() throws Exception {
mockMvc.perform(post("/owners/{ownerId}/pets/{petId}/edit", TEST_OWNER_ID, TEST_PET_ID) mockMvc.perform(post("/owners/{ownerId}/pets/{petId}/edit", TEST_OWNER_ID, TEST_PET_ID).param("name", "Betty")
.param("name", "Betty") .param("type", "hamster").param("birthDate", "2015-02-12")).andExpect(status().is3xxRedirection())
.param("type", "hamster")
.param("birthDate", "2015-02-12")
)
.andExpect(status().is3xxRedirection())
.andExpect(view().name("redirect:/owners/{ownerId}")); .andExpect(view().name("redirect:/owners/{ownerId}"));
} }
@Test @Test
void testProcessUpdateFormHasErrors() throws Exception { void testProcessUpdateFormHasErrors() throws Exception {
mockMvc.perform(post("/owners/{ownerId}/pets/{petId}/edit", TEST_OWNER_ID, TEST_PET_ID) mockMvc.perform(post("/owners/{ownerId}/pets/{petId}/edit", TEST_OWNER_ID, TEST_PET_ID).param("name", "Betty")
.param("name", "Betty") .param("birthDate", "2015/02/12")).andExpect(model().attributeHasNoErrors("owner"))
.param("birthDate", "2015/02/12") .andExpect(model().attributeHasErrors("pet")).andExpect(status().isOk())
)
.andExpect(model().attributeHasNoErrors("owner"))
.andExpect(model().attributeHasErrors("pet"))
.andExpect(status().isOk())
.andExpect(view().name("pets/createOrUpdatePetForm")); .andExpect(view().name("pets/createOrUpdatePetForm"));
} }

View file

@ -75,7 +75,6 @@ class PetTypeFormatterTests {
/** /**
* Helper method to produce some sample pet types just for test purpose * Helper method to produce some sample pet types just for test purpose
*
* @return {@link Collection} of {@link PetType} * @return {@link Collection} of {@link PetType}
*/ */
private List<PetType> makePetTypes() { private List<PetType> makePetTypes() {

View file

@ -57,28 +57,21 @@ class VisitControllerTests {
@Test @Test
void testInitNewVisitForm() throws Exception { void testInitNewVisitForm() throws Exception {
mockMvc.perform(get("/owners/*/pets/{petId}/visits/new", TEST_PET_ID)) mockMvc.perform(get("/owners/*/pets/{petId}/visits/new", TEST_PET_ID)).andExpect(status().isOk())
.andExpect(status().isOk())
.andExpect(view().name("pets/createOrUpdateVisitForm")); .andExpect(view().name("pets/createOrUpdateVisitForm"));
} }
@Test @Test
void testProcessNewVisitFormSuccess() throws Exception { void testProcessNewVisitFormSuccess() throws Exception {
mockMvc.perform(post("/owners/*/pets/{petId}/visits/new", TEST_PET_ID) mockMvc.perform(post("/owners/*/pets/{petId}/visits/new", TEST_PET_ID).param("name", "George")
.param("name", "George") .param("description", "Visit Description")).andExpect(status().is3xxRedirection())
.param("description", "Visit Description")
)
.andExpect(status().is3xxRedirection())
.andExpect(view().name("redirect:/owners/{ownerId}")); .andExpect(view().name("redirect:/owners/{ownerId}"));
} }
@Test @Test
void testProcessNewVisitFormHasErrors() throws Exception { void testProcessNewVisitFormHasErrors() throws Exception {
mockMvc.perform(post("/owners/*/pets/{petId}/visits/new", TEST_PET_ID) mockMvc.perform(post("/owners/*/pets/{petId}/visits/new", TEST_PET_ID).param("name", "George"))
.param("name", "George") .andExpect(model().attributeHasErrors("visit")).andExpect(status().isOk())
)
.andExpect(model().attributeHasErrors("visit"))
.andExpect(status().isOk())
.andExpect(view().name("pets/createOrUpdateVisitForm")); .andExpect(view().name("pets/createOrUpdateVisitForm"));
} }

View file

@ -40,15 +40,24 @@ import org.springframework.transaction.annotation.Transactional;
/** /**
* Integration test of the Service and the Repository layer. * Integration test of the Service and the Repository layer.
* <p> * <p>
* ClinicServiceSpringDataJpaTests subclasses benefit from the following services provided by the Spring * ClinicServiceSpringDataJpaTests subclasses benefit from the following services provided
* TestContext Framework: </p> <ul> <li><strong>Spring IoC container caching</strong> which spares us unnecessary set up * by the Spring TestContext Framework:
* time between test execution.</li> <li><strong>Dependency Injection</strong> of test fixture instances, meaning that * </p>
* we don't need to perform application context lookups. See the use of {@link Autowired @Autowired} on the <code>{@link * <ul>
* ClinicServiceTests#clinicService clinicService}</code> instance variable, which uses autowiring <em>by * <li><strong>Spring IoC container caching</strong> which spares us unnecessary set up
* type</em>. <li><strong>Transaction management</strong>, meaning each test method is executed in its own transaction, * time between test execution.</li>
* which is automatically rolled back by default. Thus, even if tests insert or otherwise change database state, there * <li><strong>Dependency Injection</strong> of test fixture instances, meaning that we
* is no need for a teardown or cleanup script. <li> An {@link org.springframework.context.ApplicationContext * don't need to perform application context lookups. See the use of
* ApplicationContext} is also inherited and can be used for explicit bean lookup if necessary. </li> </ul> * {@link Autowired @Autowired} on the <code>{@link
* ClinicServiceTests#clinicService clinicService}</code> instance variable, which uses
* autowiring <em>by type</em>.
* <li><strong>Transaction management</strong>, meaning each test method is executed in
* its own transaction, which is automatically rolled back by default. Thus, even if tests
* insert or otherwise change database state, there is no need for a teardown or cleanup
* script.
* <li>An {@link org.springframework.context.ApplicationContext ApplicationContext} is
* also inherited and can be used for explicit bean lookup if necessary.</li>
* </ul>
* *
* @author Ken Krebs * @author Ken Krebs
* @author Rod Johnson * @author Rod Johnson

View file

@ -22,8 +22,8 @@ import org.springframework.orm.ObjectRetrievalFailureException;
import org.springframework.samples.petclinic.model.BaseEntity; import org.springframework.samples.petclinic.model.BaseEntity;
/** /**
* Utility methods for handling entities. Separate from the BaseEntity class mainly because of dependency on the * Utility methods for handling entities. Separate from the BaseEntity class mainly
* ORM-associated ObjectRetrievalFailureException. * because of dependency on the ORM-associated ObjectRetrievalFailureException.
* *
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Sam Brannen * @author Sam Brannen
@ -34,7 +34,6 @@ public abstract class EntityUtils {
/** /**
* Look up the entity of the given class with the given id in the given collection. * Look up the entity of the given class with the given id in the given collection.
*
* @param entities the collection to search * @param entities the collection to search
* @param entityClass the entity class to look up * @param entityClass the entity class to look up
* @param entityId the entity id to look up * @param entityId the entity id to look up

View file

@ -44,7 +44,8 @@ class CrashControllerTests {
@Test @Test
void testTriggerException() throws Exception { void testTriggerException() throws Exception {
mockMvc.perform(get("/oups")).andExpect(view().name("exception")) mockMvc.perform(get("/oups")).andExpect(view().name("exception"))
.andExpect(model().attributeExists("exception")) .andExpect(model().attributeExists("exception")).andExpect(forwardedUrl("exception"))
.andExpect(forwardedUrl("exception")).andExpect(status().isOk()); .andExpect(status().isOk());
} }
} }

View file

@ -65,16 +65,14 @@ class VetControllerTests {
@Test @Test
void testShowVetListHtml() throws Exception { void testShowVetListHtml() throws Exception {
mockMvc.perform(get("/vets.html")) mockMvc.perform(get("/vets.html")).andExpect(status().isOk()).andExpect(model().attributeExists("vets"))
.andExpect(status().isOk())
.andExpect(model().attributeExists("vets"))
.andExpect(view().name("vets/vetList")); .andExpect(view().name("vets/vetList"));
} }
@Test @Test
void testShowResourcesVetList() throws Exception { void testShowResourcesVetList() throws Exception {
ResultActions actions = mockMvc.perform(get("/vets") ResultActions actions = mockMvc.perform(get("/vets").accept(MediaType.APPLICATION_JSON))
.accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk()); .andExpect(status().isOk());
actions.andExpect(content().contentType(MediaType.APPLICATION_JSON)) actions.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.vetList[0].id").value(1)); .andExpect(jsonPath("$.vetList[0].id").value(1));
} }

View file

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