Merge branch 'main' of ssh://github.com/spring-projects/spring-petclinic into feature/migrate_petclinic_to_java_11

This commit is contained in:
Robert Auer 2020-09-24 09:25:56 +02:00
commit 728111eda3
No known key found for this signature in database
GPG key ID: F4BE29C88C743725
77 changed files with 2712 additions and 2061 deletions

View file

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

8
.gitignore vendored
View file

@ -2,9 +2,17 @@ target/*
.settings/* .settings/*
.classpath .classpath
.project .project
.factorypath
.attach_pid*
.idea .idea
*.iml *.iml
/target /target
.sts4-cache/
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
_site/ _site/
# Created by https://www.gitignore.io/api/ansible # Created by https://www.gitignore.io/api/ansible

117
.mvn/wrapper/MavenWrapperDownloader.java vendored Normal file
View file

@ -0,0 +1,117 @@
/*
* Copyright 2007-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0'
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.net.*;
import java.io.*;
import java.nio.channels.*;
import java.util.Properties;
public class MavenWrapperDownloader {
private static final String WRAPPER_VERSION = "0.5.6";
/**
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
*/
private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
+ WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
/**
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
* use instead of the default one.
*/
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
".mvn/wrapper/maven-wrapper.properties";
/**
* Path where the maven-wrapper.jar will be saved to.
*/
private static final String MAVEN_WRAPPER_JAR_PATH =
".mvn/wrapper/maven-wrapper.jar";
/**
* Name of the property which should be used to override the default download url for the wrapper.
*/
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
public static void main(String args[]) {
System.out.println("- Downloader started");
File baseDirectory = new File(args[0]);
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
// If the maven-wrapper.properties exists, read it and check if it contains a custom
// wrapperUrl parameter.
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
String url = DEFAULT_DOWNLOAD_URL;
if(mavenWrapperPropertyFile.exists()) {
FileInputStream mavenWrapperPropertyFileInputStream = null;
try {
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
Properties mavenWrapperProperties = new Properties();
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
} catch (IOException e) {
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
} finally {
try {
if(mavenWrapperPropertyFileInputStream != null) {
mavenWrapperPropertyFileInputStream.close();
}
} catch (IOException e) {
// Ignore ...
}
}
}
System.out.println("- Downloading from: " + url);
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
if(!outputFile.getParentFile().exists()) {
if(!outputFile.getParentFile().mkdirs()) {
System.out.println(
"- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
}
}
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
try {
downloadFileFromURL(url, outputFile);
System.out.println("Done");
System.exit(0);
} catch (Throwable e) {
System.out.println("- Error downloading");
e.printStackTrace();
System.exit(1);
}
}
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
String username = System.getenv("MVNW_USERNAME");
char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
}
URL website = new URL(urlString);
ReadableByteChannel rbc;
rbc = Channels.newChannel(website.openStream());
FileOutputStream fos = new FileOutputStream(destination);
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
fos.close();
rbc.close();
}
}

Binary file not shown.

View file

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

View file

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<beansProjectDescription>
<version>1</version>
<pluginVersion><![CDATA[3.5.0.201404011509-RELEASE]]></pluginVersion>
<configSuffixes>
<configSuffix><![CDATA[xml]]></configSuffix>
</configSuffixes>
<enableImports><![CDATA[false]]></enableImports>
<configs>
<config>src/main/resources/spring/datasource-config.xml</config>
<config>src/main/resources/spring/mvc-core-config.xml</config>
<config>src/main/resources/spring/mvc-view-config.xml</config>
<config>src/main/resources/spring/business-config.xml</config>
</configs>
<autoconfigs>
<config>src/main/resources/spring/tools-config.xml</config>
</autoconfigs>
<configSets>
</configSets>
</beansProjectDescription>

View file

@ -1,2 +1,3 @@
dist: trusty
language: java language: java
jdk: oraclejdk8 jdk: oraclejdk8

26
.vscode/launch.json vendored Normal file
View file

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

3
.vscode/settings.json vendored Normal file
View file

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

19
.vscode/tasks.json vendored Normal file
View file

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

View file

@ -1,9 +1,12 @@
mysql: mysql:
image: mysql image: mysql:5.7
ports: ports:
- "3306:3306" - "3306:3306"
environment: environment:
- MYSQL_ROOT_PASSWORD=root - MYSQL_ROOT_PASSWORD=
- MYSQL_DATABASE=test - MYSQL_ALLOW_EMPTY_PASSWORD=true
- MYSQL_USER=petclinic
- MYSQL_PASSWORD=petclinic
- MYSQL_DATABASE=petclinic
volumes: volumes:
- "./conf.d:/etc/mysql/conf.d:ro" - "./conf.d:/etc/mysql/conf.d:ro"

174
mvnw vendored
View file

@ -8,7 +8,7 @@
# "License"); you may not use this file except in compliance # "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at # with the License. You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # https://www.apache.org/licenses/LICENSE-2.0'
# #
# Unless required by applicable law or agreed to in writing, # Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an # software distributed under the License is distributed on an
@ -19,7 +19,7 @@
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
# Maven2 Start Up Batch script # Maven Start Up Batch script
# #
# Required ENV vars: # Required ENV vars:
# ------------------ # ------------------
@ -54,36 +54,14 @@ case "`uname`" in
CYGWIN*) cygwin=true ;; CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;; MINGW*) mingw=true;;
Darwin*) darwin=true Darwin*) darwin=true
# # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# Look for the Apple JDKs first to preserve the existing behaviour, and then look # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
# for the new JDKs provided by Oracle. if [ -z "$JAVA_HOME" ]; then
# if [ -x "/usr/libexec/java_home" ]; then
if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then export JAVA_HOME="`/usr/libexec/java_home`"
# else
# Apple JDKs export JAVA_HOME="/Library/Java/Home"
#
export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home
fi fi
if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then
#
# Apple JDKs
#
export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
fi
if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then
#
# Oracle JDKs
#
export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
fi
if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then
#
# Apple JDKs
#
export JAVA_HOME=`/usr/libexec/java_home`
fi fi
;; ;;
esac esac
@ -130,13 +108,12 @@ if $cygwin ; then
CLASSPATH=`cygpath --path --unix "$CLASSPATH"` CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
fi fi
# For Migwn, ensure paths are in UNIX format before anything is touched # For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then if $mingw ; then
[ -n "$M2_HOME" ] && [ -n "$M2_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`" M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] && [ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
# TODO classpath?
fi fi
if [ -z "$JAVA_HOME" ]; then if [ -z "$JAVA_HOME" ]; then
@ -184,27 +161,28 @@ fi
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
fi
# traverses directory structure from process work directory to filesystem root # traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory # first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() { find_maven_basedir() {
local basedir=$(pwd)
local wdir=$(pwd) if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir basedir=$wdir
break break
fi fi
wdir=$(cd "$wdir/.."; pwd) # workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=`cd "$wdir/.."; pwd`
fi
# end of workaround
done done
echo "${basedir}" echo "${basedir}"
} }
@ -216,9 +194,108 @@ concat_lines() {
fi fi
} }
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} BASE_DIR=`find_maven_basedir "$(pwd)"`
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found .mvn/wrapper/maven-wrapper.jar"
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
if [ -n "$MVNW_REPOURL" ]; then
jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
else
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
fi
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
esac
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
if [ "$MVNW_VERBOSE" = true ]; then
echo "Downloading from: $jarUrl"
fi
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
if $cygwin; then
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
fi
if command -v wget > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget "$jarUrl" -O "$wrapperJarPath"
else
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl -o "$wrapperJarPath" "$jarUrl" -f
else
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaClass=`cygpath --path --windows "$javaClass"`
fi
if [ -e "$javaClass" ]; then
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Compiling MavenWrapperDownloader.java ..."
fi
# Compiling the Java class
("$JAVA_HOME/bin/javac" "$javaClass")
fi
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
# Running the downloader
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Running MavenWrapperDownloader.java ..."
fi
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
if [ "$MVNW_VERBOSE" = true ]; then
echo $MAVEN_PROJECTBASEDIR
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi
# Provide a "standardized" way to retrieve the CLI args that will # Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions. # work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
@ -230,5 +307,4 @@ exec "$JAVACMD" \
$MAVEN_OPTS \ $MAVEN_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CMD_LINE_ARGS ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

53
mvnw.cmd vendored
View file

@ -7,7 +7,7 @@
@REM "License"); you may not use this file except in compliance @REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at @REM with the License. You may obtain a copy of the License at
@REM @REM
@REM http://www.apache.org/licenses/LICENSE-2.0 @REM https://www.apache.org/licenses/LICENSE-2.0'
@REM @REM
@REM Unless required by applicable law or agreed to in writing, @REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an @REM software distributed under the License is distributed on an
@ -18,7 +18,7 @@
@REM ---------------------------------------------------------------------------- @REM ----------------------------------------------------------------------------
@REM ---------------------------------------------------------------------------- @REM ----------------------------------------------------------------------------
@REM Maven2 Start Up Batch script @REM Maven Start Up Batch script
@REM @REM
@REM Required ENV vars: @REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir @REM JAVA_HOME - location of a JDK home dir
@ -35,7 +35,9 @@
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off @echo off
@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' @REM set title of command window
title %0
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME @REM set %HOME% to equivalent of $HOME
@ -80,8 +82,6 @@ goto error
:init :init
set MAVEN_CMD_LINE_ARGS=%MAVEN_CONFIG% %*
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". @REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found. @REM Fallback to current working directory if not found.
@ -117,11 +117,48 @@ for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do s
:endReadAdditionalConfig :endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_JAR=""%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar""
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error if ERRORLEVEL 1 goto error
goto end goto end

261
pom.xml
View file

@ -1,25 +1,23 @@
<?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 http://maven.apache.org/maven-v4_0_0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.samples</groupId> <groupId>org.springframework.samples</groupId>
<artifactId>spring-petclinic</artifactId> <artifactId>spring-petclinic</artifactId>
<version>1.5.2-SNAPSHOT</version> <version>2.3.0.BUILD-SNAPSHOT</version>
<parent> <parent>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId> <artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.RELEASE</version> <version>2.3.3.RELEASE</version>
</parent> </parent>
<name>petclinic</name> <name>petclinic</name>
<properties> <properties>
<!-- Generic properties --> <!-- Generic properties -->
<java.version>11</java.version> <java.version>1.8</java.version>
<maven.compiler.release>${java.version}</maven.compiler.release>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
@ -27,33 +25,14 @@
<webjars-bootstrap.version>3.3.6</webjars-bootstrap.version> <webjars-bootstrap.version>3.3.6</webjars-bootstrap.version>
<webjars-jquery-ui.version>1.11.4</webjars-jquery-ui.version> <webjars-jquery-ui.version>1.11.4</webjars-jquery-ui.version>
<webjars-jquery.version>2.2.4</webjars-jquery.version> <webjars-jquery.version>2.2.4</webjars-jquery.version>
<wro4j.version>1.9.0</wro4j.version> <wro4j.version>1.8.0</wro4j.version>
<thymeleaf.version>3.0.6.RELEASE</thymeleaf.version>
<jacoco.version>0.8.5</jacoco.version> <jacoco.version>0.8.5</jacoco.version>
<nohttp-checkstyle.version>0.0.4.RELEASE</nohttp-checkstyle.version>
<spring-format.version>0.0.25</spring-format.version>
</properties> </properties>
<dependencies> <dependencies>
<!-- API, java.xml.bind module -->
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>2.3.2</version>
</dependency>
<!-- Runtime, com.sun.xml.bind module -->
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.3</version>
</dependency>
<!-- Apache CXF from Java 11 on - see https://stackoverflow.com/questions/55476331/tomcat8-5-and-openjdk11-noclassdeffounderror-could-not-initialize-class-org-apa -->
<dependency>
<groupId>com.sun.activation</groupId>
<artifactId>jakarta.activation</artifactId>
<version>1.2.1</version>
</dependency>
<!-- Spring and Spring Boot dependencies --> <!-- Spring and Spring Boot dependencies -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
@ -71,26 +50,30 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-web</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId> <artifactId>spring-boot-starter-thymeleaf</artifactId>
<exclusions>
<exclusion>
<groupId>nz.net.ultraq.thymeleaf</groupId>
<artifactId>thymeleaf-layout-dialect</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<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>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<!-- Databases - Uses HSQL by default --> <!-- Databases - Uses H2 by default -->
<dependency> <dependency>
<groupId>org.hsqldb</groupId> <groupId>com.h2database</groupId>
<artifactId>hsqldb</artifactId> <artifactId>h2</artifactId>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
<dependency> <dependency>
@ -112,7 +95,7 @@
<!-- webjars --> <!-- webjars -->
<dependency> <dependency>
<groupId>org.webjars</groupId> <groupId>org.webjars</groupId>
<artifactId>webjars-locator</artifactId> <artifactId>webjars-locator-core</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.webjars</groupId> <groupId>org.webjars</groupId>
@ -131,23 +114,73 @@
</dependency> </dependency>
<!-- end of webjars --> <!-- end of webjars -->
<!-- Testing -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId> <artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope> <optional>true</optional>
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>
<finalName>spring-petclinic</finalName>
<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-compiler-plugin</artifactId> <artifactId>maven-checkstyle-plugin</artifactId>
<version>3.6.0</version> <version>3.1.1</version>
<dependencies>
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>8.32</version>
</dependency>
<dependency>
<groupId>io.spring.nohttp</groupId>
<artifactId>nohttp-checkstyle</artifactId>
<version>${nohttp-checkstyle.version}</version>
</dependency>
</dependencies>
<executions>
<execution>
<id>nohttp-checkstyle-validation</id>
<phase>validate</phase>
<configuration> <configuration>
<release>8</release> <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> </configuration>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
@ -160,7 +193,6 @@
<goal>build-info</goal> <goal>build-info</goal>
</goals> </goals>
<configuration> <configuration>
<executable>true</executable>
<additionalProperties> <additionalProperties>
<encoding.source>${project.build.sourceEncoding}</encoding.source> <encoding.source>${project.build.sourceEncoding}</encoding.source>
<encoding.reporting>${project.reporting.outputEncoding}</encoding.reporting> <encoding.reporting>${project.reporting.outputEncoding}</encoding.reporting>
@ -171,24 +203,24 @@
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<executions>
<execution>
<phase>integration-test</phase>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin> <plugin>
<groupId>org.jacoco</groupId> <groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId> <artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco.version}</version> <version>${jacoco.version}</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>prepare-package</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin> </plugin>
<!-- Spring Boot Actuator displays build-related information if a git.properties <!-- Spring Boot Actuator displays build-related information if a git.properties
@ -226,8 +258,7 @@
</execution> </execution>
</executions> </executions>
<configuration> <configuration>
<wroManagerFactory>ro.isdc.wro.maven.plugin.manager.factory.ConfigurableWroManagerFactory <wroManagerFactory>ro.isdc.wro.maven.plugin.manager.factory.ConfigurableWroManagerFactory</wroManagerFactory>
</wroManagerFactory>
<cssDestinationFolder>${project.build.directory}/classes/static/resources/css</cssDestinationFolder> <cssDestinationFolder>${project.build.directory}/classes/static/resources/css</cssDestinationFolder>
<wroFile>${basedir}/src/main/wro/wro.xml</wroFile> <wroFile>${basedir}/src/main/wro/wro.xml</wroFile>
<extraConfigFile>${basedir}/src/main/wro/wro.properties</extraConfigFile> <extraConfigFile>${basedir}/src/main/wro/wro.properties</extraConfigFile>
@ -239,25 +270,115 @@
<artifactId>bootstrap</artifactId> <artifactId>bootstrap</artifactId>
<version>${webjars-bootstrap.version}</version> <version>${webjars-bootstrap.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
</dependency>
</dependencies> </dependencies>
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
<reporting>
<licenses>
<license>
<name>Apache License, Version 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0</url>
</license>
</licenses>
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
<profiles>
<profile>
<id>m2e</id>
<activation>
<property>
<name>m2e.version</name>
</property>
</activation>
<build>
<pluginManagement>
<plugins> <plugins>
<!-- integrate maven-cobertura-plugin to project site --> <!-- This plugin's configuration is used to store Eclipse m2e settings
only. It has no influence on the Maven build itself. -->
<plugin> <plugin>
<groupId>org.codehaus.mojo</groupId> <groupId>org.eclipse.m2e</groupId>
<artifactId>cobertura-maven-plugin</artifactId> <artifactId>lifecycle-mapping</artifactId>
<version>${cobertura.version}</version> <version>1.0.0</version>
<configuration> <configuration>
<formats> <lifecycleMappingMetadata>
<format>html</format> <pluginExecutions>
</formats> <pluginExecution>
<check/> <pluginExecutionFilter>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<versionRange>[1,)</versionRange>
<goals>
<goal>check</goal>
</goals>
</pluginExecutionFilter>
<action>
<ignore/>
</action>
</pluginExecution>
<pluginExecution>
<pluginExecutionFilter>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<versionRange>[1,)</versionRange>
<goals>
<goal>build-info</goal>
</goals>
</pluginExecutionFilter>
<action>
<ignore/>
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>
</reporting> </pluginManagement>
</build>
</profile>
</profiles>
</project> </project>

View file

@ -1,85 +1,102 @@
# Spring PetClinic Sample Application [![Build Status](https://travis-ci.org/spring-projects/spring-petclinic.png?branch=master)](https://travis-ci.org/spring-projects/spring-petclinic/) # Spring PetClinic Sample Application [![Build Status](https://travis-ci.org/spring-projects/spring-petclinic.png?branch=main)](https://travis-ci.org/spring-projects/spring-petclinic/)
## Understanding the Spring Petclinic application with a few diagrams ## Understanding the Spring Petclinic application with a few diagrams
<a href="https://speakerdeck.com/michaelisvy/spring-petclinic-sample-application">See the presentation here</a> <a href="https://speakerdeck.com/michaelisvy/spring-petclinic-sample-application">See the presentation here</a>
## Running petclinic locally ## Running petclinic locally
Petclinic is a [Spring Boot](https://spring.io/guides/gs/spring-boot) application built using [Maven](https://spring.io/guides/gs/maven/). You can build a jar file and run it from the command line:
``` ```
git clone https://github.com/spring-projects/spring-petclinic.git git clone https://github.com/spring-projects/spring-petclinic.git
cd spring-petclinic cd spring-petclinic
./mvnw spring-boot:run ./mvnw package
java -jar target/*.jar
``` ```
You can then access petclinic here: http://localhost:8080/ You can then access petclinic here: http://localhost:8080/
<img width="1042" alt="petclinic-screenshot" src="https://cloud.githubusercontent.com/assets/838318/19727082/2aee6d6c-9b8e-11e6-81fe-e889a5ddfded.png"> <img width="1042" alt="petclinic-screenshot" src="https://cloud.githubusercontent.com/assets/838318/19727082/2aee6d6c-9b8e-11e6-81fe-e889a5ddfded.png">
Or you can run it from Maven directly using the Spring Boot Maven plugin. If you do this it will pick up changes that you make in the project immediately (changes to Java source files require a compile as well - most people use an IDE for this):
```
./mvnw spring-boot:run
```
## In case you find a bug/suggested improvement for Spring Petclinic ## In case you find a bug/suggested improvement for Spring Petclinic
Our issue tracker is available here: https://github.com/spring-projects/spring-petclinic/issues Our issue tracker is available here: https://github.com/spring-projects/spring-petclinic/issues
## Database configuration ## Database configuration
In its default configuration, Petclinic uses an in-memory database (HSQLDB) which In its default configuration, Petclinic uses an in-memory database (H2) which
gets populated at startup with data. A similar setup is provided for MySql in case a persistent database configuration is needed. gets populated at startup with data. The h2 console is automatically exposed at `http://localhost:8080/h2-console`
Note that whenever the database type is changed, the data-access.properties file needs to be updated and the mysql-connector-java artifact from the pom.xml needs to be uncommented. and it is possible to inspect the content of the database using the `jdbc:h2:mem:testdb` url.
You could start a MySql database with docker: A similar setup is provided for MySql in case a persistent database configuration is needed. Note that whenever the database type is changed, the app needs to be run with a different profile: `spring.profiles.active=mysql` for MySql.
You could start MySql locally with whatever installer works for your OS, or with docker:
``` ```
docker run -e MYSQL_ROOT_PASSWORD=petclinic -e MYSQL_DATABASE=petclinic -p 3306:3306 mysql:5.7.8 docker run -e MYSQL_USER=petclinic -e MYSQL_PASSWORD=petclinic -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=petclinic -p 3306:3306 mysql:5.7.8
``` ```
## Working with Petclinic in Eclipse/STS Further documentation is provided [here](https://github.com/spring-projects/spring-petclinic/blob/main/src/main/resources/db/mysql/petclinic_db_setup_mysql.txt).
### prerequisites ## Working with Petclinic in your IDE
### Prerequisites
The following items should be installed in your system: The following items should be installed in your system:
* Maven 3 (http://www.sonatype.com/books/mvnref-book/reference/installation.html) * Java 8 or newer.
* git command line tool (https://help.github.com/articles/set-up-git) * git command line tool (https://help.github.com/articles/set-up-git)
* Eclipse with the m2e plugin (m2e is installed by default when using the STS (http://www.springsource.org/sts) distribution of Eclipse) * Your preferred IDE
* Eclipse with the m2e plugin. Note: when m2e is available, there is an m2 icon in `Help -> About` dialog. If m2e is
Note: when m2e is available, there is an m2 icon in Help -> About dialog. not there, just follow the install process here: https://www.eclipse.org/m2e/
If m2e is not there, just follow the install process here: http://eclipse.org/m2e/download/ * [Spring Tools Suite](https://spring.io/tools) (STS)
* IntelliJ IDEA
* [VS Code](https://code.visualstudio.com)
### Steps: ### Steps:
1) In the command line 1) On the command line
``` ```
git clone https://github.com/spring-projects/spring-petclinic.git git clone https://github.com/spring-projects/spring-petclinic.git
``` ```
2) Inside Eclipse 2) Inside Eclipse or STS
``` ```
File -> Import -> Maven -> Existing Maven project File -> Import -> Maven -> Existing Maven project
``` ```
Then either build on the command line `./mvnw generate-resources` or using the Eclipse launcher (right click on project and `Run As -> Maven install`) to generate the css. Run the application main method by right clicking on it and choosing `Run As -> Java Application`.
3) Inside IntelliJ IDEA
In the main menu, choose `File -> Open` and select the Petclinic [pom.xml](pom.xml). Click on the `Open` button.
CSS files are generated from the Maven build. You can either build them on the command line `./mvnw generate-resources` or right click on the `spring-petclinic` project then `Maven -> Generates sources and Update Folders`.
A run configuration named `PetClinicApplication` should have been created for you if you're using a recent Ultimate version. Otherwise, run the application by right clicking on the `PetClinicApplication` main class and choosing `Run 'PetClinicApplication'`.
4) Navigate to Petclinic
Visit [http://localhost:8080](http://localhost:8080) in your browser.
## Looking for something in particular? ## Looking for something in particular?
|Spring Boot Configuration | Class or Java property files | |Spring Boot Configuration | Class or Java property files |
|--------------------------|---| |--------------------------|---|
|The Main Class | [PetClinicApplication](https://github.com/spring-projects/spring-petclinic/blob/master/src/main/java/org/springframework/samples/petclinic/PetClinicApplication.java) | |The Main Class | [PetClinicApplication](https://github.com/spring-projects/spring-petclinic/blob/main/src/main/java/org/springframework/samples/petclinic/PetClinicApplication.java) |
|Properties Files | [application.properties](https://github.com/spring-projects/spring-petclinic/blob/master/src/main/resources) | |Properties Files | [application.properties](https://github.com/spring-projects/spring-petclinic/blob/main/src/main/resources) |
|Caching | [CacheConfig](https://github.com/spring-projects/spring-petclinic/blob/master/src/main/java/org/springframework/samples/petclinic/system/CacheConfig.java) | |Caching | [CacheConfiguration](https://github.com/spring-projects/spring-petclinic/blob/main/src/main/java/org/springframework/samples/petclinic/system/CacheConfiguration.java) |
## Interesting Spring Petclinic branches and forks ## Interesting Spring Petclinic branches and forks
The Spring Petclinic master branch in the main The Spring Petclinic "main" branch in the [spring-projects](https://github.com/spring-projects/spring-petclinic)
[spring-projects](https://github.com/spring-projects/spring-petclinic) GitHub org is the "canonical" implementation, currently based on Spring Boot and Thymeleaf. There are
GitHub org is the "canonical" implementation, currently based on [quite a few forks](https://spring-petclinic.github.io/docs/forks.html) in a special GitHub org
Spring Boot and Thymeleaf. There are quite a few forks in a special [spring-petclinic](https://github.com/spring-petclinic). If you have a special interest in a different technology stack
GitHub org [spring-petclinic](https://github.com/spring-petclinic). If that could be used to implement the Pet Clinic then please join the community there.
you have a special interest in a different technology stack that could
be used to implement the Pet Clinic then please join the community
there.
| Link | Main technologies |
|----------------|-------------------|
| [spring-framework-petclinic](https://github.com/spring-petclinic/spring-framework-petclinic) | Spring Framework XML configuration, JSP pages, 3 persistence layers: JDBC, JPA and Spring Data JPA |
| [javaconfig branch](https://github.com/spring-petclinic/spring-framework-petclinic/tree/javaconfig) | Same frameworks as the [spring-framework-petclinic](https://github.com/spring-petclinic/spring-framework-petclinic) but with Java Configuration instead of XML |
| [spring-petclinic-angular](https://github.com/spring-petclinic/spring-petclinic-angularjs) | AngularJS 1.x, Spring Boot and Spring Data JPA |
| [spring-petclinic-microservices](https://github.com/spring-petclinic/spring-petclinic-microservices) | Distributed version of Spring Petclinic built with Spring Cloud |
| [spring-petclinic-reactjs](https://github.com/spring-petclinic/spring-petclinic-reactjs) | ReactJS (with TypeScript) and Spring Boot |
## Interaction with other open source projects ## Interaction with other open source projects
@ -98,8 +115,19 @@ Here is a list of them:
The [issue tracker](https://github.com/spring-projects/spring-petclinic/issues) is the preferred channel for bug reports, features requests and submitting pull requests. The [issue tracker](https://github.com/spring-projects/spring-petclinic/issues) is the preferred channel for bug reports, features requests and submitting pull requests.
For pull requests, editor preferences are available in the [editor config](.editorconfig) for easy use in common text editors. Read more and download plugins at <http://editorconfig.org>. For pull requests, editor preferences are available in the [editor config](.editorconfig) for easy use in common text editors. Read more and download plugins at <https://editorconfig.org>. If you have not previously done so, please fill out and submit the [Contributor License Agreement](https://cla.pivotal.io/sign/spring).
# License
The Spring PetClinic sample application is released under version 2.0 of the [Apache License](https://www.apache.org/licenses/LICENSE-2.0).
[spring-petclinic]: https://github.com/spring-projects/spring-petclinic
[spring-framework-petclinic]: https://github.com/spring-petclinic/spring-framework-petclinic
[spring-petclinic-angularjs]: https://github.com/spring-petclinic/spring-petclinic-angularjs
[javaconfig branch]: https://github.com/spring-petclinic/spring-framework-petclinic/tree/javaconfig
[spring-petclinic-angular]: https://github.com/spring-petclinic/spring-petclinic-angular
[spring-petclinic-microservices]: https://github.com/spring-petclinic/spring-petclinic-microservices
[spring-petclinic-reactjs]: https://github.com/spring-petclinic/spring-petclinic-reactjs
[spring-petclinic-graphql]: https://github.com/spring-petclinic/spring-petclinic-graphql
[spring-petclinic-kotlin]: https://github.com/spring-petclinic/spring-petclinic-kotlin
[spring-petclinic-rest]: https://github.com/spring-petclinic/spring-petclinic-rest

View file

@ -1,13 +0,0 @@
# Required metadata
sonar.projectKey=java-sonar-runner-simple
sonar.projectName=Simple Java project analyzed with the SonarQube Runner
sonar.projectVersion=1.0
# Comma-separated paths to directories with sources (required)
sonar.sources=src
# Language
sonar.language=java
# Encoding of the source files
sonar.sourceEncoding=UTF-8

View file

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

View file

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

View file

@ -1,11 +1,11 @@
/* /*
* Copyright 2002-2014 the original author or authors. * Copyright 2012-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -25,10 +25,10 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
* @author Dave Syer * @author Dave Syer
* *
*/ */
@SpringBootApplication @SpringBootApplication(proxyBeanMethods = false)
public class PetClinicApplication { public class PetClinicApplication {
public static void main(String[] args) throws Exception { public static void main(String[] args) {
SpringApplication.run(PetClinicApplication.class, args); SpringApplication.run(PetClinicApplication.class, args);
} }

View file

@ -1,11 +1,11 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2012-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -31,6 +31,7 @@ import javax.persistence.MappedSuperclass;
*/ */
@MappedSuperclass @MappedSuperclass
public class BaseEntity implements Serializable { public class BaseEntity implements Serializable {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id; private Integer id;

View file

@ -1,11 +1,11 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2012-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -18,10 +18,9 @@ package org.springframework.samples.petclinic.model;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.MappedSuperclass; import javax.persistence.MappedSuperclass;
/** /**
* Simple JavaBean domain object adds a name property to <code>BaseEntity</code>. Used as a base class for objects * Simple JavaBean domain object adds a name property to <code>BaseEntity</code>. Used as
* needing these properties. * a base class for objects needing these properties.
* *
* @author Ken Krebs * @author Ken Krebs
* @author Juergen Hoeller * @author Juergen Hoeller

View file

@ -1,11 +1,11 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2012-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -17,8 +17,7 @@ package org.springframework.samples.petclinic.model;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.MappedSuperclass; import javax.persistence.MappedSuperclass;
import javax.validation.constraints.NotEmpty;
import org.hibernate.validator.constraints.NotEmpty;
/** /**
* Simple JavaBean domain object representing an person. * Simple JavaBean domain object representing an person.

View file

@ -1,5 +1,20 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/** /**
* The classes in this package represent utilities used by the domain. * The classes in this package represent utilities used by the domain.
*/ */
package org.springframework.samples.petclinic.model; package org.springframework.samples.petclinic.model;

View file

@ -1,11 +1,11 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2012-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -27,8 +27,8 @@ import javax.persistence.Entity;
import javax.persistence.OneToMany; 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 org.hibernate.validator.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;
@ -45,6 +45,7 @@ import org.springframework.samples.petclinic.model.Person;
@Entity @Entity
@Table(name = "owners") @Table(name = "owners")
public class Owner extends Person { public class Owner extends Person {
@Column(name = "address") @Column(name = "address")
@NotEmpty @NotEmpty
private String address; private String address;
@ -61,7 +62,6 @@ public class Owner extends Person {
@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;
} }
@ -112,7 +112,6 @@ public class Owner extends Person {
/** /**
* Return the Pet with the given name, or null if none found for this Owner. * Return the Pet with the given name, or null if none found for this Owner.
*
* @param name to test * @param name to test
* @return true if pet name is already in use * @return true if pet name is already in use
*/ */
@ -122,7 +121,6 @@ public class Owner extends Person {
/** /**
* Return the Pet with the given name, or null if none found for this Owner. * Return the Pet with the given name, or null if none found for this Owner.
*
* @param name to test * @param name to test
* @return true if pet name is already in use * @return true if pet name is already in use
*/ */
@ -144,13 +142,9 @@ public class Owner extends Person {
public String toString() { public String toString() {
return new ToStringCreator(this) return new ToStringCreator(this)
.append("id", this.getId()) .append("id", this.getId()).append("new", this.isNew()).append("lastName", this.getLastName())
.append("new", this.isNew()) .append("firstName", this.getFirstName()).append("address", this.address).append("city", this.city)
.append("lastName", this.getLastName()) .append("telephone", this.telephone).toString();
.append("firstName", this.getFirstName())
.append("address", this.address)
.append("city", this.city)
.append("telephone", this.telephone)
.toString();
} }
} }

View file

@ -1,11 +1,11 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2012-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -15,22 +15,21 @@
*/ */
package org.springframework.samples.petclinic.owner; package org.springframework.samples.petclinic.owner;
import java.util.Collection; import org.springframework.samples.petclinic.visit.VisitRepository;
import java.util.Map;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
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.GetMapping;
import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
import javax.validation.Valid;
import java.util.Collection;
import java.util.Map;
/** /**
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Ken Krebs * @author Ken Krebs
@ -41,12 +40,14 @@ import org.springframework.web.servlet.ModelAndView;
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;
@Autowired public OwnerController(OwnerRepository clinicService, VisitRepository visits) {
public OwnerController(OwnerRepository clinicService) {
this.owners = clinicService; this.owners = clinicService;
this.visits = visits;
} }
@InitBinder @InitBinder
@ -54,30 +55,31 @@ class OwnerController {
dataBinder.setDisallowedFields("id"); dataBinder.setDisallowedFields("id");
} }
@RequestMapping(value = "/owners/new", method = RequestMethod.GET) @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;
} }
@RequestMapping(value = "/owners/new", method = RequestMethod.POST) @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();
} }
} }
@RequestMapping(value = "/owners/find", method = RequestMethod.GET) @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";
} }
@RequestMapping(value = "/owners", method = RequestMethod.GET) @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
@ -91,29 +93,33 @@ class OwnerController {
// no owners found // no owners found
result.rejectValue("lastName", "notFound", "not found"); result.rejectValue("lastName", "notFound", "not found");
return "owners/findOwners"; return "owners/findOwners";
} else if (results.size() == 1) { }
else if (results.size() == 1) {
// 1 owner found // 1 owner found
owner = results.iterator().next(); owner = results.iterator().next();
return "redirect:/owners/" + owner.getId(); return "redirect:/owners/" + owner.getId();
} else { }
else {
// multiple owners found // multiple owners found
model.put("selections", results); model.put("selections", results);
return "owners/ownersList"; return "owners/ownersList";
} }
} }
@RequestMapping(value = "/owners/{ownerId}/edit", method = RequestMethod.GET) @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;
} }
@RequestMapping(value = "/owners/{ownerId}/edit", method = RequestMethod.POST) @PostMapping("/owners/{ownerId}/edit")
public String processUpdateOwnerForm(@Valid Owner owner, BindingResult result, @PathVariable("ownerId") int ownerId) { public String processUpdateOwnerForm(@Valid Owner owner, BindingResult result,
@PathVariable("ownerId") int ownerId) {
if (result.hasErrors()) { if (result.hasErrors()) {
return VIEWS_OWNER_CREATE_OR_UPDATE_FORM; return VIEWS_OWNER_CREATE_OR_UPDATE_FORM;
} else { }
else {
owner.setId(ownerId); owner.setId(ownerId);
this.owners.save(owner); this.owners.save(owner);
return "redirect:/owners/{ownerId}"; return "redirect:/owners/{ownerId}";
@ -122,14 +128,17 @@ class OwnerController {
/** /**
* Custom handler for displaying an owner. * Custom handler for displaying an owner.
*
* @param ownerId the ID of the owner to display * @param ownerId the ID of the owner to display
* @return a ModelMap with the model attributes for the view * @return a ModelMap with the model attributes for the view
*/ */
@RequestMapping("/owners/{ownerId}") @GetMapping("/owners/{ownerId}")
public ModelAndView showOwner(@PathVariable("ownerId") int ownerId) { public ModelAndView showOwner(@PathVariable("ownerId") int ownerId) {
ModelAndView mav = new ModelAndView("owners/ownerDetails"); ModelAndView mav = new ModelAndView("owners/ownerDetails");
mav.addObject(this.owners.findById(ownerId)); Owner owner = this.owners.findById(ownerId);
for (Pet pet : owner.getPets()) {
pet.setVisitsInternal(visits.findByPetId(pet.getId()));
}
mav.addObject(owner);
return mav; return mav;
} }

View file

@ -1,11 +1,11 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2012-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -23,8 +23,10 @@ import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
/** /**
* Repository class for <code>Owner</code> domain objects All method names are compliant with Spring Data naming * Repository class for <code>Owner</code> domain objects All method names are compliant
* conventions so this interface can easily be extended for Spring Data See here: http://static.springsource.org/spring-data/jpa/docs/current/reference/html/jpa.repositories.html#jpa.query-methods.query-creation * with Spring Data naming conventions so this interface can easily be extended for Spring
* Data. See:
* https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-creation
* *
* @author Ken Krebs * @author Ken Krebs
* @author Juergen Hoeller * @author Juergen Hoeller
@ -59,5 +61,4 @@ public interface OwnerRepository extends Repository<Owner, Integer> {
*/ */
void save(Owner owner); void save(Owner owner);
} }

View file

@ -1,11 +1,11 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2012-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -15,24 +15,21 @@
*/ */
package org.springframework.samples.petclinic.owner; package org.springframework.samples.petclinic.owner;
import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.HashSet; 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.CascadeType;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn; import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne; import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table; import javax.persistence.Table;
import javax.persistence.Temporal; import javax.persistence.Transient;
import javax.persistence.TemporalType;
import org.springframework.beans.support.MutableSortDefinition; import org.springframework.beans.support.MutableSortDefinition;
import org.springframework.beans.support.PropertyComparator; import org.springframework.beans.support.PropertyComparator;
@ -52,9 +49,8 @@ import org.springframework.samples.petclinic.visit.Visit;
public class Pet extends NamedEntity { public class Pet extends NamedEntity {
@Column(name = "birth_date") @Column(name = "birth_date")
@Temporal(TemporalType.DATE) @DateTimeFormat(pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy/MM/dd") private LocalDate birthDate;
private Date birthDate;
@ManyToOne @ManyToOne
@JoinColumn(name = "type_id") @JoinColumn(name = "type_id")
@ -64,14 +60,14 @@ public class Pet extends NamedEntity {
@JoinColumn(name = "owner_id") @JoinColumn(name = "owner_id")
private Owner owner; private Owner owner;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "petId", fetch = FetchType.EAGER) @Transient
private Set<Visit> visits = new LinkedHashSet<>(); private Set<Visit> visits = new LinkedHashSet<>();
public void setBirthDate(Date birthDate) { public void setBirthDate(LocalDate birthDate) {
this.birthDate = birthDate; this.birthDate = birthDate;
} }
public Date getBirthDate() { public LocalDate getBirthDate() {
return this.birthDate; return this.birthDate;
} }
@ -98,14 +94,13 @@ public class Pet extends NamedEntity {
return this.visits; return this.visits;
} }
protected void setVisitsInternal(Set<Visit> visits) { protected void setVisitsInternal(Collection<Visit> visits) {
this.visits = visits; this.visits = new LinkedHashSet<>(visits);
} }
public List<Visit> getVisits() { public List<Visit> getVisits() {
List<Visit> sortedVisits = new ArrayList<>(getVisitsInternal()); List<Visit> sortedVisits = new ArrayList<>(getVisitsInternal());
PropertyComparator.sort(sortedVisits, PropertyComparator.sort(sortedVisits, new MutableSortDefinition("date", false, false));
new MutableSortDefinition("date", false, false));
return Collections.unmodifiableList(sortedVisits); return Collections.unmodifiableList(sortedVisits);
} }

View file

@ -1,11 +1,11 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2012-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -15,21 +15,15 @@
*/ */
package org.springframework.samples.petclinic.owner; package org.springframework.samples.petclinic.owner;
import java.util.Collection;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
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.InitBinder; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable; import javax.validation.Valid;
import org.springframework.web.bind.annotation.RequestMapping; import java.util.Collection;
import org.springframework.web.bind.annotation.RequestMethod;
/** /**
* @author Juergen Hoeller * @author Juergen Hoeller
@ -41,10 +35,11 @@ import org.springframework.web.bind.annotation.RequestMethod;
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;
@Autowired
public PetController(PetRepository pets, OwnerRepository owners) { public PetController(PetRepository pets, OwnerRepository owners) {
this.pets = pets; this.pets = pets;
this.owners = owners; this.owners = owners;
@ -70,7 +65,7 @@ class PetController {
dataBinder.setValidator(new PetValidator()); dataBinder.setValidator(new PetValidator());
} }
@RequestMapping(value = "/pets/new", method = RequestMethod.GET) @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);
@ -78,7 +73,7 @@ class PetController {
return VIEWS_PETS_CREATE_OR_UPDATE_FORM; return VIEWS_PETS_CREATE_OR_UPDATE_FORM;
} }
@RequestMapping(value = "/pets/new", method = RequestMethod.POST) @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");
@ -87,26 +82,28 @@ class PetController {
if (result.hasErrors()) { if (result.hasErrors()) {
model.put("pet", pet); model.put("pet", pet);
return VIEWS_PETS_CREATE_OR_UPDATE_FORM; return VIEWS_PETS_CREATE_OR_UPDATE_FORM;
} else { }
else {
this.pets.save(pet); this.pets.save(pet);
return "redirect:/owners/{ownerId}"; return "redirect:/owners/{ownerId}";
} }
} }
@RequestMapping(value = "/pets/{petId}/edit", method = RequestMethod.GET) @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;
} }
@RequestMapping(value = "/pets/{petId}/edit", method = RequestMethod.POST) @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

@ -1,11 +1,11 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2012-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -22,8 +22,10 @@ import org.springframework.data.repository.Repository;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
/** /**
* Repository class for <code>Pet</code> domain objects All method names are compliant with Spring Data naming * Repository class for <code>Pet</code> domain objects All method names are compliant
* conventions so this interface can easily be extended for Spring Data See here: http://static.springsource.org/spring-data/jpa/docs/current/reference/html/jpa.repositories.html#jpa.query-methods.query-creation * with Spring Data naming conventions so this interface can easily be extended for Spring
* Data. See:
* https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-creation
* *
* @author Ken Krebs * @author Ken Krebs
* @author Juergen Hoeller * @author Juergen Hoeller
@ -55,4 +57,3 @@ public interface PetRepository extends Repository<Pet, Integer> {
void save(Pet pet); void save(Pet pet);
} }

View file

@ -1,11 +1,11 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2012-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -21,8 +21,7 @@ import javax.persistence.Table;
import org.springframework.samples.petclinic.model.NamedEntity; import org.springframework.samples.petclinic.model.NamedEntity;
/** /**
* @author Juergen Hoeller * @author Juergen Hoeller Can be Cat, Dog, Hamster...
* Can be Cat, Dog, Hamster...
*/ */
@Entity @Entity
@Table(name = "types") @Table(name = "types")

View file

@ -1,11 +1,11 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2012-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -15,7 +15,6 @@
*/ */
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;
@ -25,11 +24,10 @@ import org.springframework.format.Formatter;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
* Instructs Spring MVC on how to parse and print elements of type 'PetType'. Starting from Spring 3.0, Formatters have * Instructs Spring MVC on how to parse and print elements of type 'PetType'. Starting
* come as an improvement in comparison to legacy PropertyEditors. See the following links for more details: - The * from Spring 3.0, Formatters have come as an improvement in comparison to legacy
* Spring ref doc: http://static.springsource.org/spring/docs/current/spring-framework-reference/html/validation.html#format-Formatter-SPI * PropertyEditors. See the following links for more details: - The Spring ref doc:
* - A nice blog entry from Gordon Dickens: http://gordondickens.com/wordpress/2010/09/30/using-spring-3-0-custom-type-converter/ * https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#format
* <p/>
* *
* @author Mark Fisher * @author Mark Fisher
* @author Juergen Hoeller * @author Juergen Hoeller
@ -40,7 +38,6 @@ public class PetTypeFormatter implements Formatter<PetType> {
private final PetRepository pets; private final PetRepository pets;
@Autowired @Autowired
public PetTypeFormatter(PetRepository pets) { public PetTypeFormatter(PetRepository pets) {
this.pets = pets; this.pets = pets;

View file

@ -1,11 +1,11 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2012-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -22,7 +22,8 @@ import org.springframework.validation.Validator;
/** /**
* <code>Validator</code> for <code>Pet</code> forms. * <code>Validator</code> for <code>Pet</code> forms.
* <p> * <p>
* We're not using Bean Validation annotations here because it is easier to define such validation rule in Java. * We're not using Bean Validation annotations here because it is easier to define such
* validation rule in Java.
* </p> * </p>
* *
* @author Ken Krebs * @author Ken Krebs
@ -60,5 +61,4 @@ public class PetValidator implements Validator {
return Pet.class.isAssignableFrom(clazz); return Pet.class.isAssignableFrom(clazz);
} }
} }

View file

@ -1,11 +1,11 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2012-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -19,17 +19,16 @@ import java.util.Map;
import javax.validation.Valid; import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
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;
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.GetMapping;
import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/** /**
* @author Juergen Hoeller * @author Juergen Hoeller
@ -42,10 +41,9 @@ import org.springframework.web.bind.annotation.RequestMethod;
class VisitController { class VisitController {
private final VisitRepository visits; private final VisitRepository visits;
private final PetRepository pets; private final PetRepository pets;
@Autowired
public VisitController(VisitRepository visits, PetRepository pets) { public VisitController(VisitRepository visits, PetRepository pets) {
this.visits = visits; this.visits = visits;
this.pets = pets; this.pets = pets;
@ -57,18 +55,16 @@ class VisitController {
} }
/** /**
* Called before each and every @RequestMapping annotated method. * Called before each and every @RequestMapping annotated method. 2 goals: - Make sure
* 2 goals: * we always have fresh data - Since we do not use the session scope, make sure that
* - Make sure we always have fresh data * Pet object always has an id (Even though id is not part of the form fields)
* - Since we do not use the session scope, make sure that Pet object always has an id
* (Even though id is not part of the form fields)
*
* @param petId * @param petId
* @return Pet * @return Pet
*/ */
@ModelAttribute("visit") @ModelAttribute("visit")
public Visit loadPetWithVisit(@PathVariable("petId") int petId, Map<String, Object> model) { public Visit loadPetWithVisit(@PathVariable("petId") int petId, Map<String, Object> model) {
Pet pet = this.pets.findById(petId); Pet pet = this.pets.findById(petId);
pet.setVisitsInternal(this.visits.findByPetId(petId));
model.put("pet", pet); model.put("pet", pet);
Visit visit = new Visit(); Visit visit = new Visit();
pet.addVisit(visit); pet.addVisit(visit);
@ -76,17 +72,18 @@ class VisitController {
} }
// Spring MVC calls method loadPetWithVisit(...) before initNewVisitForm is called // Spring MVC calls method loadPetWithVisit(...) before initNewVisitForm is called
@RequestMapping(value = "/owners/*/pets/{petId}/visits/new", method = RequestMethod.GET) @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
@RequestMapping(value = "/owners/{ownerId}/pets/{petId}/visits/new", method = RequestMethod.POST) @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

@ -1,32 +0,0 @@
package org.springframework.samples.petclinic.system;
import javax.cache.configuration.Configuration;
import javax.cache.configuration.MutableConfiguration;
import org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Profile;
/**
* Cache could be disabled in unit test.
*/
@org.springframework.context.annotation.Configuration
@EnableCaching
@Profile("production")
class CacheConfig {
@Bean
public JCacheManagerCustomizer cacheManagerCustomizer() {
return cm -> {
Configuration<Object, Object> cacheConfiguration = createCacheConfiguration();
cm.createCache("vets", cacheConfiguration);
};
}
private Configuration<Object, Object> createCacheConfiguration() {
// Create a cache using infinite heap. A real application will want to use an
// implementation dependent configuration that will better fit your needs
return new MutableConfiguration<>().setStatisticsEnabled(true);
}
}

View file

@ -0,0 +1,55 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.system;
import javax.cache.configuration.MutableConfiguration;
import org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Cache configuration intended for caches providing the JCache API. This configuration
* creates the used cache for the application and enables statistics that become
* accessible via JMX.
*/
@Configuration(proxyBeanMethods = false)
@EnableCaching
class CacheConfiguration {
@Bean
public JCacheManagerCustomizer petclinicCacheConfigurationCustomizer() {
return cm -> {
cm.createCache("vets", cacheConfiguration());
};
}
/**
* Create a simple configuration that enable statistics via the JCache programmatic
* configuration API.
* <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
* configuration options (like the size limit) must be set via a configuration
* mechanism that is provided by the selected JCache implementation.
*/
private javax.cache.configuration.Configuration<Object, Object> cacheConfiguration() {
return new MutableConfiguration<>().setStatisticsEnabled(true);
}
}

View file

@ -1,11 +1,11 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2012-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -16,8 +16,7 @@
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.RequestMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/** /**
* Controller used to showcase what happens when an exception is thrown * Controller used to showcase what happens when an exception is thrown
@ -29,7 +28,7 @@ import org.springframework.web.bind.annotation.RequestMethod;
@Controller @Controller
class CrashController { class CrashController {
@RequestMapping(value = "/oups", method = RequestMethod.GET) @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

@ -1,14 +1,30 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.system; package org.springframework.samples.petclinic.system;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.GetMapping;
@Controller @Controller
class WelcomeController { class WelcomeController {
@RequestMapping("/") @GetMapping("/")
public String welcome() { public String welcome() {
return "welcome"; return "welcome";
} }
} }

View file

@ -1,11 +1,11 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2012-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,

View file

@ -1,11 +1,11 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2012-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -46,7 +46,8 @@ import org.springframework.samples.petclinic.model.Person;
public class Vet extends Person { public class Vet extends Person {
@ManyToMany(fetch = FetchType.EAGER) @ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "vet_specialties", joinColumns = @JoinColumn(name = "vet_id"), inverseJoinColumns = @JoinColumn(name = "specialty_id")) @JoinTable(name = "vet_specialties", joinColumns = @JoinColumn(name = "vet_id"),
inverseJoinColumns = @JoinColumn(name = "specialty_id"))
private Set<Specialty> specialties; private Set<Specialty> specialties;
protected Set<Specialty> getSpecialtiesInternal() { protected Set<Specialty> getSpecialtiesInternal() {
@ -63,8 +64,7 @@ public class Vet extends Person {
@XmlElement @XmlElement
public List<Specialty> getSpecialties() { public List<Specialty> getSpecialties() {
List<Specialty> sortedSpecs = new ArrayList<>(getSpecialtiesInternal()); List<Specialty> sortedSpecs = new ArrayList<>(getSpecialtiesInternal());
PropertyComparator.sort(sortedSpecs, PropertyComparator.sort(sortedSpecs, new MutableSortDefinition("name", true, true));
new MutableSortDefinition("name", true, true));
return Collections.unmodifiableList(sortedSpecs); return Collections.unmodifiableList(sortedSpecs);
} }

View file

@ -1,11 +1,11 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2012-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -15,13 +15,12 @@
*/ */
package org.springframework.samples.petclinic.vet; package org.springframework.samples.petclinic.vet;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping; 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;
/** /**
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Mark Fisher * @author Mark Fisher
@ -33,12 +32,11 @@ class VetController {
private final VetRepository vets; private final VetRepository vets;
@Autowired
public VetController(VetRepository clinicService) { public VetController(VetRepository clinicService) {
this.vets = clinicService; this.vets = clinicService;
} }
@RequestMapping(value = { "/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
@ -48,7 +46,7 @@ class VetController {
return "vets/vetList"; return "vets/vetList";
} }
@RequestMapping(value = { "/vets.json", "/vets.xml" }) @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

View file

@ -1,11 +1,11 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2012-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -23,8 +23,10 @@ import org.springframework.data.repository.Repository;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
/** /**
* Repository class for <code>Vet</code> domain objects All method names are compliant with Spring Data naming * Repository class for <code>Vet</code> domain objects All method names are compliant
* conventions so this interface can easily be extended for Spring Data See here: http://static.springsource.org/spring-data/jpa/docs/current/reference/html/jpa.repositories.html#jpa.query-methods.query-creation * with Spring Data naming conventions so this interface can easily be extended for Spring
* Data. See:
* https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-creation
* *
* @author Ken Krebs * @author Ken Krebs
* @author Juergen Hoeller * @author Juergen Hoeller
@ -35,12 +37,10 @@ public interface VetRepository extends Repository<Vet, Integer> {
/** /**
* Retrieve all <code>Vet</code>s from the data store. * Retrieve all <code>Vet</code>s from the data store.
*
* @return a <code>Collection</code> of <code>Vet</code>s * @return a <code>Collection</code> of <code>Vet</code>s
*/ */
@Transactional(readOnly = true) @Transactional(readOnly = true)
@Cacheable("vets") @Cacheable("vets")
Collection<Vet> findAll() throws DataAccessException; Collection<Vet> findAll() throws DataAccessException;
} }

View file

@ -1,11 +1,11 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2012-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -22,8 +22,8 @@ import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlRootElement;
/** /**
* Simple domain object representing a list of veterinarians. Mostly here to be used for the 'vets' {@link * Simple domain object representing a list of veterinarians. Mostly here to be used for
* org.springframework.web.servlet.view.xml.MarshallingView}. * the 'vets' {@link org.springframework.web.servlet.view.xml.MarshallingView}.
* *
* @author Arjen Poutsma * @author Arjen Poutsma
*/ */

View file

@ -1,11 +1,11 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2012-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -15,15 +15,13 @@
*/ */
package org.springframework.samples.petclinic.visit; package org.springframework.samples.petclinic.visit;
import java.util.Date; 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.persistence.Temporal; import javax.validation.constraints.NotEmpty;
import javax.persistence.TemporalType;
import org.hibernate.validator.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;
@ -37,86 +35,44 @@ import org.springframework.samples.petclinic.model.BaseEntity;
@Table(name = "visits") @Table(name = "visits")
public class Visit extends BaseEntity { public class Visit extends BaseEntity {
/**
* Holds value of property date.
*/
@Column(name = "visit_date") @Column(name = "visit_date")
@Temporal(TemporalType.TIMESTAMP) @DateTimeFormat(pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy/MM/dd") private LocalDate date;
private Date date;
/**
* Holds value of property description.
*/
@NotEmpty @NotEmpty
@Column(name = "description") @Column(name = "description")
private String description; private String description;
/**
* Holds value of property pet.
*/
@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 = new Date(); this.date = LocalDate.now();
} }
public LocalDate getDate() {
/**
* Getter for property date.
*
* @return Value of property date.
*/
public Date getDate() {
return this.date; return this.date;
} }
/** public void setDate(LocalDate date) {
* Setter for property date.
*
* @param date New value of property date.
*/
public void setDate(Date date) {
this.date = date; this.date = date;
} }
/**
* Getter for property description.
*
* @return Value of property description.
*/
public String getDescription() { public String getDescription() {
return this.description; return this.description;
} }
/**
* Setter for property description.
*
* @param description New value of property description.
*/
public void setDescription(String description) { public void setDescription(String description) {
this.description = description; this.description = description;
} }
/**
* Getter for property pet id.
*
* @return Value of property pet id.
*/
public Integer getPetId() { public Integer getPetId() {
return this.petId; return this.petId;
} }
/**
* Setter for property pet id.
*
* @param petId New value of property pet id.
*/
public void setPetId(Integer petId) { public void setPetId(Integer petId) {
this.petId = petId; this.petId = petId;
} }

View file

@ -1,11 +1,11 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2012-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -22,8 +22,10 @@ import org.springframework.data.repository.Repository;
import org.springframework.samples.petclinic.model.BaseEntity; import org.springframework.samples.petclinic.model.BaseEntity;
/** /**
* Repository class for <code>Visit</code> domain objects All method names are compliant with Spring Data naming * Repository class for <code>Visit</code> domain objects All method names are compliant
* conventions so this interface can easily be extended for Spring Data See here: http://static.springsource.org/spring-data/jpa/docs/current/reference/html/jpa.repositories.html#jpa.query-methods.query-creation * with Spring Data naming conventions so this interface can easily be extended for Spring
* Data. See:
* https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-creation
* *
* @author Ken Krebs * @author Ken Krebs
* @author Juergen Hoeller * @author Juergen Hoeller
@ -34,7 +36,6 @@ public interface VisitRepository extends Repository<Visit, Integer> {
/** /**
* Save a <code>Visit</code> to the data store, either inserting or updating it. * Save a <code>Visit</code> to the data store, either inserting or updating it.
*
* @param visit the <code>Visit</code> to save * @param visit the <code>Visit</code> to save
* @see BaseEntity#isNew * @see BaseEntity#isNew
*/ */

View file

@ -3,7 +3,7 @@
* *
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,

View file

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

View file

@ -1,5 +1,5 @@
# database init, supports mysql too # database init, supports mysql too
database=hsqldb database=h2
spring.datasource.schema=classpath*:db/${database}/schema.sql spring.datasource.schema=classpath*:db/${database}/schema.sql
spring.datasource.data=classpath*:db/${database}/data.sql spring.datasource.data=classpath*:db/${database}/data.sql
@ -8,19 +8,18 @@ spring.thymeleaf.mode=HTML
# JPA # JPA
spring.jpa.hibernate.ddl-auto=none spring.jpa.hibernate.ddl-auto=none
spring.jpa.open-in-view=false
# Internationalization # Internationalization
spring.messages.basename=messages/messages spring.messages.basename=messages/messages
# Actuator / Management # Actuator
management.contextPath=/manage management.endpoints.web.exposure.include=*
# Spring Boot 1.5 makes actuator secure by default
management.security.enabled=false
# Logging # Logging
logging.level.org.springframework=INFO logging.level.org.springframework=INFO
# logging.level.org.springframework.web=DEBUG # logging.level.org.springframework.web=DEBUG
# logging.level.org.springframework.context.annotation=TRACE # logging.level.org.springframework.context.annotation=TRACE
# Active Spring profiles # Maximum time static resources should be cached
spring.profiles.active=production spring.resources.cache.cachecontrol.max-age=12h

View file

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

View file

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

View file

@ -9,9 +9,24 @@
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
1) Download and install the MySQL database (e.g., MySQL Community Server 5.1.x), 1) Download and install the MySQL database (e.g., MySQL Community Server 5.1.x),
which can be found here: http://dev.mysql.com/downloads/. Or run the which can be found here: https://dev.mysql.com/downloads/. Or run the
"docker-compose.yml" from the root of the project (if you have docker installed "docker-compose.yml" from the root of the project (if you have docker installed
locally). locally):
2) Create the PetClinic database and user by executing the "db/mysql/{schema,data}.sql" $ docker-compose up
scripts (or set "spring.datasource.initialize=true" the first time you run the app). ...
mysql_1_eedb4818d817 | MySQL init process done. Ready for start up.
...
2) (Once only) create the PetClinic database and user by executing the "db/mysql/user.sql"
scripts. You can connect to the database running in the docker container using
`mysql -u root -h localhost --protocol tcp`, but you don't need to run the script there
because the petclinic user is already set up if you use the provided `docker-compose.yaml`.
3) Run the app with `spring.profiles.active=mysql` (e.g. as a System property via the command
line, but any way that sets that property in a Spring Boot app should work).
N.B. the "petclinic" database has to exist for the app to work with the JDBC URL value
as it is configured by default. This condition is taken care of automatically by the
docker-compose configuration provided, or by the `user.sql` script if you run that as
root.

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" th:replace="~{fragments/layout :: layout (~{::body},'error')}"> <html xmlns:th="https://www.thymeleaf.org" th:replace="~{fragments/layout :: layout (~{::body},'error')}">
<body> <body>
<img src="../static/resources/images/pets.png" th:src="@{/resources/images/pets.png}"/> <img src="../static/resources/images/pets.png" th:src="@{/resources/images/pets.png}"/>

View file

@ -1,14 +1,18 @@
<html> <html>
<body> <body>
<form> <form>
<th:block th:fragment="input (label, name)"> <th:block th:fragment="input (label, name, type)">
<div th:with="valid=${!#fields.hasErrors(name)}" <div th:with="valid=${!#fields.hasErrors(name)}"
th:class="${'form-group' + (valid ? '' : ' has-error')}" th:class="${'form-group' + (valid ? '' : ' has-error')}"
class="form-group"> class="form-group">
<label class="col-sm-2 control-label" th:text="${label}">Label</label> <label class="col-sm-2 control-label" th:text="${label}">Label</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input class="form-control" type="text" <div th:switch="${type}">
th:field="*{__${name}__}" /> <input th:case="'text'" class="form-control" type="text" th:field="*{__${name}__}" />
<input th:case="'date'" class="form-control" type="text" th:field="*{__${name}__}"
placeholder="YYYY-MM-DD" title="Enter a date in this format: YYYY-MM-DD"
pattern="(?:19|20)[0-9]{2}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-9])|(?:(?!02)(?:0[1-9]|1[0-2])-(?:30))|(?:(?:0[13578]|1[02])-31))"/>
</div>
<span th:if="${valid}" <span th:if="${valid}"
class="glyphicon glyphicon-ok form-control-feedback" class="glyphicon glyphicon-ok form-control-feedback"
aria-hidden="true"></span> aria-hidden="true"></span>

View file

@ -28,22 +28,31 @@
<div class="navbar-header"> <div class="navbar-header">
<a class="navbar-brand" th:href="@{/}"><span></span></a> <a class="navbar-brand" th:href="@{/}"><span></span></a>
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#main-navbar"> <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#main-navbar">
<span class="sr-only"><os-p>Toggle navigation</os-p></span> <span class="sr-only">
<os-p>Toggle navigation</os-p>
</span>
<span class="icon-bar"></span> <span class="icon-bar"></span>
<span class="icon-bar"></span> <span class="icon-bar"></span>
<span class="icon-bar"></span> <span class="icon-bar"></span>
</button> </button>
</div> </div>
<div class="navbar-collapse collapse" id="main-navbar"> <div class="navbar-collapse collapse" id="main-navbar">
<ul class="nav navbar-nav navbar-right">
<li th:fragment="menuItem (path,active,title,glyph,text)" class="active" th:class="${active==menu ? 'active' : ''}"> <ul class="nav navbar-nav navbar-right" th:remove="all">
<a th:href="@{__${path}__}" th:title="${title}">
<span th:class="'glyphicon glyphicon-'+${glyph}" class="glyphicon glyphicon-home" aria-hidden="true"></span> <li th:fragment="menuItem (link,active,title,glyph,text)" class="active"
th:class="${active==menu ? 'active' : ''}">
<a th:href="@{__${link}__}" th:title="${title}">
<span th:class="'glyphicon glyphicon-'+${glyph}" class="glyphicon glyphicon-home"
aria-hidden="true"></span>
<span th:text="${text}">Template</span> <span th:text="${text}">Template</span>
</a> </a>
</li> </li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li th:replace="::menuItem ('/','home','home page','home','Home')"> <li th:replace="::menuItem ('/','home','home page','home','Home')">
<span class="glyphicon glyphicon-home" aria-hidden="true"></span> <span class="glyphicon glyphicon-home" aria-hidden="true"></span>
<span>Home</span> <span>Home</span>
@ -59,7 +68,8 @@
<span>Veterinarians</span> <span>Veterinarians</span>
</li> </li>
<li th:replace="::menuItem ('/oups','error','trigger a RuntimeException to see how it is handled','warning-sign','Error')"> <li
th:replace="::menuItem ('/oups','error','trigger a RuntimeException to see how it is handled','warning-sign','Error')">
<span class="glyphicon glyphicon-warning-sign" aria-hidden="true"></span> <span class="glyphicon glyphicon-warning-sign" aria-hidden="true"></span>
<span>Error</span> <span>Error</span>
</li> </li>
@ -71,15 +81,15 @@
<div class="container-fluid"> <div class="container-fluid">
<div class="container xd-container"> <div class="container xd-container">
<div th:replace="${template}"/> <th:block th:include="${template}" />
<br /> <br />
<br /> <br />
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-12 text-center"> <div class="col-12 text-center">
<img src="../static/resources/images/spring-pivotal-logo.png" th:src="@{/resources/images/spring-pivotal-logo.png}" <img src="../static/resources/images/spring-pivotal-logo.png"
alt="Sponsored by Pivotal"/></div> th:src="@{/resources/images/spring-pivotal-logo.png}" alt="Sponsored by Pivotal" /></div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,4 +1,4 @@
<html xmlns:th="http://www.thymeleaf.org" <html xmlns:th="https://www.thymeleaf.org"
th:replace="~{fragments/layout :: layout (~{::body},'owners')}"> th:replace="~{fragments/layout :: layout (~{::body},'owners')}">
<body> <body>
@ -7,15 +7,15 @@
<form th:object="${owner}" class="form-horizontal" id="add-owner-form" method="post"> <form th:object="${owner}" class="form-horizontal" id="add-owner-form" method="post">
<div class="form-group has-feedback"> <div class="form-group has-feedback">
<input <input
th:replace="~{fragments/inputField :: input ('First Name', 'firstName')}" /> th:replace="~{fragments/inputField :: input ('First Name', 'firstName', 'text')}" />
<input <input
th:replace="~{fragments/inputField :: input ('Last Name', 'lastName')}" /> th:replace="~{fragments/inputField :: input ('Last Name', 'lastName', 'text')}" />
<input <input
th:replace="~{fragments/inputField :: input ('Address', 'address')}" /> th:replace="~{fragments/inputField :: input ('Address', 'address', 'text')}" />
<input <input
th:replace="~{fragments/inputField :: input ('City', 'city')}" /> th:replace="~{fragments/inputField :: input ('City', 'city', 'text')}" />
<input <input
th:replace="~{fragments/inputField :: input ('Telephone', 'telephone')}" /> th:replace="~{fragments/inputField :: input ('Telephone', 'telephone', 'text')}" />
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="col-sm-offset-2 col-sm-10"> <div class="col-sm-offset-2 col-sm-10">

View file

@ -1,4 +1,4 @@
<html xmlns:th="http://www.thymeleaf.org" <html xmlns:th="https://www.thymeleaf.org"
th:replace="~{fragments/layout :: layout (~{::body},'owners')}"> th:replace="~{fragments/layout :: layout (~{::body},'owners')}">
<body> <body>
@ -8,7 +8,7 @@
<form th:object="${owner}" th:action="@{/owners}" method="get" <form th:object="${owner}" th:action="@{/owners}" method="get"
class="form-horizontal" id="search-owner-form"> class="form-horizontal" id="search-owner-form">
<div class="form-group"> <div class="form-group">
<div class="control-group" id="lastName"> <div class="control-group" id="lastNameGroup">
<label class="col-sm-2 control-label">Last name </label> <label class="col-sm-2 control-label">Last name </label>
<div class="col-sm-10"> <div class="col-sm-10">
<input class="form-control" th:field="*{lastName}" size="30" <input class="form-control" th:field="*{lastName}" size="30"

View file

@ -1,6 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" <html xmlns:th="https://www.thymeleaf.org"
th:replace="~{fragments/layout :: layout (~{::body},'owners')}"> th:replace="~{fragments/layout :: layout (~{::body},'owners')}">
<body> <body>
@ -16,15 +16,15 @@
</tr> </tr>
<tr> <tr>
<th>Address</th> <th>Address</th>
<td th:text="*{address}" /></td> <td th:text="*{address}"></td>
</tr> </tr>
<tr> <tr>
<th>City</th> <th>City</th>
<td th:text="*{city}" /></td> <td th:text="*{city}"></td>
</tr> </tr>
<tr> <tr>
<th>Telephone</th> <th>Telephone</th>
<td th:text="*{telephone}" /></td> <td th:text="*{telephone}"></td>
</tr> </tr>
</table> </table>
@ -44,12 +44,12 @@
<td valign="top"> <td valign="top">
<dl class="dl-horizontal"> <dl class="dl-horizontal">
<dt>Name</dt> <dt>Name</dt>
<dd th:text="${pet.name}" /></dd> <dd th:text="${pet.name}"></dd>
<dt>Birth Date</dt> <dt>Birth Date</dt>
<dd <dd
th:text="${#calendars.format(pet.birthDate, 'yyyy-MM-dd')}" /></dd> th:text="${#temporals.format(pet.birthDate, 'yyyy-MM-dd')}"></dd>
<dt>Type</dt> <dt>Type</dt>
<dd th:text="${pet.type}" /></dd> <dd th:text="${pet.type}"></dd>
</dl> </dl>
</td> </td>
<td valign="top"> <td valign="top">
@ -61,7 +61,7 @@
</tr> </tr>
</thead> </thead>
<tr th:each="visit : ${pet.visits}"> <tr th:each="visit : ${pet.visits}">
<td th:text="${#calendars.format(visit.date, 'yyyy-MM-dd')}"></td> <td th:text="${#temporals.format(visit.date, 'yyyy-MM-dd')}"></td>
<td th:text="${visit?.description}"></td> <td th:text="${visit?.description}"></td>
</tr> </tr>
<tr> <tr>

View file

@ -1,12 +1,12 @@
<!DOCTYPE html> <!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" th:replace="~{fragments/layout :: layout (~{::body},'owners')}"> <html xmlns:th="https://www.thymeleaf.org" th:replace="~{fragments/layout :: layout (~{::body},'owners')}">
<body> <body>
<h2>Owners</h2> <h2>Owners</h2>
<table id="vets" class="table table-striped"> <table id="owners" class="table table-striped">
<thead> <thead>
<tr> <tr>
<th style="width: 150px;">Name</th> <th style="width: 150px;">Name</th>
@ -19,7 +19,7 @@
<tbody> <tbody>
<tr th:each="owner : ${selections}"> <tr th:each="owner : ${selections}">
<td> <td>
<a th:href="@{owners/__${owner.id}__}" th:text="${owner.firstName + ' ' + owner.lastName}"/></a> <a th:href="@{/owners/__${owner.id}__}" th:text="${owner.firstName + ' ' + owner.lastName}"/></a>
</td> </td>
<td th:text="${owner.address}"/> <td th:text="${owner.address}"/>
<td th:text="${owner.city}"/> <td th:text="${owner.city}"/>

View file

@ -1,4 +1,4 @@
<html xmlns:th="http://www.thymeleaf.org" <html xmlns:th="https://www.thymeleaf.org"
th:replace="~{fragments/layout :: layout (~{::body},'owners')}"> th:replace="~{fragments/layout :: layout (~{::body},'owners')}">
<body> <body>
@ -17,16 +17,16 @@
</div> </div>
</div> </div>
<input <input
th:replace="~{fragments/inputField :: input ('Name', 'name')}" /> th:replace="~{fragments/inputField :: input ('Name', 'name', 'text')}" />
<input <input
th:replace="~{fragments/inputField :: input ('Birth Date', 'birthDate')}" /> th:replace="~{fragments/inputField :: input ('Birth Date', 'birthDate', 'date')}" />
<input <input
th:replace="~{fragments/selectField :: select ('Type', 'type', ${types})}" /> th:replace="~{fragments/selectField :: select ('Type', 'type', ${types})}" />
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="col-sm-offset-2 col-sm-10"> <div class="col-sm-offset-2 col-sm-10">
<button <button
th:with="text=${owner['new']} ? 'Add Pet' : 'Update Pet'" th:with="text=${pet['new']} ? 'Add Pet' : 'Update Pet'"
class="btn btn-default" type="submit" th:text="${text}">Add class="btn btn-default" type="submit" th:text="${text}">Add
Pet</button> Pet</button>
</div> </div>

View file

@ -1,4 +1,4 @@
<html xmlns:th="http://www.thymeleaf.org" <html xmlns:th="https://www.thymeleaf.org"
th:replace="~{fragments/layout :: layout (~{::body},'owners')}"> th:replace="~{fragments/layout :: layout (~{::body},'owners')}">
<body> <body>
@ -19,21 +19,21 @@
</tr> </tr>
</thead> </thead>
<tr> <tr>
<td th:text="${pet.name}" /></td> <td th:text="${pet.name}"></td>
<td <td
th:text="${#calendars.format(pet.birthDate, 'yyyy-MM-dd')}" /></td> th:text="${#temporals.format(pet.birthDate, 'yyyy-MM-dd')}"></td>
<td th:text="${pet.type}" /></td> <td th:text="${pet.type}"></td>
<td <td
th:text="${pet.owner?.firstName + ' ' + pet.owner?.lastName}" /></td> th:text="${pet.owner?.firstName + ' ' + pet.owner?.lastName}"></td>
</tr> </tr>
</table> </table>
<form th:object="${visit}" class="form-horizontal" method="post"> <form th:object="${visit}" class="form-horizontal" method="post">
<div class="form-group has-feedback"> <div class="form-group has-feedback">
<input <input
th:replace="~{fragments/inputField :: input ('Date', 'date')}" /> th:replace="~{fragments/inputField :: input ('Date', 'date', 'date')}" />
<input <input
th:replace="~{fragments/inputField :: input ('Description', 'description')}" /> th:replace="~{fragments/inputField :: input ('Description', 'description', 'text')}" />
</div> </div>
<div class="form-group"> <div class="form-group">
@ -52,8 +52,8 @@
<th>Description</th> <th>Description</th>
</tr> </tr>
<tr th:if="${!visit['new']}" th:each="visit : ${pet.visits}"> <tr th:if="${!visit['new']}" th:each="visit : ${pet.visits}">
<td th:text="${#calendars.format(visit.date, 'yyyy-MM-dd')}" /></td> <td th:text="${#temporals.format(visit.date, 'yyyy-MM-dd')}"></td>
<td th:text=" ${visit.description}" /></td> <td th:text=" ${visit.description}"></td>
</tr> </tr>
</table> </table>

View file

@ -1,6 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" <html xmlns:th="https://www.thymeleaf.org"
th:replace="~{fragments/layout :: layout (~{::body},'vets')}"> th:replace="~{fragments/layout :: layout (~{::body},'vets')}">
<body> <body>
@ -23,13 +23,5 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
<table class="table-buttons">
<tr>
<td><a th:href="@{/vets.xml}">View as XML</a></td>
<td><a th:href="@{/vets.json}">View as JSON</a></td>
</tr>
</table>
</body> </body>
</html> </html>

View file

@ -1,6 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" th:replace="~{fragments/layout :: layout (~{::body},'home')}"> <html xmlns:th="https://www.thymeleaf.org" th:replace="~{fragments/layout :: layout (~{::body},'home')}">
<body> <body>

View file

@ -0,0 +1,36 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.samples.petclinic.vet.VetRepository;
@SpringBootTest
class PetclinicIntegrationTests {
@Autowired
private VetRepository vets;
@Test
void testFindAll() throws Exception {
vets.findAll();
vets.findAll(); // served from cache
}
}

View file

@ -1,6 +1,20 @@
package org.springframework.samples.petclinic.model; /*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import static org.assertj.core.api.Assertions.assertThat; package org.springframework.samples.petclinic.model;
import java.util.Locale; import java.util.Locale;
import java.util.Set; import java.util.Set;
@ -8,16 +22,17 @@ import java.util.Set;
import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolation;
import javax.validation.Validator; import javax.validation.Validator;
import org.junit.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 * @author Michael Isvy Simple test to make sure that Bean Validation is working (useful
* Simple test to make sure that Bean Validation is working * when upgrading to a new version of Hibernate Validator/ Bean Validation)
* (useful when upgrading to a new version of Hibernate Validator/ Bean Validation)
*/ */
public class ValidatorTests { class ValidatorTests {
private Validator createValidator() { private Validator createValidator() {
LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean(); LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();
@ -26,7 +41,7 @@ public class ValidatorTests {
} }
@Test @Test
public void shouldNotValidateWhenFirstNameEmpty() { void shouldNotValidateWhenFirstNameEmpty() {
LocaleContextHolder.setLocale(Locale.ENGLISH); LocaleContextHolder.setLocale(Locale.ENGLISH);
Person person = new Person(); Person person = new Person();
@ -36,10 +51,10 @@ public class ValidatorTests {
Validator validator = createValidator(); Validator validator = createValidator();
Set<ConstraintViolation<Person>> constraintViolations = validator.validate(person); Set<ConstraintViolation<Person>> constraintViolations = validator.validate(person);
assertThat(constraintViolations.size()).isEqualTo(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("may not be empty"); assertThat(violation.getMessage()).isEqualTo("must not be empty");
} }
} }

View file

@ -1,7 +1,41 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.owner; package org.springframework.samples.petclinic.owner;
import java.time.LocalDate;
import java.util.Collections;
import java.util.List;
import org.assertj.core.util.Lists;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.samples.petclinic.visit.Visit;
import org.springframework.samples.petclinic.visit.VisitRepository;
import org.springframework.test.web.servlet.MockMvc;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.hasProperty; import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
@ -9,27 +43,13 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
import org.assertj.core.util.Lists;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.samples.petclinic.owner.Owner;
import org.springframework.samples.petclinic.owner.OwnerController;
import org.springframework.samples.petclinic.owner.OwnerRepository;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
/** /**
* Test class for {@link OwnerController} * Test class for {@link OwnerController}
* *
* @author Colin But * @author Colin But
*/ */
@RunWith(SpringRunner.class)
@WebMvcTest(OwnerController.class) @WebMvcTest(OwnerController.class)
public class OwnerControllerTests { class OwnerControllerTests {
private static final int TEST_OWNER_ID = 1; private static final int TEST_OWNER_ID = 1;
@ -39,10 +59,13 @@ public class OwnerControllerTests {
@MockBean @MockBean
private OwnerRepository owners; private OwnerRepository owners;
@MockBean
private VisitRepository visits;
private Owner george; private Owner george;
@Before @BeforeEach
public 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");
@ -50,84 +73,73 @@ public class OwnerControllerTests {
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();
PetType dog = new PetType();
dog.setName("dog");
max.setId(1);
max.setType(dog);
max.setName("Max");
max.setBirthDate(LocalDate.now());
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.setDate(LocalDate.now());
given(this.visits.findByPetId(max.getId())).willReturn(Collections.singletonList(visit));
} }
@Test @Test
public void testInitCreationForm() throws Exception { void testInitCreationForm() throws Exception {
mockMvc.perform(get("/owners/new")) mockMvc.perform(get("/owners/new")).andExpect(status().isOk()).andExpect(model().attributeExists("owner"))
.andExpect(status().isOk())
.andExpect(model().attributeExists("owner"))
.andExpect(view().name("owners/createOrUpdateOwnerForm")); .andExpect(view().name("owners/createOrUpdateOwnerForm"));
} }
@Test @Test
public void testProcessCreationFormSuccess() throws Exception { void testProcessCreationFormSuccess() throws Exception {
mockMvc.perform(post("/owners/new") mockMvc.perform(post("/owners/new").param("firstName", "Joe").param("lastName", "Bloggs")
.param("firstName", "Joe") .param("address", "123 Caramel Street").param("city", "London").param("telephone", "01316761638"))
.param("lastName", "Bloggs")
.param("address", "123 Caramel Street")
.param("city", "London")
.param("telephone", "01316761638")
)
.andExpect(status().is3xxRedirection()); .andExpect(status().is3xxRedirection());
} }
@Test @Test
public void testProcessCreationFormHasErrors() throws Exception { void testProcessCreationFormHasErrors() throws Exception {
mockMvc.perform(post("/owners/new") mockMvc.perform(
.param("firstName", "Joe") post("/owners/new").param("firstName", "Joe").param("lastName", "Bloggs").param("city", "London"))
.param("lastName", "Bloggs") .andExpect(status().isOk()).andExpect(model().attributeHasErrors("owner"))
.param("city", "London")
)
.andExpect(status().isOk())
.andExpect(model().attributeHasErrors("owner"))
.andExpect(model().attributeHasFieldErrors("owner", "address")) .andExpect(model().attributeHasFieldErrors("owner", "address"))
.andExpect(model().attributeHasFieldErrors("owner", "telephone")) .andExpect(model().attributeHasFieldErrors("owner", "telephone"))
.andExpect(view().name("owners/createOrUpdateOwnerForm")); .andExpect(view().name("owners/createOrUpdateOwnerForm"));
} }
@Test @Test
public void testInitFindForm() throws Exception { void testInitFindForm() throws Exception {
mockMvc.perform(get("/owners/find")) mockMvc.perform(get("/owners/find")).andExpect(status().isOk()).andExpect(model().attributeExists("owner"))
.andExpect(status().isOk())
.andExpect(model().attributeExists("owner"))
.andExpect(view().name("owners/findOwners")); .andExpect(view().name("owners/findOwners"));
} }
@Test @Test
public void testProcessFindFormSuccess() throws Exception { void testProcessFindFormSuccess() throws Exception {
given(this.owners.findByLastName("")).willReturn(Lists.newArrayList(george, new Owner())); given(this.owners.findByLastName("")).willReturn(Lists.newArrayList(george, new Owner()));
mockMvc.perform(get("/owners")) mockMvc.perform(get("/owners")).andExpect(status().isOk()).andExpect(view().name("owners/ownersList"));
.andExpect(status().isOk())
.andExpect(view().name("owners/ownersList"));
} }
@Test @Test
public void testProcessFindFormByLastName() throws Exception { void testProcessFindFormByLastName() throws Exception {
given(this.owners.findByLastName(george.getLastName())).willReturn(Lists.newArrayList(george)); given(this.owners.findByLastName(george.getLastName())).willReturn(Lists.newArrayList(george));
mockMvc.perform(get("/owners") mockMvc.perform(get("/owners").param("lastName", "Franklin")).andExpect(status().is3xxRedirection())
.param("lastName", "Franklin")
)
.andExpect(status().is3xxRedirection())
.andExpect(view().name("redirect:/owners/" + TEST_OWNER_ID)); .andExpect(view().name("redirect:/owners/" + TEST_OWNER_ID));
} }
@Test @Test
public void testProcessFindFormNoOwnersFound() throws Exception { void testProcessFindFormNoOwnersFound() throws Exception {
mockMvc.perform(get("/owners") mockMvc.perform(get("/owners").param("lastName", "Unknown Surname")).andExpect(status().isOk())
.param("lastName", "Unknown Surname")
)
.andExpect(status().isOk())
.andExpect(model().attributeHasFieldErrors("owner", "lastName")) .andExpect(model().attributeHasFieldErrors("owner", "lastName"))
.andExpect(model().attributeHasFieldErrorCode("owner", "lastName", "notFound")) .andExpect(model().attributeHasFieldErrorCode("owner", "lastName", "notFound"))
.andExpect(view().name("owners/findOwners")); .andExpect(view().name("owners/findOwners"));
} }
@Test @Test
public void testInitUpdateOwnerForm() throws Exception { void testInitUpdateOwnerForm() throws Exception {
mockMvc.perform(get("/owners/{ownerId}/edit", TEST_OWNER_ID)) mockMvc.perform(get("/owners/{ownerId}/edit", TEST_OWNER_ID)).andExpect(status().isOk())
.andExpect(status().isOk())
.andExpect(model().attributeExists("owner")) .andExpect(model().attributeExists("owner"))
.andExpect(model().attribute("owner", hasProperty("lastName", is("Franklin")))) .andExpect(model().attribute("owner", hasProperty("lastName", is("Franklin"))))
.andExpect(model().attribute("owner", hasProperty("firstName", is("George")))) .andExpect(model().attribute("owner", hasProperty("firstName", is("George"))))
@ -138,26 +150,17 @@ public class OwnerControllerTests {
} }
@Test @Test
public void testProcessUpdateOwnerFormSuccess() throws Exception { void testProcessUpdateOwnerFormSuccess() throws Exception {
mockMvc.perform(post("/owners/{ownerId}/edit", TEST_OWNER_ID) mockMvc.perform(post("/owners/{ownerId}/edit", TEST_OWNER_ID).param("firstName", "Joe")
.param("firstName", "Joe") .param("lastName", "Bloggs").param("address", "123 Caramel Street").param("city", "London")
.param("lastName", "Bloggs") .param("telephone", "01616291589")).andExpect(status().is3xxRedirection())
.param("address", "123 Caramel Street")
.param("city", "London")
.param("telephone", "01616291589")
)
.andExpect(status().is3xxRedirection())
.andExpect(view().name("redirect:/owners/{ownerId}")); .andExpect(view().name("redirect:/owners/{ownerId}"));
} }
@Test @Test
public void testProcessUpdateOwnerFormHasErrors() throws Exception { void testProcessUpdateOwnerFormHasErrors() throws Exception {
mockMvc.perform(post("/owners/{ownerId}/edit", TEST_OWNER_ID) mockMvc.perform(post("/owners/{ownerId}/edit", TEST_OWNER_ID).param("firstName", "Joe")
.param("firstName", "Joe") .param("lastName", "Bloggs").param("city", "London")).andExpect(status().isOk())
.param("lastName", "Bloggs")
.param("city", "London")
)
.andExpect(status().isOk())
.andExpect(model().attributeHasErrors("owner")) .andExpect(model().attributeHasErrors("owner"))
.andExpect(model().attributeHasFieldErrors("owner", "address")) .andExpect(model().attributeHasFieldErrors("owner", "address"))
.andExpect(model().attributeHasFieldErrors("owner", "telephone")) .andExpect(model().attributeHasFieldErrors("owner", "telephone"))
@ -165,15 +168,32 @@ public class OwnerControllerTests {
} }
@Test @Test
public void testShowOwner() throws Exception { void testShowOwner() throws Exception {
mockMvc.perform(get("/owners/{ownerId}", TEST_OWNER_ID)) mockMvc.perform(get("/owners/{ownerId}", TEST_OWNER_ID)).andExpect(status().isOk())
.andExpect(status().isOk())
.andExpect(model().attribute("owner", hasProperty("lastName", is("Franklin")))) .andExpect(model().attribute("owner", hasProperty("lastName", is("Franklin"))))
.andExpect(model().attribute("owner", hasProperty("firstName", is("George")))) .andExpect(model().attribute("owner", hasProperty("firstName", is("George"))))
.andExpect(model().attribute("owner", hasProperty("address", is("110 W. Liberty St.")))) .andExpect(model().attribute("owner", hasProperty("address", is("110 W. Liberty St."))))
.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/ownerDetails")); .andExpect(model().attribute("owner", hasProperty("pets", not(empty()))))
.andExpect(model().attribute("owner", hasProperty("pets", new BaseMatcher<List<Pet>>() {
@Override
public boolean matches(Object item) {
@SuppressWarnings("unchecked")
List<Pet> pets = (List<Pet>) item;
Pet pet = pets.get(0);
if (pet.getVisits().isEmpty()) {
return false;
}
return true;
}
@Override
public void describeTo(Description description) {
description.appendText("Max did not have any visits");
}
}))).andExpect(view().name("owners/ownerDetails"));
} }
} }

View file

@ -1,3 +1,19 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.owner; package org.springframework.samples.petclinic.owner;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
@ -8,22 +24,13 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
import org.assertj.core.util.Lists; import org.assertj.core.util.Lists;
import org.junit.Before; import org.junit.jupiter.api.BeforeEach;
import org.junit.Test; import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType; import org.springframework.context.annotation.FilterType;
import org.springframework.samples.petclinic.owner.Owner;
import org.springframework.samples.petclinic.owner.OwnerRepository;
import org.springframework.samples.petclinic.owner.Pet;
import org.springframework.samples.petclinic.owner.PetController;
import org.springframework.samples.petclinic.owner.PetRepository;
import org.springframework.samples.petclinic.owner.PetType;
import org.springframework.samples.petclinic.owner.PetTypeFormatter;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
/** /**
@ -31,16 +38,13 @@ import org.springframework.test.web.servlet.MockMvc;
* *
* @author Colin But * @author Colin But
*/ */
@RunWith(SpringRunner.class)
@WebMvcTest(value = PetController.class, @WebMvcTest(value = PetController.class,
includeFilters = @ComponentScan.Filter( includeFilters = @ComponentScan.Filter(value = PetTypeFormatter.class, type = FilterType.ASSIGNABLE_TYPE))
value = PetTypeFormatter.class, class PetControllerTests {
type = FilterType.ASSIGNABLE_TYPE))
public 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;
@ -51,8 +55,8 @@ public class PetControllerTests {
@MockBean @MockBean
private OwnerRepository owners; private OwnerRepository owners;
@Before @BeforeEach
public void setup() { void setup() {
PetType cat = new PetType(); PetType cat = new PetType();
cat.setId(3); cat.setId(3);
cat.setName("hamster"); cat.setName("hamster");
@ -63,64 +67,46 @@ public class PetControllerTests {
} }
@Test @Test
public void testInitCreationForm() throws Exception { void testInitCreationForm() throws Exception {
mockMvc.perform(get("/owners/{ownerId}/pets/new", TEST_OWNER_ID)) mockMvc.perform(get("/owners/{ownerId}/pets/new", TEST_OWNER_ID)).andExpect(status().isOk())
.andExpect(status().isOk()) .andExpect(view().name("pets/createOrUpdatePetForm")).andExpect(model().attributeExists("pet"));
.andExpect(view().name("pets/createOrUpdatePetForm"))
.andExpect(model().attributeExists("pet"));
} }
@Test @Test
public void testProcessCreationFormSuccess() throws Exception { void testProcessCreationFormSuccess() throws Exception {
mockMvc.perform(post("/owners/{ownerId}/pets/new", TEST_OWNER_ID) mockMvc.perform(post("/owners/{ownerId}/pets/new", TEST_OWNER_ID).param("name", "Betty")
.param("name", "Betty") .param("type", "hamster").param("birthDate", "2015-02-12")).andExpect(status().is3xxRedirection())
.param("type", "hamster")
.param("birthDate", "2015/02/12")
)
.andExpect(status().is3xxRedirection())
.andExpect(view().name("redirect:/owners/{ownerId}")); .andExpect(view().name("redirect:/owners/{ownerId}"));
} }
@Test @Test
public void testProcessCreationFormHasErrors() throws Exception { void testProcessCreationFormHasErrors() throws Exception {
mockMvc.perform(post("/owners/{ownerId}/pets/{petId}/edit", TEST_OWNER_ID, TEST_PET_ID) mockMvc.perform(post("/owners/{ownerId}/pets/new", TEST_OWNER_ID).param("name", "Betty").param("birthDate",
.param("name", "Betty") "2015-02-12")).andExpect(model().attributeHasNoErrors("owner"))
.param("birthDate", "2015/02/12") .andExpect(model().attributeHasErrors("pet")).andExpect(model().attributeHasFieldErrors("pet", "type"))
) .andExpect(model().attributeHasFieldErrorCode("pet", "type", "required")).andExpect(status().isOk())
.andExpect(model().attributeHasNoErrors("owner"))
.andExpect(model().attributeHasErrors("pet"))
.andExpect(status().isOk())
.andExpect(view().name("pets/createOrUpdatePetForm")); .andExpect(view().name("pets/createOrUpdatePetForm"));
} }
@Test @Test
public void testInitUpdateForm() throws Exception { void testInitUpdateForm() throws Exception {
mockMvc.perform(get("/owners/{ownerId}/pets/{petId}/edit", TEST_OWNER_ID, TEST_PET_ID)) mockMvc.perform(get("/owners/{ownerId}/pets/{petId}/edit", TEST_OWNER_ID, TEST_PET_ID))
.andExpect(status().isOk()) .andExpect(status().isOk()).andExpect(model().attributeExists("pet"))
.andExpect(model().attributeExists("pet"))
.andExpect(view().name("pets/createOrUpdatePetForm")); .andExpect(view().name("pets/createOrUpdatePetForm"));
} }
@Test @Test
public void testProcessUpdateFormSuccess() throws Exception { void testProcessUpdateFormSuccess() throws Exception {
mockMvc.perform(post("/owners/{ownerId}/pets/{petId}/edit", TEST_OWNER_ID, TEST_PET_ID) mockMvc.perform(post("/owners/{ownerId}/pets/{petId}/edit", TEST_OWNER_ID, TEST_PET_ID).param("name", "Betty")
.param("name", "Betty") .param("type", "hamster").param("birthDate", "2015-02-12")).andExpect(status().is3xxRedirection())
.param("type", "hamster")
.param("birthDate", "2015/02/12")
)
.andExpect(status().is3xxRedirection())
.andExpect(view().name("redirect:/owners/{ownerId}")); .andExpect(view().name("redirect:/owners/{ownerId}"));
} }
@Test @Test
public void testProcessUpdateFormHasErrors() throws Exception { void testProcessUpdateFormHasErrors() throws Exception {
mockMvc.perform(post("/owners/{ownerId}/pets/{petId}/edit", TEST_OWNER_ID, TEST_PET_ID) mockMvc.perform(post("/owners/{ownerId}/pets/{petId}/edit", TEST_OWNER_ID, TEST_PET_ID).param("name", "Betty")
.param("name", "Betty") .param("birthDate", "2015/02/12")).andExpect(model().attributeHasNoErrors("owner"))
.param("birthDate", "2015/02/12") .andExpect(model().attributeHasErrors("pet")).andExpect(status().isOk())
)
.andExpect(model().attributeHasNoErrors("owner"))
.andExpect(model().attributeHasErrors("pet"))
.andExpect(status().isOk())
.andExpect(view().name("pets/createOrUpdatePetForm")); .andExpect(view().name("pets/createOrUpdatePetForm"));
} }

View file

@ -1,6 +1,20 @@
package org.springframework.samples.petclinic.owner; /*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import static org.junit.Assert.assertEquals; package org.springframework.samples.petclinic.owner;
import java.text.ParseException; import java.text.ParseException;
import java.util.ArrayList; import java.util.ArrayList;
@ -8,58 +22,59 @@ import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import org.junit.Before; import org.junit.jupiter.api.Assertions;
import org.junit.Test; import org.junit.jupiter.api.BeforeEach;
import org.junit.runner.RunWith; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.samples.petclinic.owner.PetRepository; import static org.assertj.core.api.Assertions.assertThat;
import org.springframework.samples.petclinic.owner.PetType; import static org.mockito.BDDMockito.given;
import org.springframework.samples.petclinic.owner.PetTypeFormatter;
/** /**
* Test class for {@link PetTypeFormatter} * Test class for {@link PetTypeFormatter}
* *
* @author Colin But * @author Colin But
*/ */
@RunWith(MockitoJUnitRunner.class) @ExtendWith(MockitoExtension.class)
public class PetTypeFormatterTests { class PetTypeFormatterTests {
@Mock @Mock
private PetRepository pets; private PetRepository pets;
private PetTypeFormatter petTypeFormatter; private PetTypeFormatter petTypeFormatter;
@Before @BeforeEach
public void setup() { void setup() {
this.petTypeFormatter = new PetTypeFormatter(pets); this.petTypeFormatter = new PetTypeFormatter(pets);
} }
@Test @Test
public 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);
assertEquals("Hamster", petTypeName); assertThat(petTypeName).isEqualTo("Hamster");
} }
@Test @Test
public void shouldParse() throws ParseException { void shouldParse() throws ParseException {
Mockito.when(this.pets.findPetTypes()).thenReturn(makePetTypes()); given(this.pets.findPetTypes()).willReturn(makePetTypes());
PetType petType = petTypeFormatter.parse("Bird", Locale.ENGLISH); PetType petType = petTypeFormatter.parse("Bird", Locale.ENGLISH);
assertEquals("Bird", petType.getName()); assertThat(petType.getName()).isEqualTo("Bird");
} }
@Test(expected = ParseException.class) @Test
public void shouldThrowParseException() throws ParseException { void shouldThrowParseException() throws ParseException {
Mockito.when(this.pets.findPetTypes()).thenReturn(makePetTypes()); given(this.pets.findPetTypes()).willReturn(makePetTypes());
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() {

View file

@ -1,3 +1,19 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.owner; package org.springframework.samples.petclinic.owner;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
@ -7,17 +23,12 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
import org.junit.Before; import org.junit.jupiter.api.BeforeEach;
import org.junit.Test; import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.samples.petclinic.owner.Pet;
import org.springframework.samples.petclinic.owner.PetRepository;
import org.springframework.samples.petclinic.owner.VisitController;
import org.springframework.samples.petclinic.visit.VisitRepository; import org.springframework.samples.petclinic.visit.VisitRepository;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
/** /**
@ -25,9 +36,8 @@ import org.springframework.test.web.servlet.MockMvc;
* *
* @author Colin But * @author Colin But
*/ */
@RunWith(SpringRunner.class)
@WebMvcTest(VisitController.class) @WebMvcTest(VisitController.class)
public class VisitControllerTests { class VisitControllerTests {
private static final int TEST_PET_ID = 1; private static final int TEST_PET_ID = 1;
@ -40,35 +50,28 @@ public class VisitControllerTests {
@MockBean @MockBean
private PetRepository pets; private PetRepository pets;
@Before @BeforeEach
public 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
public void testInitNewVisitForm() throws Exception { void testInitNewVisitForm() throws Exception {
mockMvc.perform(get("/owners/*/pets/{petId}/visits/new", TEST_PET_ID)) mockMvc.perform(get("/owners/*/pets/{petId}/visits/new", TEST_PET_ID)).andExpect(status().isOk())
.andExpect(status().isOk())
.andExpect(view().name("pets/createOrUpdateVisitForm")); .andExpect(view().name("pets/createOrUpdateVisitForm"));
} }
@Test @Test
public void testProcessNewVisitFormSuccess() throws Exception { void testProcessNewVisitFormSuccess() throws Exception {
mockMvc.perform(post("/owners/*/pets/{petId}/visits/new", TEST_PET_ID) mockMvc.perform(post("/owners/*/pets/{petId}/visits/new", TEST_PET_ID).param("name", "George")
.param("name", "George") .param("description", "Visit Description")).andExpect(status().is3xxRedirection())
.param("description", "Visit Description")
)
.andExpect(status().is3xxRedirection())
.andExpect(view().name("redirect:/owners/{ownerId}")); .andExpect(view().name("redirect:/owners/{ownerId}"));
} }
@Test @Test
public void testProcessNewVisitFormHasErrors() throws Exception { void testProcessNewVisitFormHasErrors() throws Exception {
mockMvc.perform(post("/owners/*/pets/{petId}/visits/new", TEST_PET_ID) mockMvc.perform(post("/owners/*/pets/{petId}/visits/new", TEST_PET_ID).param("name", "George"))
.param("name", "George") .andExpect(model().attributeHasErrors("visit")).andExpect(status().isOk())
)
.andExpect(model().attributeHasErrors("visit"))
.andExpect(status().isOk())
.andExpect(view().name("pets/createOrUpdateVisitForm")); .andExpect(view().name("pets/createOrUpdateVisitForm"));
} }

View file

@ -1,12 +1,27 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.service; package org.springframework.samples.petclinic.service;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import java.time.LocalDate;
import java.util.Collection; import java.util.Collection;
import java.util.Date;
import org.junit.Test; import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan;
@ -20,21 +35,29 @@ import org.springframework.samples.petclinic.vet.VetRepository;
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.Service; import org.springframework.stereotype.Service;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
/** /**
* Integration test of the Service and the Repository layer. * Integration test of the Service and the Repository layer.
* <p> * <p>
* ClinicServiceSpringDataJpaTests subclasses benefit from the following services provided by the Spring * ClinicServiceSpringDataJpaTests subclasses benefit from the following services provided
* TestContext Framework: </p> <ul> <li><strong>Spring IoC container caching</strong> which spares us unnecessary set up * by the Spring TestContext Framework:
* time between test execution.</li> <li><strong>Dependency Injection</strong> of test fixture instances, meaning that * </p>
* we don't need to perform application context lookups. See the use of {@link Autowired @Autowired} on the <code>{@link * <ul>
* ClinicServiceTests#clinicService clinicService}</code> instance variable, which uses autowiring <em>by * <li><strong>Spring IoC container caching</strong> which spares us unnecessary set up
* type</em>. <li><strong>Transaction management</strong>, meaning each test method is executed in its own transaction, * time between test execution.</li>
* which is automatically rolled back by default. Thus, even if tests insert or otherwise change database state, there * <li><strong>Dependency Injection</strong> of test fixture instances, meaning that we
* is no need for a teardown or cleanup script. <li> An {@link org.springframework.context.ApplicationContext * don't need to perform application context lookups. See the use of
* ApplicationContext} is also inherited and can be used for explicit bean lookup if necessary. </li> </ul> * {@link Autowired @Autowired} on the <code>{@link
* ClinicServiceTests#clinicService clinicService}</code> instance variable, which uses
* autowiring <em>by type</em>.
* <li><strong>Transaction management</strong>, meaning each test method is executed in
* its own transaction, which is automatically rolled back by default. Thus, even if tests
* insert or otherwise change database state, there is no need for a teardown or cleanup
* script.
* <li>An {@link org.springframework.context.ApplicationContext ApplicationContext} is
* also inherited and can be used for explicit bean lookup if necessary.</li>
* </ul>
* *
* @author Ken Krebs * @author Ken Krebs
* @author Rod Johnson * @author Rod Johnson
@ -43,10 +66,8 @@ import org.springframework.transaction.annotation.Transactional;
* @author Michael Isvy * @author Michael Isvy
* @author Dave Syer * @author Dave Syer
*/ */
@RunWith(SpringRunner.class)
@DataJpaTest(includeFilters = @ComponentScan.Filter(Service.class)) @DataJpaTest(includeFilters = @ComponentScan.Filter(Service.class))
public class ClinicServiceTests { class ClinicServiceTests {
@Autowired @Autowired
protected OwnerRepository owners; protected OwnerRepository owners;
@ -61,26 +82,26 @@ public class ClinicServiceTests {
protected VetRepository vets; protected VetRepository vets;
@Test @Test
public void shouldFindOwnersByLastName() { void shouldFindOwnersByLastName() {
Collection<Owner> owners = this.owners.findByLastName("Davis"); Collection<Owner> owners = this.owners.findByLastName("Davis");
assertThat(owners.size()).isEqualTo(2); assertThat(owners).hasSize(2);
owners = this.owners.findByLastName("Daviss"); owners = this.owners.findByLastName("Daviss");
assertThat(owners.isEmpty()).isTrue(); assertThat(owners).isEmpty();
} }
@Test @Test
public 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().size()).isEqualTo(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
public 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();
@ -99,7 +120,7 @@ public class ClinicServiceTests {
@Test @Test
@Transactional @Transactional
public 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";
@ -113,7 +134,7 @@ public class ClinicServiceTests {
} }
@Test @Test
public 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");
@ -121,7 +142,7 @@ public class ClinicServiceTests {
} }
@Test @Test
public 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);
@ -132,7 +153,7 @@ public class ClinicServiceTests {
@Test @Test
@Transactional @Transactional
public 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();
@ -140,7 +161,7 @@ public class ClinicServiceTests {
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(new Date()); pet.setBirthDate(LocalDate.now());
owner6.addPet(pet); owner6.addPet(pet);
assertThat(owner6.getPets().size()).isEqualTo(found + 1); assertThat(owner6.getPets().size()).isEqualTo(found + 1);
@ -155,7 +176,7 @@ public class ClinicServiceTests {
@Test @Test
@Transactional @Transactional
public 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();
@ -168,7 +189,7 @@ public class ClinicServiceTests {
} }
@Test @Test
public 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);
@ -180,7 +201,7 @@ public class ClinicServiceTests {
@Test @Test
@Transactional @Transactional
public 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();
@ -195,9 +216,9 @@ public class ClinicServiceTests {
} }
@Test @Test
public void shouldFindVisitsByPetId() throws Exception { void shouldFindVisitsByPetId() throws Exception {
Collection<Visit> visits = this.visits.findByPetId(7); Collection<Visit> visits = this.visits.findByPetId(7);
assertThat(visits.size()).isEqualTo(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

@ -1,11 +1,11 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2012-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -22,8 +22,8 @@ import org.springframework.orm.ObjectRetrievalFailureException;
import org.springframework.samples.petclinic.model.BaseEntity; import org.springframework.samples.petclinic.model.BaseEntity;
/** /**
* Utility methods for handling entities. Separate from the BaseEntity class mainly because of dependency on the * Utility methods for handling entities. Separate from the BaseEntity class mainly
* ORM-associated ObjectRetrievalFailureException. * because of dependency on the ORM-associated ObjectRetrievalFailureException.
* *
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Sam Brannen * @author Sam Brannen
@ -34,7 +34,6 @@ public abstract class EntityUtils {
/** /**
* Look up the entity of the given class with the given id in the given collection. * Look up the entity of the given class with the given id in the given collection.
*
* @param entities the collection to search * @param entities the collection to search
* @param entityClass the entity class to look up * @param entityClass the entity class to look up
* @param entityId the entity id to look up * @param entityId the entity id to look up

View file

@ -1,12 +1,25 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.system; package org.springframework.samples.petclinic.system;
import org.junit.Ignore; import org.junit.jupiter.api.Disabled;
import org.junit.Test; import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
@ -20,19 +33,19 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* *
* @author Colin But * @author Colin But
*/ */
@RunWith(SpringRunner.class)
// Waiting https://github.com/spring-projects/spring-boot/issues/5574 // Waiting https://github.com/spring-projects/spring-boot/issues/5574
@Ignore @Disabled
@WebMvcTest(controllers = CrashController.class) @WebMvcTest(controllers = CrashController.class)
public class CrashControllerTests { class CrashControllerTests {
@Autowired @Autowired
private MockMvc mockMvc; private MockMvc mockMvc;
@Test @Test
public void testTriggerException() throws Exception { void testTriggerException() throws Exception {
mockMvc.perform(get("/oups")).andExpect(view().name("exception")) mockMvc.perform(get("/oups")).andExpect(view().name("exception"))
.andExpect(model().attributeExists("exception")) .andExpect(model().attributeExists("exception")).andExpect(forwardedUrl("exception"))
.andExpect(forwardedUrl("exception")).andExpect(status().isOk()); .andExpect(status().isOk());
} }
} }

View file

@ -1,23 +0,0 @@
package org.springframework.samples.petclinic.system;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.samples.petclinic.vet.VetRepository;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class ProductionConfigurationTests {
@Autowired
private VetRepository vets;
@Test
public void testFindAll() throws Exception {
vets.findAll();
vets.findAll(); // served from cache
}
}

View file

@ -1,6 +1,21 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.vet; package org.springframework.samples.petclinic.vet;
import static org.hamcrest.xml.HasXPath.hasXPath;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
@ -10,27 +25,20 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
import org.assertj.core.util.Lists; import org.assertj.core.util.Lists;
import org.junit.Before; import org.junit.jupiter.api.BeforeEach;
import org.junit.Test; import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.samples.petclinic.vet.Specialty;
import org.springframework.samples.petclinic.vet.Vet;
import org.springframework.samples.petclinic.vet.VetController;
import org.springframework.samples.petclinic.vet.VetRepository;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.ResultActions;
/** /**
* Test class for the {@link VetController} * Test class for the {@link VetController}
*/ */
@RunWith(SpringRunner.class)
@WebMvcTest(VetController.class) @WebMvcTest(VetController.class)
public class VetControllerTests { class VetControllerTests {
@Autowired @Autowired
private MockMvc mockMvc; private MockMvc mockMvc;
@ -38,8 +46,8 @@ public class VetControllerTests {
@MockBean @MockBean
private VetRepository vets; private VetRepository vets;
@Before @BeforeEach
public void setup() { void setup() {
Vet james = new Vet(); Vet james = new Vet();
james.setFirstName("James"); james.setFirstName("James");
james.setLastName("Carter"); james.setLastName("Carter");
@ -56,27 +64,17 @@ public class VetControllerTests {
} }
@Test @Test
public void testShowVetListHtml() throws Exception { void testShowVetListHtml() throws Exception {
mockMvc.perform(get("/vets.html")) mockMvc.perform(get("/vets.html")).andExpect(status().isOk()).andExpect(model().attributeExists("vets"))
.andExpect(status().isOk())
.andExpect(model().attributeExists("vets"))
.andExpect(view().name("vets/vetList")); .andExpect(view().name("vets/vetList"));
} }
@Test @Test
public void testShowResourcesVetList() throws Exception { void testShowResourcesVetList() throws Exception {
ResultActions actions = mockMvc.perform(get("/vets.json").accept(MediaType.APPLICATION_JSON)) ResultActions actions = mockMvc.perform(get("/vets").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()); .andExpect(status().isOk());
actions.andExpect(content().contentType("application/json;charset=UTF-8")) actions.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.vetList[0].id").value(1)); .andExpect(jsonPath("$.vetList[0].id").value(1));
} }
@Test
public void testShowVetListXml() throws Exception {
mockMvc.perform(get("/vets.xml").accept(MediaType.APPLICATION_XML))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_XML_VALUE))
.andExpect(content().node(hasXPath("/vets/vetList[id=1]/id")));
}
} }

View file

@ -1,11 +1,11 @@
/* /*
* Copyright 2016-2017 the original author or authors. * Copyright 2012-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -15,26 +15,23 @@
*/ */
package org.springframework.samples.petclinic.vet; package org.springframework.samples.petclinic.vet;
import org.junit.Test; import org.junit.jupiter.api.Test;
import org.springframework.util.SerializationUtils; import org.springframework.util.SerializationUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
* @author Dave Syer * @author Dave Syer
*
*/ */
public class VetTests { class VetTests {
@Test @Test
public 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 Vet other = (Vet) SerializationUtils.deserialize(SerializationUtils.serialize(vet));
.deserialize(SerializationUtils.serialize(vet));
assertThat(other.getFirstName()).isEqualTo(vet.getFirstName()); assertThat(other.getFirstName()).isEqualTo(vet.getFirstName());
assertThat(other.getLastName()).isEqualTo(vet.getLastName()); assertThat(other.getLastName()).isEqualTo(vet.getLastName());
assertThat(other.getId()).isEqualTo(vet.getId()); assertThat(other.getId()).isEqualTo(vet.getId());

View file

@ -135,7 +135,7 @@
<stringProp name="HTTPSampler.response_timeout"></stringProp> <stringProp name="HTTPSampler.response_timeout"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp> <stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp> <stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">${CONTEXT_WEB}/vendors/jquery/jquery.js</stringProp> <stringProp name="HTTPSampler.path">${CONTEXT_WEB}/webjars/jquery/jquery.min.js</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp> <stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp> <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp> <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
@ -175,7 +175,7 @@
<stringProp name="HTTPSampler.response_timeout"></stringProp> <stringProp name="HTTPSampler.response_timeout"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp> <stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp> <stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">${CONTEXT_WEB}/owners/find.html</stringProp> <stringProp name="HTTPSampler.path">${CONTEXT_WEB}/owners/find</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp> <stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp> <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp> <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
@ -195,7 +195,7 @@
<stringProp name="HTTPSampler.response_timeout"></stringProp> <stringProp name="HTTPSampler.response_timeout"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp> <stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp> <stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">${CONTEXT_WEB}/owners.html?lastName=</stringProp> <stringProp name="HTTPSampler.path">${CONTEXT_WEB}/owners?lastName=</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp> <stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp> <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp> <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
@ -215,7 +215,7 @@
<stringProp name="HTTPSampler.response_timeout"></stringProp> <stringProp name="HTTPSampler.response_timeout"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp> <stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp> <stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">${CONTEXT_WEB}/owners/${count}.html</stringProp> <stringProp name="HTTPSampler.path">${CONTEXT_WEB}/owners/${count}</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp> <stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp> <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp> <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
@ -235,7 +235,7 @@
<stringProp name="HTTPSampler.response_timeout"></stringProp> <stringProp name="HTTPSampler.response_timeout"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp> <stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp> <stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">${CONTEXT_WEB}/owners/${count}/edit.html</stringProp> <stringProp name="HTTPSampler.path">${CONTEXT_WEB}/owners/${count}/edit</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp> <stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp> <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp> <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
@ -262,7 +262,7 @@
<stringProp name="HTTPSampler.response_timeout"></stringProp> <stringProp name="HTTPSampler.response_timeout"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp> <stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp> <stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">${CONTEXT_WEB}/owners/${count}/edit.html</stringProp> <stringProp name="HTTPSampler.path">${CONTEXT_WEB}/owners/${count}/edit</stringProp>
<stringProp name="HTTPSampler.method">POST</stringProp> <stringProp name="HTTPSampler.method">POST</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp> <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp> <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
@ -329,7 +329,7 @@
<stringProp name="HTTPSampler.response_timeout"></stringProp> <stringProp name="HTTPSampler.response_timeout"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp> <stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp> <stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">${CONTEXT_WEB}/owners/${count}.html</stringProp> <stringProp name="HTTPSampler.path">${CONTEXT_WEB}/owners/${count}</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp> <stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp> <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp> <boolProp name="HTTPSampler.auto_redirects">false</boolProp>