adding checkstyle.xml and formatting the files

This commit is contained in:
anbu 2021-01-30 20:16:33 +00:00
parent 8c6f7820ff
commit e063ff2dbf
38 changed files with 1499 additions and 1132 deletions

View file

@ -10,7 +10,7 @@ indent_style = space
[*.{java,xml}] [*.{java,xml}]
indent_size = 4 indent_size = 4
trim_trailing_whitespace = true trim_trailing_whitespace = true
indent_style = tab indent_style = space
tab_width = 4 tab_width = 4
[{pom,wro}.xml] [{pom,wro}.xml]

57
pom.xml
View file

@ -1,7 +1,7 @@
<?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/xsd/maven-4.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>
@ -62,12 +62,12 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope> <scope>test</scope>
<exclusions> <exclusions>
<exclusion> <exclusion>
<groupId>org.junit.vintage</groupId> <groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId> <artifactId>junit-vintage-engine</artifactId>
</exclusion> </exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<!-- Databases - Uses H2 by default --> <!-- Databases - Uses H2 by default -->
@ -92,6 +92,12 @@
<artifactId>ehcache</artifactId> <artifactId>ehcache</artifactId>
</dependency> </dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- webjars --> <!-- webjars -->
<dependency> <dependency>
<groupId>org.webjars</groupId> <groupId>org.webjars</groupId>
@ -123,47 +129,28 @@
<build> <build>
<plugins> <plugins>
<plugin>
<groupId>io.spring.javaformat</groupId>
<artifactId>spring-javaformat-maven-plugin</artifactId>
<version>${spring-format.version}</version>
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>validate</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId> <artifactId>maven-checkstyle-plugin</artifactId>
<version>3.1.1</version> <version>3.1.1</version>
<configuration>
<configLocation>src/checkstyle/checkstyle.xml</configLocation>
<encoding>UTF-8</encoding>
<consoleOutput>true</consoleOutput>
<failsOnError>true</failsOnError>
<linkXRef>false</linkXRef>
</configuration>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>com.puppycrawl.tools</groupId> <groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId> <artifactId>checkstyle</artifactId>
<version>8.32</version> <version>8.32</version>
</dependency> </dependency>
<dependency>
<groupId>io.spring.nohttp</groupId>
<artifactId>nohttp-checkstyle</artifactId>
<version>${nohttp-checkstyle.version}</version>
</dependency>
</dependencies> </dependencies>
<executions> <executions>
<execution> <execution>
<id>nohttp-checkstyle-validation</id> <id>validate</id>
<phase>validate</phase> <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> <goals>
<goal>check</goal> <goal>check</goal>
</goals> </goals>

View file

@ -0,0 +1,333 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
"https://checkstyle.org/dtds/configuration_1_3.dtd">
<!--
Checkstyle configuration that checks the Google coding conventions from Google Java Style
that can be found at https://google.github.io/styleguide/javaguide.html
Checkstyle is very configurable. Be sure to read the documentation at
http://checkstyle.org (or in your downloaded distribution).
To completely disable a check, just comment it out or delete it from the file.
To suppress certain violations please review suppression filters.
Authors: Max Vetrenko, Ruslan Diachenko, Roman Ivanov.
-->
<module name = "Checker">
<property name="charset" value="UTF-8"/>
<property name="severity" value="error"/>
<property name="fileExtensions" value="java, properties, xml"/>
<!-- Excludes all 'module-info.java' files -->
<!-- See https://checkstyle.org/config_filefilters.html -->
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="module\-info\.java$"/>
</module>
<!-- https://checkstyle.org/config_filters.html#SuppressionFilter -->
<module name="SuppressionFilter">
<property name="file" value="${org.checkstyle.google.suppressionfilter.config}"
default="checkstyle-suppressions.xml" />
<property name="optional" value="true"/>
</module>
<!-- Checks for whitespace -->
<!-- See http://checkstyle.org/config_whitespace.html -->
<module name="FileTabCharacter">
<property name="eachLine" value="true"/>
</module>
<module name="LineLength">
<property name="fileExtensions" value="java"/>
<property name="max" value="120"/>
<property name="ignorePattern" value="^package.*|^import.*|a href|href|http://|https://|ftp://"/>
</module>
<module name="TreeWalker">
<module name="OuterTypeFilename"/>
<module name="IllegalTokenText">
<property name="tokens" value="STRING_LITERAL, CHAR_LITERAL"/>
<property name="format"
value="\\u00(09|0(a|A)|0(c|C)|0(d|D)|22|27|5(C|c))|\\(0(10|11|12|14|15|42|47)|134)"/>
<property name="message"
value="Consider using special escape sequence instead of octal value or Unicode escaped value."/>
</module>
<module name="AvoidEscapedUnicodeCharacters">
<property name="allowEscapesForControlCharacters" value="true"/>
<property name="allowByTailComment" value="true"/>
<property name="allowNonPrintableEscapes" value="true"/>
</module>
<module name="AvoidStarImport"/>
<module name="OneTopLevelClass"/>
<module name="NoLineWrap">
<property name="tokens" value="PACKAGE_DEF, IMPORT, STATIC_IMPORT"/>
</module>
<module name="EmptyBlock">
<property name="option" value="TEXT"/>
<property name="tokens"
value="LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/>
</module>
<module name="NeedBraces">
<property name="tokens"
value="LITERAL_DO, LITERAL_ELSE, LITERAL_FOR, LITERAL_IF, LITERAL_WHILE"/>
</module>
<module name="LeftCurly">
<property name="tokens"
value="ANNOTATION_DEF, CLASS_DEF, CTOR_DEF, ENUM_CONSTANT_DEF, ENUM_DEF,
INTERFACE_DEF, LAMBDA, LITERAL_CASE, LITERAL_CATCH, LITERAL_DEFAULT,
LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF,
LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE, METHOD_DEF,
OBJBLOCK, STATIC_INIT"/>
</module>
<module name="RightCurly">
<property name="id" value="RightCurlySame"/>
<property name="tokens"
value="LITERAL_TRY, LITERAL_CATCH, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE,
LITERAL_DO"/>
</module>
<module name="RightCurly">
<property name="id" value="RightCurlyAlone"/>
<property name="option" value="alone"/>
<property name="tokens"
value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, STATIC_INIT,
INSTANCE_INIT, ANNOTATION_DEF, ENUM_DEF"/>
</module>
<module name="SuppressionXpathSingleFilter">
<!-- suppresion is required till https://github.com/checkstyle/checkstyle/issues/7541 -->
<property name="id" value="RightCurlyAlone"/>
<property name="query" value="//RCURLY[parent::SLIST[count(./*)=1]
or preceding-sibling::*[last()][self::LCURLY]]"/>
</module>
<module name="WhitespaceAfter">
<property name="tokens"
value="COMMA, SEMI, TYPECAST, LITERAL_IF, LITERAL_ELSE,
LITERAL_WHILE, LITERAL_DO, LITERAL_FOR, DO_WHILE"/>
</module>
<module name="WhitespaceAround">
<property name="allowEmptyConstructors" value="true"/>
<property name="allowEmptyLambdas" value="true"/>
<property name="allowEmptyMethods" value="true"/>
<property name="allowEmptyTypes" value="true"/>
<property name="allowEmptyLoops" value="true"/>
<property name="tokens"
value="ASSIGN, BAND, BAND_ASSIGN, BOR, BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR,
BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN, DO_WHILE, EQUAL, GE, GT, LAMBDA, LAND,
LCURLY, LE, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY,
LITERAL_FOR, LITERAL_IF, LITERAL_RETURN, LITERAL_SWITCH, LITERAL_SYNCHRONIZED,
LITERAL_TRY, LITERAL_WHILE, LOR, LT, MINUS, MINUS_ASSIGN, MOD, MOD_ASSIGN,
NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION, RCURLY, SL, SLIST, SL_ASSIGN, SR,
SR_ASSIGN, STAR, STAR_ASSIGN, LITERAL_ASSERT, TYPE_EXTENSION_AND"/>
<message key="ws.notFollowed"
value="WhitespaceAround: ''{0}'' is not followed by whitespace. Empty blocks may only be represented as '{}' when not part of a multi-block statement (4.1.3)"/>
<message key="ws.notPreceded"
value="WhitespaceAround: ''{0}'' is not preceded with whitespace."/>
</module>
<module name="OneStatementPerLine"/>
<module name="MultipleVariableDeclarations"/>
<module name="ArrayTypeStyle"/>
<module name="MissingSwitchDefault"/>
<module name="FallThrough"/>
<module name="UpperEll"/>
<module name="ModifierOrder"/>
<module name="EmptyLineSeparator">
<property name="tokens"
value="PACKAGE_DEF, IMPORT, STATIC_IMPORT, CLASS_DEF, INTERFACE_DEF, ENUM_DEF,
STATIC_INIT, INSTANCE_INIT, METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/>
<property name="allowNoEmptyLineBetweenFields" value="true"/>
</module>
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapDot"/>
<property name="tokens" value="DOT"/>
<property name="option" value="nl"/>
</module>
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapComma"/>
<property name="tokens" value="COMMA"/>
<property name="option" value="EOL"/>
</module>
<module name="SeparatorWrap">
<!-- ELLIPSIS is EOL until https://github.com/google/styleguide/issues/258 -->
<property name="id" value="SeparatorWrapEllipsis"/>
<property name="tokens" value="ELLIPSIS"/>
<property name="option" value="EOL"/>
</module>
<module name="SeparatorWrap">
<!-- ARRAY_DECLARATOR is EOL until https://github.com/google/styleguide/issues/259 -->
<property name="id" value="SeparatorWrapArrayDeclarator"/>
<property name="tokens" value="ARRAY_DECLARATOR"/>
<property name="option" value="EOL"/>
</module>
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapMethodRef"/>
<property name="tokens" value="METHOD_REF"/>
<property name="option" value="nl"/>
</module>
<module name="PackageName">
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/>
<message key="name.invalidPattern"
value="Package name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="TypeName">
<property name="tokens" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, ANNOTATION_DEF"/>
<message key="name.invalidPattern"
value="Type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="MemberName">
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/>
<message key="name.invalidPattern"
value="Member name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="ParameterName">
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Parameter name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="LambdaParameterName">
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Lambda parameter name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="CatchParameterName">
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Catch parameter name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="LocalVariableName">
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Local variable name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="ClassTypeParameterName">
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
<message key="name.invalidPattern"
value="Class type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="MethodTypeParameterName">
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
<message key="name.invalidPattern"
value="Method type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="InterfaceTypeParameterName">
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
<message key="name.invalidPattern"
value="Interface type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="NoFinalizer"/>
<module name="GenericWhitespace">
<message key="ws.followed"
value="GenericWhitespace ''{0}'' is followed by whitespace."/>
<message key="ws.preceded"
value="GenericWhitespace ''{0}'' is preceded with whitespace."/>
<message key="ws.illegalFollow"
value="GenericWhitespace ''{0}'' should followed by whitespace."/>
<message key="ws.notPreceded"
value="GenericWhitespace ''{0}'' is not preceded with whitespace."/>
</module>
<module name="Indentation">
<property name="basicOffset" value="4"/>
<property name="braceAdjustment" value="0"/>
<property name="caseIndent" value="4"/>
<property name="throwsIndent" value="4"/>
<property name="lineWrappingIndentation" value="4"/>
<property name="arrayInitIndent" value="4"/>
</module>
<module name="AbbreviationAsWordInName">
<property name="ignoreFinal" value="false"/>
<property name="allowedAbbreviationLength" value="1"/>
<property name="tokens"
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, ANNOTATION_DEF, ANNOTATION_FIELD_DEF,
PARAMETER_DEF, VARIABLE_DEF, METHOD_DEF"/>
</module>
<module name="OverloadMethodsDeclarationOrder"/>
<module name="VariableDeclarationUsageDistance"/>
<module name="CustomImportOrder">
<property name="sortImportsInGroupAlphabetically" value="true"/>
<property name="separateLineBetweenGroups" value="true"/>
<property name="customImportOrderRules" value="STATIC###THIRD_PARTY_PACKAGE"/>
<property name="tokens" value="IMPORT, STATIC_IMPORT, PACKAGE_DEF"/>
</module>
<module name="MethodParamPad">
<property name="tokens"
value="CTOR_DEF, LITERAL_NEW, METHOD_CALL, METHOD_DEF,
SUPER_CTOR_CALL, ENUM_CONSTANT_DEF"/>
</module>
<module name="NoWhitespaceBefore">
<property name="tokens"
value="COMMA, SEMI, POST_INC, POST_DEC, DOT, ELLIPSIS,
LABELED_STAT, METHOD_REF"/>
<property name="allowLineBreaks" value="true"/>
</module>
<module name="ParenPad">
<property name="tokens"
value="ANNOTATION, ANNOTATION_FIELD_DEF, CTOR_CALL, CTOR_DEF, DOT, ENUM_CONSTANT_DEF,
EXPR, LITERAL_CATCH, LITERAL_DO, LITERAL_FOR, LITERAL_IF, LITERAL_NEW,
LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_WHILE, METHOD_CALL,
METHOD_DEF, QUESTION, RESOURCE_SPECIFICATION, SUPER_CTOR_CALL, LAMBDA"/>
</module>
<module name="OperatorWrap">
<property name="option" value="NL"/>
<property name="tokens"
value="BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR,
LT, MINUS, MOD, NOT_EQUAL, PLUS, QUESTION, SL, SR, STAR, METHOD_REF "/>
</module>
<module name="AnnotationLocation">
<property name="id" value="AnnotationLocationMostCases"/>
<property name="tokens"
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF"/>
</module>
<module name="AnnotationLocation">
<property name="id" value="AnnotationLocationVariables"/>
<property name="tokens" value="VARIABLE_DEF"/>
<property name="allowSamelineMultipleAnnotations" value="true"/>
</module>
<module name="NonEmptyAtclauseDescription"/>
<module name="InvalidJavadocPosition"/>
<module name="JavadocTagContinuationIndentation"/>
<module name="SummaryJavadoc">
<property name="forbiddenSummaryFragments"
value="^@return the *|^This method returns |^A [{]@code [a-zA-Z0-9]+[}]( is a )"/>
</module>
<module name="JavadocParagraph"/>
<module name="AtclauseOrder">
<property name="tagOrder" value="@param, @return, @throws, @deprecated"/>
<property name="target"
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/>
</module>
<module name="JavadocMethod">
<property name="scope" value="public"/>
<property name="allowMissingParamTags" value="true"/>
<property name="allowMissingReturnTag" value="true"/>
<property name="allowedAnnotations" value="Override, Test"/>
<property name="tokens" value="METHOD_DEF, CTOR_DEF, ANNOTATION_FIELD_DEF"/>
</module>
<module name="MissingJavadocMethod">
<property name="scope" value="public"/>
<property name="minLineCount" value="2"/>
<property name="allowedAnnotations" value="Override, Test"/>
<property name="tokens" value="METHOD_DEF, CTOR_DEF, ANNOTATION_FIELD_DEF"/>
</module>
<module name="MethodName">
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9_]*$"/>
<message key="name.invalidPattern"
value="Method name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="SingleLineJavadoc">
<property name="ignoreInlineTags" value="false"/>
</module>
<module name="EmptyCatchBlock">
<property name="exceptionVariableName" value="expected"/>
</module>
<module name="CommentsIndentation">
<property name="tokens" value="SINGLE_LINE_COMMENT, BLOCK_COMMENT_BEGIN"/>
</module>
<!-- https://checkstyle.org/config_filters.html#SuppressionXpathFilter -->
<module name="SuppressionXpathFilter">
<property name="file" value="${org.checkstyle.google.suppressionxpathfilter.config}"
default="checkstyle-xpath-suppressions.xml" />
<property name="optional" value="true"/>
</module>
</module>
</module>

View file

@ -28,8 +28,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(proxyBeanMethods = false) @SpringBootApplication(proxyBeanMethods = false)
public class PetClinicApplication { public class PetClinicApplication {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(PetClinicApplication.class, args); SpringApplication.run(PetClinicApplication.class, args);
} }
} }

View file

@ -13,10 +13,10 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.samples.petclinic.model; package org.springframework.samples.petclinic.model;
import java.io.Serializable; import java.io.Serializable;
import javax.persistence.GeneratedValue; import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType; import javax.persistence.GenerationType;
import javax.persistence.Id; import javax.persistence.Id;
@ -32,20 +32,20 @@ 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;
public Integer getId() { public Integer getId() {
return id; return id;
} }
public void setId(Integer id) { public void setId(Integer id) {
this.id = id; this.id = id;
} }
public boolean isNew() { public boolean isNew() {
return this.id == null; return this.id == null;
} }
} }

View file

@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.samples.petclinic.model; package org.springframework.samples.petclinic.model;
import javax.persistence.Column; import javax.persistence.Column;
@ -28,20 +29,20 @@ import javax.persistence.MappedSuperclass;
@MappedSuperclass @MappedSuperclass
public class NamedEntity extends BaseEntity { public class NamedEntity extends BaseEntity {
@Column(name = "name") @Column(name = "name")
private String name; private String name;
public String getName() { public String getName() {
return this.name; return this.name;
} }
public void setName(String name) { public void setName(String name) {
this.name = name; this.name = name;
} }
@Override @Override
public String toString() { public String toString() {
return this.getName(); return this.getName();
} }
} }

View file

@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.samples.petclinic.model; package org.springframework.samples.petclinic.model;
import javax.persistence.Column; import javax.persistence.Column;
@ -27,28 +28,28 @@ import javax.validation.constraints.NotEmpty;
@MappedSuperclass @MappedSuperclass
public class Person extends BaseEntity { public class Person extends BaseEntity {
@Column(name = "first_name") @Column(name = "first_name")
@NotEmpty @NotEmpty
private String firstName; private String firstName;
@Column(name = "last_name") @Column(name = "last_name")
@NotEmpty @NotEmpty
private String lastName; private String lastName;
public String getFirstName() { public String getFirstName() {
return this.firstName; return this.firstName;
} }
public void setFirstName(String firstName) { public void setFirstName(String firstName) {
this.firstName = firstName; this.firstName = firstName;
} }
public String getLastName() { public String getLastName() {
return this.lastName; return this.lastName;
} }
public void setLastName(String lastName) { public void setLastName(String lastName) {
this.lastName = lastName; this.lastName = lastName;
} }
} }

View file

@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.samples.petclinic.owner; package org.springframework.samples.petclinic.owner;
import java.util.ArrayList; import java.util.ArrayList;
@ -20,7 +21,6 @@ import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import javax.persistence.CascadeType; import javax.persistence.CascadeType;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
@ -28,7 +28,6 @@ import javax.persistence.OneToMany;
import javax.persistence.Table; import javax.persistence.Table;
import javax.validation.constraints.Digits; import javax.validation.constraints.Digits;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
import org.springframework.beans.support.MutableSortDefinition; import org.springframework.beans.support.MutableSortDefinition;
import org.springframework.beans.support.PropertyComparator; import org.springframework.beans.support.PropertyComparator;
import org.springframework.core.style.ToStringCreator; import org.springframework.core.style.ToStringCreator;
@ -46,105 +45,118 @@ import org.springframework.samples.petclinic.model.Person;
@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;
@Column(name = "city") @Column(name = "city")
@NotEmpty @NotEmpty
private String city; private String city;
@Column(name = "telephone") @Column(name = "telephone")
@NotEmpty @NotEmpty
@Digits(fraction = 0, integer = 10) @Digits(fraction = 0, integer = 10)
private String telephone; private String telephone;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "owner") @OneToMany(cascade = CascadeType.ALL, mappedBy = "owner")
private Set<Pet> pets; private Set<Pet> pets;
public String getAddress() { public String getAddress() {
return this.address; return this.address;
} }
public void setAddress(String address) { public void setAddress(String address) {
this.address = address; this.address = address;
} }
public String getCity() { public String getCity() {
return this.city; return this.city;
} }
public void setCity(String city) { public void setCity(String city) {
this.city = city; this.city = city;
} }
public String getTelephone() { public String getTelephone() {
return this.telephone; return this.telephone;
} }
public void setTelephone(String telephone) { public void setTelephone(String telephone) {
this.telephone = telephone; this.telephone = telephone;
} }
protected Set<Pet> getPetsInternal() { protected Set<Pet> getPetsInternal() {
if (this.pets == null) { if (this.pets == null) {
this.pets = new HashSet<>(); this.pets = new HashSet<>();
} }
return this.pets; return this.pets;
} }
protected void setPetsInternal(Set<Pet> pets) { protected void setPetsInternal(Set<Pet> pets) {
this.pets = pets; this.pets = pets;
} }
public List<Pet> getPets() { /**
List<Pet> sortedPets = new ArrayList<>(getPetsInternal()); * list of pets.
PropertyComparator.sort(sortedPets, new MutableSortDefinition("name", true, true)); *
return Collections.unmodifiableList(sortedPets); * @return Returns list of pets.
} */
public List<Pet> getPets() {
List<Pet> sortedPets = new ArrayList<>(getPetsInternal());
PropertyComparator.sort(sortedPets, new MutableSortDefinition("name", true, true));
return Collections.unmodifiableList(sortedPets);
}
public void addPet(Pet pet) { /**
if (pet.isNew()) { * adds a pet.
getPetsInternal().add(pet); *
} * @param pet the pet to be added.
pet.setOwner(this); */
} public void addPet(Pet pet) {
if (pet.isNew()) {
getPetsInternal().add(pet);
}
pet.setOwner(this);
}
/** /**
* 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 *
* @return true if pet name is already in use * @param name to test
*/ * @return true if pet name is already in use
public Pet getPet(String name) { */
return getPet(name, false); public Pet getPet(String name) {
} return getPet(name, false);
}
/** /**
* 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 *
* @return true if pet name is already in use * @param name to test
*/ * @return true if pet name is already in use
public Pet getPet(String name, boolean ignoreNew) { */
name = name.toLowerCase(); public Pet getPet(String name, boolean ignoreNew) {
for (Pet pet : getPetsInternal()) { name = name.toLowerCase();
if (!ignoreNew || !pet.isNew()) { for (Pet pet : getPetsInternal()) {
String compName = pet.getName(); if (!ignoreNew || !pet.isNew()) {
compName = compName.toLowerCase(); String compName = pet.getName();
if (compName.equals(name)) { compName = compName.toLowerCase();
return pet; if (compName.equals(name)) {
} return pet;
} }
} }
return null; }
} return null;
}
@Override @Override
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

@ -13,8 +13,12 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.samples.petclinic.owner; package org.springframework.samples.petclinic.owner;
import java.util.Collection;
import java.util.Map;
import javax.validation.Valid;
import org.springframework.samples.petclinic.visit.VisitRepository; import org.springframework.samples.petclinic.visit.VisitRepository;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
@ -26,11 +30,9 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
import javax.validation.Valid;
import java.util.Collection;
import java.util.Map;
/** /**
* Owner controller.
*
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Ken Krebs * @author Ken Krebs
* @author Arjen Poutsma * @author Arjen Poutsma
@ -39,107 +41,105 @@ import java.util.Map;
@Controller @Controller
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 final OwnerRepository owners;
private VisitRepository visits; private final VisitRepository visits;
public OwnerController(OwnerRepository clinicService, VisitRepository visits) { public OwnerController(OwnerRepository clinicService, VisitRepository visits) {
this.owners = clinicService; this.owners = clinicService;
this.visits = visits; this.visits = visits;
} }
@InitBinder @InitBinder
public void setAllowedFields(WebDataBinder dataBinder) { public void setAllowedFields(WebDataBinder dataBinder) {
dataBinder.setDisallowedFields("id"); dataBinder.setDisallowedFields("id");
} }
@GetMapping("/owners/new") @GetMapping("/owners/new")
public String initCreationForm(Map<String, Object> model) { public String initCreationForm(Map<String, Object> model) {
Owner owner = new Owner(); Owner owner = new Owner();
model.put("owner", owner); model.put("owner", owner);
return VIEWS_OWNER_CREATE_OR_UPDATE_FORM; return VIEWS_OWNER_CREATE_OR_UPDATE_FORM;
} }
@PostMapping("/owners/new") @PostMapping("/owners/new")
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(); }
} }
}
@GetMapping("/owners/find") @GetMapping("/owners/find")
public String initFindForm(Map<String, Object> model) { public String initFindForm(Map<String, Object> model) {
model.put("owner", new Owner()); model.put("owner", new Owner());
return "owners/findOwners"; return "owners/findOwners";
} }
@GetMapping("/owners") @GetMapping("/owners")
public String processFindForm(Owner owner, BindingResult result, Map<String, Object> model) { public String processFindForm(Owner owner, BindingResult result, Map<String, Object> model) {
// allow parameterless GET request for /owners to return all records // allow parameterless GET request for /owners to return all records
if (owner.getLastName() == null) { if (owner.getLastName() == null) {
owner.setLastName(""); // empty string signifies broadest possible search owner.setLastName(""); // empty string signifies broadest possible search
} }
// find owners by last name // find owners by last name
Collection<Owner> results = this.owners.findByLastName(owner.getLastName()); Collection<Owner> results = this.owners.findByLastName(owner.getLastName());
if (results.isEmpty()) { if (results.isEmpty()) {
// 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 {
} // multiple owners found
else { model.put("selections", results);
// multiple owners found return "owners/ownersList";
model.put("selections", results); }
return "owners/ownersList"; }
}
}
@GetMapping("/owners/{ownerId}/edit") @GetMapping("/owners/{ownerId}/edit")
public String initUpdateOwnerForm(@PathVariable("ownerId") int ownerId, Model model) { public String initUpdateOwnerForm(@PathVariable("ownerId") int ownerId, Model model) {
Owner owner = this.owners.findById(ownerId); Owner owner = this.owners.findById(ownerId);
model.addAttribute(owner); model.addAttribute(owner);
return VIEWS_OWNER_CREATE_OR_UPDATE_FORM; return VIEWS_OWNER_CREATE_OR_UPDATE_FORM;
} }
@PostMapping("/owners/{ownerId}/edit") @PostMapping("/owners/{ownerId}/edit")
public String processUpdateOwnerForm(@Valid Owner owner, BindingResult result, public String processUpdateOwnerForm(@Valid Owner owner, BindingResult result,
@PathVariable("ownerId") int ownerId) { @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}"; }
} }
}
/** /**
* Custom handler for displaying an owner. * Custom handler for displaying an owner.
* @param ownerId the ID of the owner to display *
* @return a ModelMap with the model attributes for the view * @param ownerId the ID of the owner to display
*/ * @return a ModelMap with the model attributes for the view
@GetMapping("/owners/{ownerId}") */
public ModelAndView showOwner(@PathVariable("ownerId") int ownerId) { @GetMapping("/owners/{ownerId}")
ModelAndView mav = new ModelAndView("owners/ownerDetails"); public ModelAndView showOwner(@PathVariable("ownerId") int ownerId) {
Owner owner = this.owners.findById(ownerId); ModelAndView mav = new ModelAndView("owners/ownerDetails");
for (Pet pet : owner.getPets()) { Owner owner = this.owners.findById(ownerId);
pet.setVisitsInternal(visits.findByPetId(pet.getId())); for (Pet pet : owner.getPets()) {
} pet.setVisitsInternal(visits.findByPetId(pet.getId()));
mav.addObject(owner); }
return mav; mav.addObject(owner);
} return mav;
}
} }

View file

@ -13,10 +13,10 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.samples.petclinic.owner; package org.springframework.samples.petclinic.owner;
import java.util.Collection; import java.util.Collection;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.Repository; import org.springframework.data.repository.Repository;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
@ -35,30 +35,32 @@ import org.springframework.transaction.annotation.Transactional;
*/ */
public interface OwnerRepository extends Repository<Owner, Integer> { public interface OwnerRepository extends Repository<Owner, Integer> {
/** /**
* Retrieve {@link Owner}s from the data store by last name, returning all owners * Retrieve {@link Owner}s from the data store by last name, returning all owners
* whose last name <i>starts</i> with the given name. * whose last name <i>starts</i> with the given name.
* @param lastName Value to search for *
* @return a Collection of matching {@link Owner}s (or an empty Collection if none * @param lastName Value to search for
* found) * @return a Collection of matching {@link Owner}s (or an empty Collection if none found)
*/ */
@Query("SELECT DISTINCT owner FROM Owner owner left join fetch owner.pets WHERE owner.lastName LIKE :lastName%") @Query("SELECT DISTINCT owner FROM Owner owner left join fetch owner.pets WHERE owner.lastName LIKE :lastName%")
@Transactional(readOnly = true) @Transactional(readOnly = true)
Collection<Owner> findByLastName(@Param("lastName") String lastName); Collection<Owner> findByLastName(@Param("lastName") String lastName);
/** /**
* Retrieve an {@link Owner} from the data store by id. * Retrieve an {@link Owner} from the data store by id.
* @param id the id to search for *
* @return the {@link Owner} if found * @param id the id to search for
*/ * @return the {@link Owner} if found
@Query("SELECT owner FROM Owner owner left join fetch owner.pets WHERE owner.id =:id") */
@Transactional(readOnly = true) @Query("SELECT owner FROM Owner owner left join fetch owner.pets WHERE owner.id =:id")
Owner findById(@Param("id") Integer id); @Transactional(readOnly = true)
Owner findById(@Param("id") Integer id);
/** /**
* Save an {@link Owner} to the data store, either inserting or updating it. * Save an {@link Owner} to the data store, either inserting or updating it.
* @param owner the {@link Owner} to save *
*/ * @param owner the {@link Owner} to save
void save(Owner owner); */
void save(Owner owner);
} }

View file

@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.samples.petclinic.owner; package org.springframework.samples.petclinic.owner;
import java.time.LocalDate; import java.time.LocalDate;
@ -23,14 +24,12 @@ import java.util.HashSet;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.JoinColumn; import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne; import javax.persistence.ManyToOne;
import javax.persistence.Table; import javax.persistence.Table;
import javax.persistence.Transient; import javax.persistence.Transient;
import org.springframework.beans.support.MutableSortDefinition; import org.springframework.beans.support.MutableSortDefinition;
import org.springframework.beans.support.PropertyComparator; import org.springframework.beans.support.PropertyComparator;
import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat;
@ -48,65 +47,75 @@ import org.springframework.samples.petclinic.visit.Visit;
@Table(name = "pets") @Table(name = "pets")
public class Pet extends NamedEntity { public class Pet extends NamedEntity {
@Column(name = "birth_date") @Column(name = "birth_date")
@DateTimeFormat(pattern = "yyyy-MM-dd") @DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate birthDate; private LocalDate birthDate;
@ManyToOne @ManyToOne
@JoinColumn(name = "type_id") @JoinColumn(name = "type_id")
private PetType type; private PetType type;
@ManyToOne @ManyToOne
@JoinColumn(name = "owner_id") @JoinColumn(name = "owner_id")
private Owner owner; private Owner owner;
@Transient @Transient
private Set<Visit> visits = new LinkedHashSet<>(); private Set<Visit> visits = new LinkedHashSet<>();
public void setBirthDate(LocalDate birthDate) { public void setBirthDate(LocalDate birthDate) {
this.birthDate = birthDate; this.birthDate = birthDate;
} }
public LocalDate getBirthDate() { public LocalDate getBirthDate() {
return this.birthDate; return this.birthDate;
} }
public PetType getType() { public PetType getType() {
return this.type; return this.type;
} }
public void setType(PetType type) { public void setType(PetType type) {
this.type = type; this.type = type;
} }
public Owner getOwner() { public Owner getOwner() {
return this.owner; return this.owner;
} }
protected void setOwner(Owner owner) { protected void setOwner(Owner owner) {
this.owner = owner; this.owner = owner;
} }
protected Set<Visit> getVisitsInternal() { protected Set<Visit> getVisitsInternal() {
if (this.visits == null) { if (this.visits == null) {
this.visits = new HashSet<>(); this.visits = new HashSet<>();
} }
return this.visits; return this.visits;
} }
protected void setVisitsInternal(Collection<Visit> visits) { protected void setVisitsInternal(Collection<Visit> visits) {
this.visits = new LinkedHashSet<>(visits); this.visits = new LinkedHashSet<>(visits);
} }
public List<Visit> getVisits() { /**
List<Visit> sortedVisits = new ArrayList<>(getVisitsInternal()); * list of visits.
PropertyComparator.sort(sortedVisits, new MutableSortDefinition("date", false, false)); *
return Collections.unmodifiableList(sortedVisits); * @return list of visits
} */
public List<Visit> getVisits() {
List<Visit> sortedVisits = new ArrayList<>(getVisitsInternal());
PropertyComparator.sort(sortedVisits, new MutableSortDefinition("date", false, false));
return Collections.unmodifiableList(sortedVisits);
}
public void addVisit(Visit visit) { /**
getVisitsInternal().add(visit); * add a visit to list.
visit.setPetId(this.getId()); *
} * @param visit the visit to add.
*/
public void addVisit(Visit visit) {
getVisitsInternal().add(visit);
visit.setPetId(this.getId());
}
} }

View file

@ -13,19 +13,26 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.samples.petclinic.owner; package org.springframework.samples.petclinic.owner;
import java.util.Collection;
import javax.validation.Valid;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap; import org.springframework.ui.ModelMap;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult; import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.InitBinder;
import javax.validation.Valid; import org.springframework.web.bind.annotation.ModelAttribute;
import java.util.Collection; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
/** /**
* Pet controller.
*
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Ken Krebs * @author Ken Krebs
* @author Arjen Poutsma * @author Arjen Poutsma
@ -34,80 +41,78 @@ import java.util.Collection;
@RequestMapping("/owners/{ownerId}") @RequestMapping("/owners/{ownerId}")
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) {
this.pets = pets; this.pets = pets;
this.owners = owners; this.owners = owners;
} }
@ModelAttribute("types") @ModelAttribute("types")
public Collection<PetType> populatePetTypes() { public Collection<PetType> populatePetTypes() {
return this.pets.findPetTypes(); return this.pets.findPetTypes();
} }
@ModelAttribute("owner") @ModelAttribute("owner")
public Owner findOwner(@PathVariable("ownerId") int ownerId) { public Owner findOwner(@PathVariable("ownerId") int ownerId) {
return this.owners.findById(ownerId); return this.owners.findById(ownerId);
} }
@InitBinder("owner") @InitBinder("owner")
public void initOwnerBinder(WebDataBinder dataBinder) { public void initOwnerBinder(WebDataBinder dataBinder) {
dataBinder.setDisallowedFields("id"); dataBinder.setDisallowedFields("id");
} }
@InitBinder("pet") @InitBinder("pet")
public void initPetBinder(WebDataBinder dataBinder) { public void initPetBinder(WebDataBinder dataBinder) {
dataBinder.setValidator(new PetValidator()); dataBinder.setValidator(new PetValidator());
} }
@GetMapping("/pets/new") @GetMapping("/pets/new")
public String initCreationForm(Owner owner, ModelMap model) { public String initCreationForm(Owner owner, ModelMap model) {
Pet pet = new Pet(); Pet pet = new Pet();
owner.addPet(pet); owner.addPet(pet);
model.put("pet", pet); model.put("pet", pet);
return VIEWS_PETS_CREATE_OR_UPDATE_FORM; return VIEWS_PETS_CREATE_OR_UPDATE_FORM;
} }
@PostMapping("/pets/new") @PostMapping("/pets/new")
public String processCreationForm(Owner owner, @Valid Pet pet, BindingResult result, ModelMap model) { public String processCreationForm(Owner owner, @Valid Pet pet, BindingResult result, ModelMap model) {
if (StringUtils.hasLength(pet.getName()) && pet.isNew() && owner.getPet(pet.getName(), true) != null) { if (StringUtils.hasLength(pet.getName()) && pet.isNew() && owner.getPet(pet.getName(), true) != null) {
result.rejectValue("name", "duplicate", "already exists"); result.rejectValue("name", "duplicate", "already exists");
} }
owner.addPet(pet); owner.addPet(pet);
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}"; }
} }
}
@GetMapping("/pets/{petId}/edit") @GetMapping("/pets/{petId}/edit")
public String initUpdateForm(@PathVariable("petId") int petId, ModelMap model) { public String initUpdateForm(@PathVariable("petId") int petId, ModelMap model) {
Pet pet = this.pets.findById(petId); Pet pet = this.pets.findById(petId);
model.put("pet", pet); model.put("pet", pet);
return VIEWS_PETS_CREATE_OR_UPDATE_FORM; return VIEWS_PETS_CREATE_OR_UPDATE_FORM;
} }
@PostMapping("/pets/{petId}/edit") @PostMapping("/pets/{petId}/edit")
public String processUpdateForm(@Valid Pet pet, BindingResult result, Owner owner, ModelMap model) { public String processUpdateForm(@Valid Pet pet, BindingResult result, Owner owner, ModelMap model) {
if (result.hasErrors()) { if (result.hasErrors()) {
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

@ -13,10 +13,10 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.samples.petclinic.owner; package org.springframework.samples.petclinic.owner;
import java.util.List; import java.util.List;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.Repository; import org.springframework.data.repository.Repository;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -34,26 +34,29 @@ import org.springframework.transaction.annotation.Transactional;
*/ */
public interface PetRepository extends Repository<Pet, Integer> { public interface PetRepository extends Repository<Pet, Integer> {
/** /**
* Retrieve all {@link PetType}s from the data store. * Retrieve all {@link PetType}s from the data store.
* @return a Collection of {@link PetType}s. *
*/ * @return a Collection of {@link PetType}s.
@Query("SELECT ptype FROM PetType ptype ORDER BY ptype.name") */
@Transactional(readOnly = true) @Query("SELECT ptype FROM PetType ptype ORDER BY ptype.name")
List<PetType> findPetTypes(); @Transactional(readOnly = true)
List<PetType> findPetTypes();
/** /**
* Retrieve a {@link Pet} from the data store by id. * Retrieve a {@link Pet} from the data store by id.
* @param id the id to search for *
* @return the {@link Pet} if found * @param id the id to search for
*/ * @return the {@link Pet} if found
@Transactional(readOnly = true) */
Pet findById(Integer id); @Transactional(readOnly = true)
Pet findById(Integer id);
/** /**
* Save a {@link Pet} to the data store, either inserting or updating it. * Save a {@link Pet} to the data store, either inserting or updating it.
* @param pet the {@link Pet} to save *
*/ * @param pet the {@link Pet} to save
void save(Pet pet); */
void save(Pet pet);
} }

View file

@ -13,14 +13,16 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.samples.petclinic.owner; package org.springframework.samples.petclinic.owner;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.Table; import javax.persistence.Table;
import org.springframework.samples.petclinic.model.NamedEntity; import org.springframework.samples.petclinic.model.NamedEntity;
/** /**
* Pet type.
*
* @author Juergen Hoeller Can be Cat, Dog, Hamster... * @author Juergen Hoeller Can be Cat, Dog, Hamster...
*/ */
@Entity @Entity

View file

@ -13,12 +13,12 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.samples.petclinic.owner; package org.springframework.samples.petclinic.owner;
import java.text.ParseException; import java.text.ParseException;
import java.util.Collection; import java.util.Collection;
import java.util.Locale; import java.util.Locale;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.Formatter; import org.springframework.format.Formatter;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -36,27 +36,27 @@ import org.springframework.stereotype.Component;
@Component @Component
public class PetTypeFormatter implements Formatter<PetType> { 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;
} }
@Override @Override
public String print(PetType petType, Locale locale) { public String print(PetType petType, Locale locale) {
return petType.getName(); return petType.getName();
} }
@Override @Override
public PetType parse(String text, Locale locale) throws ParseException { public PetType parse(String text, Locale locale) throws ParseException {
Collection<PetType> findPetTypes = this.pets.findPetTypes(); Collection<PetType> findPetTypes = this.pets.findPetTypes();
for (PetType type : findPetTypes) { for (PetType type : findPetTypes) {
if (type.getName().equals(text)) { if (type.getName().equals(text)) {
return type; return type;
} }
} }
throw new ParseException("type not found: " + text, 0); throw new ParseException("type not found: " + text, 0);
} }
} }

View file

@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.samples.petclinic.owner; package org.springframework.samples.petclinic.owner;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -31,34 +32,34 @@ import org.springframework.validation.Validator;
*/ */
public class PetValidator implements Validator { public class PetValidator implements Validator {
private static final String REQUIRED = "required"; private static final String REQUIRED = "required";
@Override @Override
public void validate(Object obj, Errors errors) { public void validate(Object obj, Errors errors) {
Pet pet = (Pet) obj; Pet pet = (Pet) obj;
String name = pet.getName(); String name = pet.getName();
// name validation // name validation
if (!StringUtils.hasLength(name)) { if (!StringUtils.hasLength(name)) {
errors.rejectValue("name", REQUIRED, REQUIRED); errors.rejectValue("name", REQUIRED, REQUIRED);
} }
// type validation // type validation
if (pet.isNew() && pet.getType() == null) { if (pet.isNew() && pet.getType() == null) {
errors.rejectValue("type", REQUIRED, REQUIRED); errors.rejectValue("type", REQUIRED, REQUIRED);
} }
// birth date validation // birth date validation
if (pet.getBirthDate() == null) { if (pet.getBirthDate() == null) {
errors.rejectValue("birthDate", REQUIRED, REQUIRED); errors.rejectValue("birthDate", REQUIRED, REQUIRED);
} }
} }
/** /**
* This Validator validates *just* Pet instances * This Validator validates *just* Pet instances.
*/ */
@Override @Override
public boolean supports(Class<?> clazz) { public boolean supports(Class<?> clazz) {
return Pet.class.isAssignableFrom(clazz); return Pet.class.isAssignableFrom(clazz);
} }
} }

View file

@ -13,12 +13,11 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.samples.petclinic.owner; package org.springframework.samples.petclinic.owner;
import java.util.Map; import java.util.Map;
import javax.validation.Valid; import javax.validation.Valid;
import org.springframework.samples.petclinic.visit.Visit; import org.springframework.samples.petclinic.visit.Visit;
import org.springframework.samples.petclinic.visit.VisitRepository; import org.springframework.samples.petclinic.visit.VisitRepository;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
@ -31,6 +30,8 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
/** /**
* Visit controller.
*
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Ken Krebs * @author Ken Krebs
* @author Arjen Poutsma * @author Arjen Poutsma
@ -40,53 +41,53 @@ import org.springframework.web.bind.annotation.PostMapping;
@Controller @Controller
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;
this.pets = pets; this.pets = pets;
} }
@InitBinder @InitBinder
public void setAllowedFields(WebDataBinder dataBinder) { public void setAllowedFields(WebDataBinder dataBinder) {
dataBinder.setDisallowedFields("id"); dataBinder.setDisallowedFields("id");
} }
/** /**
* Called before each and every @RequestMapping annotated method. 2 goals: - Make sure * Called before each and every @RequestMapping annotated method. 2 goals: - Make sure
* we always have fresh data - Since we do not use the session scope, make sure that * we always have fresh data - 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) * Pet object always has an id (Even though id is not part of the form fields)
* @param petId *
* @return Pet * @param petId the pete id
*/ * @return Pet the visit.
@ModelAttribute("visit") */
public Visit loadPetWithVisit(@PathVariable("petId") int petId, Map<String, Object> model) { @ModelAttribute("visit")
Pet pet = this.pets.findById(petId); public Visit loadPetWithVisit(@PathVariable("petId") int petId, Map<String, Object> model) {
pet.setVisitsInternal(this.visits.findByPetId(petId)); Pet pet = this.pets.findById(petId);
model.put("pet", pet); pet.setVisitsInternal(this.visits.findByPetId(petId));
Visit visit = new Visit(); model.put("pet", pet);
pet.addVisit(visit); Visit visit = new Visit();
return visit; pet.addVisit(visit);
} return visit;
}
// Spring MVC calls method loadPetWithVisit(...) before initNewVisitForm is called // Spring MVC calls method loadPetWithVisit(...) before initNewVisitForm is called
@GetMapping("/owners/*/pets/{petId}/visits/new") @GetMapping("/owners/*/pets/{petId}/visits/new")
public String initNewVisitForm(@PathVariable("petId") int petId, Map<String, Object> model) { public String initNewVisitForm(@PathVariable("petId") int petId, Map<String, Object> model) {
return "pets/createOrUpdateVisitForm"; return "pets/createOrUpdateVisitForm";
} }
// Spring MVC calls method loadPetWithVisit(...) before processNewVisitForm is called // Spring MVC calls method loadPetWithVisit(...) before processNewVisitForm is called
@PostMapping("/owners/{ownerId}/pets/{petId}/visits/new") @PostMapping("/owners/{ownerId}/pets/{petId}/visits/new")
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

@ -17,7 +17,6 @@
package org.springframework.samples.petclinic.system; package org.springframework.samples.petclinic.system;
import javax.cache.configuration.MutableConfiguration; import javax.cache.configuration.MutableConfiguration;
import org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer; import org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer;
import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -32,24 +31,24 @@ import org.springframework.context.annotation.Configuration;
@EnableCaching @EnableCaching
class CacheConfiguration { class CacheConfiguration {
@Bean @Bean
public JCacheManagerCustomizer petclinicCacheConfigurationCustomizer() { public JCacheManagerCustomizer petclinicCacheConfigurationCustomizer() {
return cm -> { return cm -> {
cm.createCache("vets", cacheConfiguration()); cm.createCache("vets", cacheConfiguration());
}; };
} }
/** /**
* Create a simple configuration that enable statistics via the JCache programmatic * Create a simple configuration that enable statistics via the JCache programmatic
* configuration API. * configuration API.
* <p> *
* Within the configuration object that is provided by the JCache API standard, there * <p>Within the configuration object that is provided by the JCache API standard, there
* is only a very limited set of configuration options. The really relevant * is only a very limited set of configuration options. The really relevant
* configuration options (like the size limit) must be set via a configuration * configuration options (like the size limit) must be set via a configuration
* mechanism that is provided by the selected JCache implementation. * mechanism that is provided by the selected JCache implementation.
*/ */
private javax.cache.configuration.Configuration<Object, Object> cacheConfiguration() { private javax.cache.configuration.Configuration<Object, Object> cacheConfiguration() {
return new MutableConfiguration<>().setStatisticsEnabled(true); return new MutableConfiguration<>().setStatisticsEnabled(true);
} }
} }

View file

@ -13,25 +13,26 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
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;
/** /**
* Controller used to showcase what happens when an exception is thrown * Controller used to showcase what happens when an exception is thrown.
* *
* @author Michael Isvy * @author Michael Isvy
* <p/> *
* Also see how a view that resolves to "error" has been added ("error.html"). * <p/>Also see how a view that resolves to "error" has been added ("error.html").
*/ */
@Controller @Controller
class CrashController { class CrashController {
@GetMapping("/oups") @GetMapping("/oups")
public String triggerException() { public String triggerException() {
throw new RuntimeException( throw new RuntimeException(
"Expected: controller used to showcase what " + "happens when an exception is thrown"); "Expected: controller used to showcase what " + "happens when an exception is thrown");
} }
} }

View file

@ -22,9 +22,9 @@ import org.springframework.web.bind.annotation.GetMapping;
@Controller @Controller
class WelcomeController { class WelcomeController {
@GetMapping("/") @GetMapping("/")
public String welcome() { public String welcome() {
return "welcome"; return "welcome";
} }
} }

View file

@ -13,13 +13,12 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.samples.petclinic.vet; package org.springframework.samples.petclinic.vet;
import java.io.Serializable; import java.io.Serializable;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.Table; import javax.persistence.Table;
import org.springframework.samples.petclinic.model.NamedEntity; import org.springframework.samples.petclinic.model.NamedEntity;
/** /**

View file

@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.samples.petclinic.vet; package org.springframework.samples.petclinic.vet;
import java.util.ArrayList; import java.util.ArrayList;
@ -20,7 +21,6 @@ import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.FetchType; import javax.persistence.FetchType;
import javax.persistence.JoinColumn; import javax.persistence.JoinColumn;
@ -28,7 +28,6 @@ import javax.persistence.JoinTable;
import javax.persistence.ManyToMany; import javax.persistence.ManyToMany;
import javax.persistence.Table; import javax.persistence.Table;
import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElement;
import org.springframework.beans.support.MutableSortDefinition; import org.springframework.beans.support.MutableSortDefinition;
import org.springframework.beans.support.PropertyComparator; import org.springframework.beans.support.PropertyComparator;
import org.springframework.samples.petclinic.model.Person; import org.springframework.samples.petclinic.model.Person;
@ -45,35 +44,44 @@ import org.springframework.samples.petclinic.model.Person;
@Table(name = "vets") @Table(name = "vets")
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"), @JoinTable(name = "vet_specialties", joinColumns = @JoinColumn(name = "vet_id"),
inverseJoinColumns = @JoinColumn(name = "specialty_id")) inverseJoinColumns = @JoinColumn(name = "specialty_id"))
private Set<Specialty> specialties; private Set<Specialty> specialties;
protected Set<Specialty> getSpecialtiesInternal() { protected Set<Specialty> getSpecialtiesInternal() {
if (this.specialties == null) { if (this.specialties == null) {
this.specialties = new HashSet<>(); this.specialties = new HashSet<>();
} }
return this.specialties; return this.specialties;
} }
protected void setSpecialtiesInternal(Set<Specialty> specialties) { protected void setSpecialtiesInternal(Set<Specialty> specialties) {
this.specialties = specialties; this.specialties = specialties;
} }
@XmlElement /**
public List<Specialty> getSpecialties() { * gets list of specialties.
List<Specialty> sortedSpecs = new ArrayList<>(getSpecialtiesInternal()); */
PropertyComparator.sort(sortedSpecs, new MutableSortDefinition("name", true, true)); @XmlElement
return Collections.unmodifiableList(sortedSpecs); public List<Specialty> getSpecialties() {
} List<Specialty> sortedSpecs = new ArrayList<>(getSpecialtiesInternal());
PropertyComparator.sort(sortedSpecs, new MutableSortDefinition("name", true, true));
return Collections.unmodifiableList(sortedSpecs);
}
public int getNrOfSpecialties() { /**
return getSpecialtiesInternal().size(); * number of specialties.
} */
public int getNrOfSpecialties() {
return getSpecialtiesInternal().size();
}
public void addSpecialty(Specialty specialty) { /**
getSpecialtiesInternal().add(specialty); * Add a specialty.
} */
public void addSpecialty(Specialty specialty) {
getSpecialtiesInternal().add(specialty);
}
} }

View file

@ -13,15 +13,17 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.samples.petclinic.vet; package org.springframework.samples.petclinic.vet;
import java.util.Map;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Map;
/** /**
* Vet Controller.
*
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Mark Fisher * @author Mark Fisher
* @author Ken Krebs * @author Ken Krebs
@ -30,29 +32,29 @@ import java.util.Map;
@Controller @Controller
class VetController { class VetController {
private final VetRepository vets; private final VetRepository vets;
public VetController(VetRepository clinicService) { public VetController(VetRepository clinicService) {
this.vets = clinicService; this.vets = clinicService;
} }
@GetMapping("/vets.html") @GetMapping("/vets.html")
public String showVetList(Map<String, Object> model) { public String showVetList(Map<String, Object> model) {
// Here we are returning an object of type 'Vets' rather than a collection of Vet // Here we are returning an object of type 'Vets' rather than a collection of Vet
// objects so it is simpler for Object-Xml mapping // objects so it is simpler for Object-Xml mapping
Vets vets = new Vets(); Vets vets = new Vets();
vets.getVetList().addAll(this.vets.findAll()); vets.getVetList().addAll(this.vets.findAll());
model.put("vets", vets); model.put("vets", vets);
return "vets/vetList"; return "vets/vetList";
} }
@GetMapping({ "/vets" }) @GetMapping({"/vets"})
public @ResponseBody Vets showResourcesVetList() { public @ResponseBody Vets showResourcesVetList() {
// Here we are returning an object of type 'Vets' rather than a collection of Vet // Here we are returning an object of type 'Vets' rather than a collection of Vet
// objects so it is simpler for JSon/Object mapping // objects so it is simpler for JSon/Object mapping
Vets vets = new Vets(); Vets vets = new Vets();
vets.getVetList().addAll(this.vets.findAll()); vets.getVetList().addAll(this.vets.findAll());
return vets; return vets;
} }
} }

View file

@ -13,10 +13,10 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.samples.petclinic.vet; package org.springframework.samples.petclinic.vet;
import java.util.Collection; import java.util.Collection;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessException;
import org.springframework.data.repository.Repository; import org.springframework.data.repository.Repository;
@ -35,12 +35,13 @@ import org.springframework.transaction.annotation.Transactional;
*/ */
public interface VetRepository extends Repository<Vet, Integer> { 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) */
@Cacheable("vets") @Transactional(readOnly = true)
Collection<Vet> findAll() throws DataAccessException; @Cacheable("vets")
Collection<Vet> findAll() throws DataAccessException;
} }

View file

@ -13,11 +13,11 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.samples.petclinic.vet; package org.springframework.samples.petclinic.vet;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlRootElement;
@ -30,14 +30,16 @@ import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement @XmlRootElement
public class Vets { public class Vets {
private List<Vet> vets; private List<Vet> vets;
@XmlElement /**
public List<Vet> getVetList() { * returns all Vets.
if (vets == null) { */
vets = new ArrayList<>(); @XmlElement public List<Vet> getVetList() {
} if (vets == null) {
return vets; vets = new ArrayList<>();
} }
return vets;
}
} }

View file

@ -13,15 +13,14 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.samples.petclinic.visit; package org.springframework.samples.petclinic.visit;
import java.time.LocalDate; import java.time.LocalDate;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.Table; import javax.persistence.Table;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.samples.petclinic.model.BaseEntity; import org.springframework.samples.petclinic.model.BaseEntity;
@ -35,46 +34,46 @@ import org.springframework.samples.petclinic.model.BaseEntity;
@Table(name = "visits") @Table(name = "visits")
public class Visit extends BaseEntity { public class Visit extends BaseEntity {
@Column(name = "visit_date") @Column(name = "visit_date")
@DateTimeFormat(pattern = "yyyy-MM-dd") @DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate date; private LocalDate date;
@NotEmpty @NotEmpty
@Column(name = "description") @Column(name = "description")
private String description; private String description;
@Column(name = "pet_id") @Column(name = "pet_id")
private Integer petId; private Integer petId;
/** /**
* Creates a new instance of Visit for the current date * Creates a new instance of Visit for the current date.
*/ */
public Visit() { public Visit() {
this.date = LocalDate.now(); this.date = LocalDate.now();
} }
public LocalDate getDate() { public LocalDate getDate() {
return this.date; return this.date;
} }
public void setDate(LocalDate date) { public void setDate(LocalDate date) {
this.date = date; this.date = date;
} }
public String getDescription() { public String getDescription() {
return this.description; return this.description;
} }
public void setDescription(String description) { public void setDescription(String description) {
this.description = description; this.description = description;
} }
public Integer getPetId() { public Integer getPetId() {
return this.petId; return this.petId;
} }
public void setPetId(Integer petId) { public void setPetId(Integer petId) {
this.petId = petId; this.petId = petId;
} }
} }

View file

@ -13,10 +13,10 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.samples.petclinic.visit; package org.springframework.samples.petclinic.visit;
import java.util.List; import java.util.List;
import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessException;
import org.springframework.data.repository.Repository; import org.springframework.data.repository.Repository;
import org.springframework.samples.petclinic.model.BaseEntity; import org.springframework.samples.petclinic.model.BaseEntity;
@ -34,13 +34,14 @@ import org.springframework.samples.petclinic.model.BaseEntity;
*/ */
public interface VisitRepository extends Repository<Visit, Integer> { 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 *
* @see BaseEntity#isNew * @param visit the <code>Visit</code> to save
*/ * @see BaseEntity#isNew
void save(Visit visit) throws DataAccessException; */
void save(Visit visit) throws DataAccessException;
List<Visit> findByPetId(Integer petId); List<Visit> findByPetId(Integer petId);
} }

View file

@ -24,13 +24,13 @@ import org.springframework.samples.petclinic.vet.VetRepository;
@SpringBootTest @SpringBootTest
class PetclinicIntegrationTests { class PetclinicIntegrationTests {
@Autowired @Autowired
private VetRepository vets; private VetRepository vets;
@Test @Test
void testFindAll() throws Exception { void testFindAll() throws Exception {
vets.findAll(); vets.findAll();
vets.findAll(); // served from cache vets.findAll(); // served from cache
} }
} }

View file

@ -16,45 +16,43 @@
package org.springframework.samples.petclinic.model; package org.springframework.samples.petclinic.model;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Locale; import java.util.Locale;
import java.util.Set; import java.util.Set;
import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolation;
import javax.validation.Validator; import javax.validation.Validator;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import static org.assertj.core.api.Assertions.assertThat;
/** /**
* @author Michael Isvy Simple test to make sure that Bean Validation is working (useful * @author Michael Isvy Simple test to make sure that Bean Validation is working (useful
* when upgrading to a new version of Hibernate Validator/ Bean Validation) * when upgrading to a new version of Hibernate Validator/ Bean Validation)
*/ */
class ValidatorTests { class ValidatorTests {
private Validator createValidator() { private Validator createValidator() {
LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean(); LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();
localValidatorFactoryBean.afterPropertiesSet(); localValidatorFactoryBean.afterPropertiesSet();
return localValidatorFactoryBean; return localValidatorFactoryBean;
} }
@Test @Test
void shouldNotValidateWhenFirstNameEmpty() { void shouldNotValidateWhenFirstNameEmpty() {
LocaleContextHolder.setLocale(Locale.ENGLISH); LocaleContextHolder.setLocale(Locale.ENGLISH);
Person person = new Person(); Person person = new Person();
person.setFirstName(""); person.setFirstName("");
person.setLastName("smith"); person.setLastName("smith");
Validator validator = createValidator(); Validator validator = createValidator();
Set<ConstraintViolation<Person>> constraintViolations = validator.validate(person); Set<ConstraintViolation<Person>> constraintViolations = validator.validate(person);
assertThat(constraintViolations).hasSize(1); assertThat(constraintViolations).hasSize(1);
ConstraintViolation<Person> violation = constraintViolations.iterator().next(); ConstraintViolation<Person> violation = constraintViolations.iterator().next();
assertThat(violation.getPropertyPath().toString()).isEqualTo("firstName"); assertThat(violation.getPropertyPath().toString()).isEqualTo("firstName");
assertThat(violation.getMessage()).isEqualTo("must not be empty"); assertThat(violation.getMessage()).isEqualTo("must not be empty");
} }
} }

View file

@ -51,149 +51,149 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
@WebMvcTest(OwnerController.class) @WebMvcTest(OwnerController.class)
class OwnerControllerTests { class OwnerControllerTests {
private static final int TEST_OWNER_ID = 1; private static final int TEST_OWNER_ID = 1;
@Autowired @Autowired
private MockMvc mockMvc; private MockMvc mockMvc;
@MockBean @MockBean
private OwnerRepository owners; private OwnerRepository owners;
@MockBean @MockBean
private VisitRepository visits; private VisitRepository visits;
private Owner george; private Owner george;
@BeforeEach @BeforeEach
void setup() { void setup() {
george = new Owner(); george = new Owner();
george.setId(TEST_OWNER_ID); george.setId(TEST_OWNER_ID);
george.setFirstName("George"); george.setFirstName("George");
george.setLastName("Franklin"); george.setLastName("Franklin");
george.setAddress("110 W. Liberty St."); george.setAddress("110 W. Liberty St.");
george.setCity("Madison"); george.setCity("Madison");
george.setTelephone("6085551023"); george.setTelephone("6085551023");
Pet max = new Pet(); Pet max = new Pet();
PetType dog = new PetType(); PetType dog = new PetType();
dog.setName("dog"); dog.setName("dog");
max.setId(1); max.setId(1);
max.setType(dog); max.setType(dog);
max.setName("Max"); max.setName("Max");
max.setBirthDate(LocalDate.now()); max.setBirthDate(LocalDate.now());
george.setPetsInternal(Collections.singleton(max)); george.setPetsInternal(Collections.singleton(max));
given(this.owners.findById(TEST_OWNER_ID)).willReturn(george); given(this.owners.findById(TEST_OWNER_ID)).willReturn(george);
Visit visit = new Visit(); Visit visit = new Visit();
visit.setDate(LocalDate.now()); visit.setDate(LocalDate.now());
given(this.visits.findByPetId(max.getId())).willReturn(Collections.singletonList(visit)); given(this.visits.findByPetId(max.getId())).willReturn(Collections.singletonList(visit));
} }
@Test @Test
void testInitCreationForm() throws Exception { void testInitCreationForm() throws Exception {
mockMvc.perform(get("/owners/new")).andExpect(status().isOk()).andExpect(model().attributeExists("owner")) mockMvc.perform(get("/owners/new")).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").param("firstName", "Joe").param("lastName", "Bloggs") mockMvc.perform(post("/owners/new").param("firstName", "Joe").param("lastName", "Bloggs")
.param("address", "123 Caramel Street").param("city", "London").param("telephone", "01316761638")) .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( mockMvc.perform(
post("/owners/new").param("firstName", "Joe").param("lastName", "Bloggs").param("city", "London")) post("/owners/new").param("firstName", "Joe").param("lastName", "Bloggs").param("city", "London"))
.andExpect(status().isOk()).andExpect(model().attributeHasErrors("owner")) .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"));
} }
@Test @Test
void testInitFindForm() throws Exception { void testInitFindForm() throws Exception {
mockMvc.perform(get("/owners/find")).andExpect(status().isOk()).andExpect(model().attributeExists("owner")) mockMvc.perform(get("/owners/find")).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")).andExpect(status().isOk()).andExpect(view().name("owners/ownersList")); mockMvc.perform(get("/owners")).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").param("lastName", "Franklin")).andExpect(status().is3xxRedirection()) mockMvc.perform(get("/owners").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").param("lastName", "Unknown Surname")).andExpect(status().isOk()) mockMvc.perform(get("/owners").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"));
} }
@Test @Test
void testInitUpdateOwnerForm() throws Exception { void testInitUpdateOwnerForm() throws Exception {
mockMvc.perform(get("/owners/{ownerId}/edit", TEST_OWNER_ID)).andExpect(status().isOk()) mockMvc.perform(get("/owners/{ownerId}/edit", TEST_OWNER_ID)).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"))))
.andExpect(model().attribute("owner", hasProperty("address", is("110 W. Liberty St.")))) .andExpect(model().attribute("owner", hasProperty("address", is("110 W. Liberty St."))))
.andExpect(model().attribute("owner", hasProperty("city", is("Madison")))) .andExpect(model().attribute("owner", hasProperty("city", is("Madison"))))
.andExpect(model().attribute("owner", hasProperty("telephone", is("6085551023")))) .andExpect(model().attribute("owner", hasProperty("telephone", is("6085551023"))))
.andExpect(view().name("owners/createOrUpdateOwnerForm")); .andExpect(view().name("owners/createOrUpdateOwnerForm"));
} }
@Test @Test
void testProcessUpdateOwnerFormSuccess() throws Exception { void testProcessUpdateOwnerFormSuccess() throws Exception {
mockMvc.perform(post("/owners/{ownerId}/edit", TEST_OWNER_ID).param("firstName", "Joe") mockMvc.perform(post("/owners/{ownerId}/edit", TEST_OWNER_ID).param("firstName", "Joe")
.param("lastName", "Bloggs").param("address", "123 Caramel Street").param("city", "London") .param("lastName", "Bloggs").param("address", "123 Caramel Street").param("city", "London")
.param("telephone", "01616291589")).andExpect(status().is3xxRedirection()) .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).param("firstName", "Joe") mockMvc.perform(post("/owners/{ownerId}/edit", TEST_OWNER_ID).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"))
.andExpect(view().name("owners/createOrUpdateOwnerForm")); .andExpect(view().name("owners/createOrUpdateOwnerForm"));
} }
@Test @Test
void testShowOwner() throws Exception { void testShowOwner() throws Exception {
mockMvc.perform(get("/owners/{ownerId}", TEST_OWNER_ID)).andExpect(status().isOk()) mockMvc.perform(get("/owners/{ownerId}", TEST_OWNER_ID)).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."))))
.andExpect(model().attribute("owner", hasProperty("city", is("Madison")))) .andExpect(model().attribute("owner", hasProperty("city", is("Madison"))))
.andExpect(model().attribute("owner", hasProperty("telephone", is("6085551023")))) .andExpect(model().attribute("owner", hasProperty("telephone", is("6085551023"))))
.andExpect(model().attribute("owner", hasProperty("pets", not(empty())))) .andExpect(model().attribute("owner", hasProperty("pets", not(empty()))))
.andExpect(model().attribute("owner", hasProperty("pets", new BaseMatcher<List<Pet>>() { .andExpect(model().attribute("owner", hasProperty("pets", new BaseMatcher<List<Pet>>() {
@Override @Override
public boolean matches(Object item) { public boolean matches(Object item) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
List<Pet> pets = (List<Pet>) item; List<Pet> pets = (List<Pet>) item;
Pet pet = pets.get(0); Pet pet = pets.get(0);
if (pet.getVisits().isEmpty()) { if (pet.getVisits().isEmpty()) {
return false; return false;
} }
return true; return true;
} }
@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,75 +39,75 @@ import org.springframework.test.web.servlet.MockMvc;
* @author Colin But * @author Colin But
*/ */
@WebMvcTest(value = PetController.class, @WebMvcTest(value = PetController.class,
includeFilters = @ComponentScan.Filter(value = PetTypeFormatter.class, type = FilterType.ASSIGNABLE_TYPE)) includeFilters = @ComponentScan.Filter(value = PetTypeFormatter.class, type = FilterType.ASSIGNABLE_TYPE))
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;
@MockBean @MockBean
private PetRepository pets; private PetRepository pets;
@MockBean @MockBean
private OwnerRepository owners; private OwnerRepository owners;
@BeforeEach @BeforeEach
void setup() { void setup() {
PetType cat = new PetType(); PetType cat = new PetType();
cat.setId(3); cat.setId(3);
cat.setName("hamster"); cat.setName("hamster");
given(this.pets.findPetTypes()).willReturn(Lists.newArrayList(cat)); given(this.pets.findPetTypes()).willReturn(Lists.newArrayList(cat));
given(this.owners.findById(TEST_OWNER_ID)).willReturn(new Owner()); given(this.owners.findById(TEST_OWNER_ID)).willReturn(new Owner());
given(this.pets.findById(TEST_PET_ID)).willReturn(new Pet()); given(this.pets.findById(TEST_PET_ID)).willReturn(new Pet());
} }
@Test @Test
void testInitCreationForm() throws Exception { void testInitCreationForm() throws Exception {
mockMvc.perform(get("/owners/{ownerId}/pets/new", TEST_OWNER_ID)).andExpect(status().isOk()) mockMvc.perform(get("/owners/{ownerId}/pets/new", TEST_OWNER_ID)).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).param("name", "Betty") mockMvc.perform(post("/owners/{ownerId}/pets/new", TEST_OWNER_ID).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).param("name", "Betty").param("birthDate", mockMvc.perform(post("/owners/{ownerId}/pets/new", TEST_OWNER_ID).param("name", "Betty").param("birthDate",
"2015-02-12")).andExpect(model().attributeHasNoErrors("owner")) "2015-02-12")).andExpect(model().attributeHasNoErrors("owner"))
.andExpect(model().attributeHasErrors("pet")).andExpect(model().attributeHasFieldErrors("pet", "type")) .andExpect(model().attributeHasErrors("pet")).andExpect(model().attributeHasFieldErrors("pet", "type"))
.andExpect(model().attributeHasFieldErrorCode("pet", "type", "required")).andExpect(status().isOk()) .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(model().attributeExists("pet")) .andExpect(status().isOk()).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).param("name", "Betty") mockMvc.perform(post("/owners/{ownerId}/pets/{petId}/edit", TEST_OWNER_ID, TEST_PET_ID).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).param("name", "Betty") mockMvc.perform(post("/owners/{ownerId}/pets/{petId}/edit", TEST_OWNER_ID, TEST_PET_ID).param("name", "Betty")
.param("birthDate", "2015/02/12")).andExpect(model().attributeHasNoErrors("owner")) .param("birthDate", "2015/02/12")).andExpect(model().attributeHasNoErrors("owner"))
.andExpect(model().attributeHasErrors("pet")).andExpect(status().isOk()) .andExpect(model().attributeHasErrors("pet")).andExpect(status().isOk())
.andExpect(view().name("pets/createOrUpdatePetForm")); .andExpect(view().name("pets/createOrUpdatePetForm"));
} }
} }

View file

@ -40,56 +40,56 @@ import static org.mockito.BDDMockito.given;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class PetTypeFormatterTests { class PetTypeFormatterTests {
@Mock @Mock
private PetRepository pets; private PetRepository pets;
private PetTypeFormatter petTypeFormatter; private PetTypeFormatter petTypeFormatter;
@BeforeEach @BeforeEach
void setup() { void setup() {
this.petTypeFormatter = new PetTypeFormatter(pets); this.petTypeFormatter = new PetTypeFormatter(pets);
} }
@Test @Test
void testPrint() { void testPrint() {
PetType petType = new PetType(); PetType petType = new PetType();
petType.setName("Hamster"); petType.setName("Hamster");
String petTypeName = this.petTypeFormatter.print(petType, Locale.ENGLISH); String petTypeName = this.petTypeFormatter.print(petType, Locale.ENGLISH);
assertThat(petTypeName).isEqualTo("Hamster"); assertThat(petTypeName).isEqualTo("Hamster");
} }
@Test @Test
void shouldParse() throws ParseException { void shouldParse() throws ParseException {
given(this.pets.findPetTypes()).willReturn(makePetTypes()); given(this.pets.findPetTypes()).willReturn(makePetTypes());
PetType petType = petTypeFormatter.parse("Bird", Locale.ENGLISH); PetType petType = petTypeFormatter.parse("Bird", Locale.ENGLISH);
assertThat(petType.getName()).isEqualTo("Bird"); assertThat(petType.getName()).isEqualTo("Bird");
} }
@Test @Test
void shouldThrowParseException() throws ParseException { void shouldThrowParseException() throws ParseException {
given(this.pets.findPetTypes()).willReturn(makePetTypes()); given(this.pets.findPetTypes()).willReturn(makePetTypes());
Assertions.assertThrows(ParseException.class, () -> { Assertions.assertThrows(ParseException.class, () -> {
petTypeFormatter.parse("Fish", Locale.ENGLISH); petTypeFormatter.parse("Fish", Locale.ENGLISH);
}); });
} }
/** /**
* 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() {
List<PetType> petTypes = new ArrayList<>(); List<PetType> petTypes = new ArrayList<>();
petTypes.add(new PetType() { petTypes.add(new PetType() {
{ {
setName("Dog"); setName("Dog");
} }
}); });
petTypes.add(new PetType() { petTypes.add(new PetType() {
{ {
setName("Bird"); setName("Bird");
} }
}); });
return petTypes; return petTypes;
} }
} }

View file

@ -39,40 +39,40 @@ import org.springframework.test.web.servlet.MockMvc;
@WebMvcTest(VisitController.class) @WebMvcTest(VisitController.class)
class VisitControllerTests { class VisitControllerTests {
private static final int TEST_PET_ID = 1; private static final int TEST_PET_ID = 1;
@Autowired @Autowired
private MockMvc mockMvc; private MockMvc mockMvc;
@MockBean @MockBean
private VisitRepository visits; private VisitRepository visits;
@MockBean @MockBean
private PetRepository pets; private PetRepository pets;
@BeforeEach @BeforeEach
void init() { void init() {
given(this.pets.findById(TEST_PET_ID)).willReturn(new Pet()); given(this.pets.findById(TEST_PET_ID)).willReturn(new Pet());
} }
@Test @Test
void testInitNewVisitForm() throws Exception { void testInitNewVisitForm() throws Exception {
mockMvc.perform(get("/owners/*/pets/{petId}/visits/new", TEST_PET_ID)).andExpect(status().isOk()) mockMvc.perform(get("/owners/*/pets/{petId}/visits/new", TEST_PET_ID)).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).param("name", "George") mockMvc.perform(post("/owners/*/pets/{petId}/visits/new", TEST_PET_ID).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).param("name", "George")) mockMvc.perform(post("/owners/*/pets/{petId}/visits/new", TEST_PET_ID).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

@ -69,159 +69,159 @@ import org.springframework.transaction.annotation.Transactional;
@DataJpaTest(includeFilters = @ComponentScan.Filter(Service.class)) @DataJpaTest(includeFilters = @ComponentScan.Filter(Service.class))
class ClinicServiceTests { class ClinicServiceTests {
@Autowired @Autowired
protected OwnerRepository owners; protected OwnerRepository owners;
@Autowired @Autowired
protected PetRepository pets; protected PetRepository pets;
@Autowired @Autowired
protected VisitRepository visits; protected VisitRepository visits;
@Autowired @Autowired
protected VetRepository vets; protected VetRepository vets;
@Test @Test
void shouldFindOwnersByLastName() { void shouldFindOwnersByLastName() {
Collection<Owner> owners = this.owners.findByLastName("Davis"); Collection<Owner> owners = this.owners.findByLastName("Davis");
assertThat(owners).hasSize(2); assertThat(owners).hasSize(2);
owners = this.owners.findByLastName("Daviss"); owners = this.owners.findByLastName("Daviss");
assertThat(owners).isEmpty(); assertThat(owners).isEmpty();
} }
@Test @Test
void shouldFindSingleOwnerWithPet() { void shouldFindSingleOwnerWithPet() {
Owner owner = this.owners.findById(1); Owner owner = this.owners.findById(1);
assertThat(owner.getLastName()).startsWith("Franklin"); assertThat(owner.getLastName()).startsWith("Franklin");
assertThat(owner.getPets()).hasSize(1); assertThat(owner.getPets()).hasSize(1);
assertThat(owner.getPets().get(0).getType()).isNotNull(); assertThat(owner.getPets().get(0).getType()).isNotNull();
assertThat(owner.getPets().get(0).getType().getName()).isEqualTo("cat"); assertThat(owner.getPets().get(0).getType().getName()).isEqualTo("cat");
} }
@Test @Test
@Transactional @Transactional
void shouldInsertOwner() { void shouldInsertOwner() {
Collection<Owner> owners = this.owners.findByLastName("Schultz"); Collection<Owner> owners = this.owners.findByLastName("Schultz");
int found = owners.size(); int found = owners.size();
Owner owner = new Owner(); Owner owner = new Owner();
owner.setFirstName("Sam"); owner.setFirstName("Sam");
owner.setLastName("Schultz"); owner.setLastName("Schultz");
owner.setAddress("4, Evans Street"); owner.setAddress("4, Evans Street");
owner.setCity("Wollongong"); owner.setCity("Wollongong");
owner.setTelephone("4444444444"); owner.setTelephone("4444444444");
this.owners.save(owner); this.owners.save(owner);
assertThat(owner.getId().longValue()).isNotEqualTo(0); assertThat(owner.getId().longValue()).isNotEqualTo(0);
owners = this.owners.findByLastName("Schultz"); owners = this.owners.findByLastName("Schultz");
assertThat(owners.size()).isEqualTo(found + 1); assertThat(owners.size()).isEqualTo(found + 1);
} }
@Test @Test
@Transactional @Transactional
void shouldUpdateOwner() { void shouldUpdateOwner() {
Owner owner = this.owners.findById(1); Owner owner = this.owners.findById(1);
String oldLastName = owner.getLastName(); String oldLastName = owner.getLastName();
String newLastName = oldLastName + "X"; String newLastName = oldLastName + "X";
owner.setLastName(newLastName); owner.setLastName(newLastName);
this.owners.save(owner); this.owners.save(owner);
// retrieving new name from database // retrieving new name from database
owner = this.owners.findById(1); owner = this.owners.findById(1);
assertThat(owner.getLastName()).isEqualTo(newLastName); assertThat(owner.getLastName()).isEqualTo(newLastName);
} }
@Test @Test
void shouldFindPetWithCorrectId() { void shouldFindPetWithCorrectId() {
Pet pet7 = this.pets.findById(7); Pet pet7 = this.pets.findById(7);
assertThat(pet7.getName()).startsWith("Samantha"); assertThat(pet7.getName()).startsWith("Samantha");
assertThat(pet7.getOwner().getFirstName()).isEqualTo("Jean"); assertThat(pet7.getOwner().getFirstName()).isEqualTo("Jean");
} }
@Test @Test
void shouldFindAllPetTypes() { void shouldFindAllPetTypes() {
Collection<PetType> petTypes = this.pets.findPetTypes(); Collection<PetType> petTypes = this.pets.findPetTypes();
PetType petType1 = EntityUtils.getById(petTypes, PetType.class, 1); PetType petType1 = EntityUtils.getById(petTypes, PetType.class, 1);
assertThat(petType1.getName()).isEqualTo("cat"); assertThat(petType1.getName()).isEqualTo("cat");
PetType petType4 = EntityUtils.getById(petTypes, PetType.class, 4); PetType petType4 = EntityUtils.getById(petTypes, PetType.class, 4);
assertThat(petType4.getName()).isEqualTo("snake"); assertThat(petType4.getName()).isEqualTo("snake");
} }
@Test @Test
@Transactional @Transactional
void shouldInsertPetIntoDatabaseAndGenerateId() { void shouldInsertPetIntoDatabaseAndGenerateId() {
Owner owner6 = this.owners.findById(6); Owner owner6 = this.owners.findById(6);
int found = owner6.getPets().size(); int found = owner6.getPets().size();
Pet pet = new Pet(); Pet pet = new Pet();
pet.setName("bowser"); pet.setName("bowser");
Collection<PetType> types = this.pets.findPetTypes(); Collection<PetType> types = this.pets.findPetTypes();
pet.setType(EntityUtils.getById(types, PetType.class, 2)); pet.setType(EntityUtils.getById(types, PetType.class, 2));
pet.setBirthDate(LocalDate.now()); pet.setBirthDate(LocalDate.now());
owner6.addPet(pet); owner6.addPet(pet);
assertThat(owner6.getPets().size()).isEqualTo(found + 1); assertThat(owner6.getPets().size()).isEqualTo(found + 1);
this.pets.save(pet); this.pets.save(pet);
this.owners.save(owner6); this.owners.save(owner6);
owner6 = this.owners.findById(6); owner6 = this.owners.findById(6);
assertThat(owner6.getPets().size()).isEqualTo(found + 1); assertThat(owner6.getPets().size()).isEqualTo(found + 1);
// checks that id has been generated // checks that id has been generated
assertThat(pet.getId()).isNotNull(); assertThat(pet.getId()).isNotNull();
} }
@Test @Test
@Transactional @Transactional
void shouldUpdatePetName() throws Exception { void shouldUpdatePetName() throws Exception {
Pet pet7 = this.pets.findById(7); Pet pet7 = this.pets.findById(7);
String oldName = pet7.getName(); String oldName = pet7.getName();
String newName = oldName + "X"; String newName = oldName + "X";
pet7.setName(newName); pet7.setName(newName);
this.pets.save(pet7); this.pets.save(pet7);
pet7 = this.pets.findById(7); pet7 = this.pets.findById(7);
assertThat(pet7.getName()).isEqualTo(newName); assertThat(pet7.getName()).isEqualTo(newName);
} }
@Test @Test
void shouldFindVets() { void shouldFindVets() {
Collection<Vet> vets = this.vets.findAll(); Collection<Vet> vets = this.vets.findAll();
Vet vet = EntityUtils.getById(vets, Vet.class, 3); Vet vet = EntityUtils.getById(vets, Vet.class, 3);
assertThat(vet.getLastName()).isEqualTo("Douglas"); assertThat(vet.getLastName()).isEqualTo("Douglas");
assertThat(vet.getNrOfSpecialties()).isEqualTo(2); assertThat(vet.getNrOfSpecialties()).isEqualTo(2);
assertThat(vet.getSpecialties().get(0).getName()).isEqualTo("dentistry"); assertThat(vet.getSpecialties().get(0).getName()).isEqualTo("dentistry");
assertThat(vet.getSpecialties().get(1).getName()).isEqualTo("surgery"); assertThat(vet.getSpecialties().get(1).getName()).isEqualTo("surgery");
} }
@Test @Test
@Transactional @Transactional
void shouldAddNewVisitForPet() { void shouldAddNewVisitForPet() {
Pet pet7 = this.pets.findById(7); Pet pet7 = this.pets.findById(7);
int found = pet7.getVisits().size(); int found = pet7.getVisits().size();
Visit visit = new Visit(); Visit visit = new Visit();
pet7.addVisit(visit); pet7.addVisit(visit);
visit.setDescription("test"); visit.setDescription("test");
this.visits.save(visit); this.visits.save(visit);
this.pets.save(pet7); this.pets.save(pet7);
pet7 = this.pets.findById(7); pet7 = this.pets.findById(7);
assertThat(pet7.getVisits().size()).isEqualTo(found + 1); assertThat(pet7.getVisits().size()).isEqualTo(found + 1);
assertThat(visit.getId()).isNotNull(); assertThat(visit.getId()).isNotNull();
} }
@Test @Test
void shouldFindVisitsByPetId() throws Exception { void shouldFindVisitsByPetId() throws Exception {
Collection<Visit> visits = this.visits.findByPetId(7); Collection<Visit> visits = this.visits.findByPetId(7);
assertThat(visits).hasSize(2); assertThat(visits).hasSize(2);
Visit[] visitArr = visits.toArray(new Visit[visits.size()]); Visit[] visitArr = visits.toArray(new Visit[visits.size()]);
assertThat(visitArr[0].getDate()).isNotNull(); assertThat(visitArr[0].getDate()).isNotNull();
assertThat(visitArr[0].getPetId()).isEqualTo(7); assertThat(visitArr[0].getPetId()).isEqualTo(7);
} }
} }

View file

@ -32,22 +32,22 @@ import org.springframework.samples.petclinic.model.BaseEntity;
*/ */
public abstract class EntityUtils { 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
* @return the found entity * @return the found entity
* @throws ObjectRetrievalFailureException if the entity was not found * @throws ObjectRetrievalFailureException if the entity was not found
*/ */
public static <T extends BaseEntity> T getById(Collection<T> entities, Class<T> entityClass, int entityId) public static <T extends BaseEntity> T getById(Collection<T> entities, Class<T> entityClass, int entityId)
throws ObjectRetrievalFailureException { throws ObjectRetrievalFailureException {
for (T entity : entities) { for (T entity : entities) {
if (entity.getId() == entityId && entityClass.isInstance(entity)) { if (entity.getId() == entityId && entityClass.isInstance(entity)) {
return entity; return entity;
} }
} }
throw new ObjectRetrievalFailureException(entityClass, entityId); throw new ObjectRetrievalFailureException(entityClass, entityId);
} }
} }

View file

@ -38,14 +38,14 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
@WebMvcTest(controllers = CrashController.class) @WebMvcTest(controllers = CrashController.class)
class CrashControllerTests { class CrashControllerTests {
@Autowired @Autowired
private MockMvc mockMvc; private MockMvc mockMvc;
@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(forwardedUrl("exception")) .andExpect(model().attributeExists("exception")).andExpect(forwardedUrl("exception"))
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
} }

View file

@ -40,41 +40,41 @@ import org.springframework.test.web.servlet.ResultActions;
@WebMvcTest(VetController.class) @WebMvcTest(VetController.class)
class VetControllerTests { class VetControllerTests {
@Autowired @Autowired
private MockMvc mockMvc; private MockMvc mockMvc;
@MockBean @MockBean
private VetRepository vets; private VetRepository vets;
@BeforeEach @BeforeEach
void setup() { void setup() {
Vet james = new Vet(); Vet james = new Vet();
james.setFirstName("James"); james.setFirstName("James");
james.setLastName("Carter"); james.setLastName("Carter");
james.setId(1); james.setId(1);
Vet helen = new Vet(); Vet helen = new Vet();
helen.setFirstName("Helen"); helen.setFirstName("Helen");
helen.setLastName("Leary"); helen.setLastName("Leary");
helen.setId(2); helen.setId(2);
Specialty radiology = new Specialty(); Specialty radiology = new Specialty();
radiology.setId(1); radiology.setId(1);
radiology.setName("radiology"); radiology.setName("radiology");
helen.addSpecialty(radiology); helen.addSpecialty(radiology);
given(this.vets.findAll()).willReturn(Lists.newArrayList(james, helen)); given(this.vets.findAll()).willReturn(Lists.newArrayList(james, helen));
} }
@Test @Test
void testShowVetListHtml() throws Exception { void testShowVetListHtml() throws Exception {
mockMvc.perform(get("/vets.html")).andExpect(status().isOk()).andExpect(model().attributeExists("vets")) mockMvc.perform(get("/vets.html")).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").accept(MediaType.APPLICATION_JSON)) ResultActions actions = mockMvc.perform(get("/vets").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

@ -25,16 +25,16 @@ import static org.assertj.core.api.Assertions.assertThat;
*/ */
class VetTests { class VetTests {
@Test @Test
void testSerialization() { void testSerialization() {
Vet vet = new Vet(); Vet vet = new Vet();
vet.setFirstName("Zaphod"); vet.setFirstName("Zaphod");
vet.setLastName("Beeblebrox"); vet.setLastName("Beeblebrox");
vet.setId(123); vet.setId(123);
Vet other = (Vet) SerializationUtils.deserialize(SerializationUtils.serialize(vet)); Vet other = (Vet) SerializationUtils.deserialize(SerializationUtils.serialize(vet));
assertThat(other.getFirstName()).isEqualTo(vet.getFirstName()); assertThat(other.getFirstName()).isEqualTo(vet.getFirstName());
assertThat(other.getLastName()).isEqualTo(vet.getLastName()); assertThat(other.getLastName()).isEqualTo(vet.getLastName());
assertThat(other.getId()).isEqualTo(vet.getId()); assertThat(other.getId()).isEqualTo(vet.getId());
} }
} }