diff --git a/README.md b/README.md
index 977d0753b..46e5053cb 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,25 @@ A full description is available in the plugin’s [documentation](https://go.clo
Demo
---
-The plugin has an outdated Docker-based demo. See the [demo README from v1.12](https://github.com/jenkinsci/docker-workflow-plugin/tree/docker-workflow-1.12/demo) for setup and launch guidelines.
+The plugin has a Docker-based demo. See the [demo README](demo/README.md) page for setup and launch guidelines.
+
+Releasing
+---
+
+Prior to release, edit `demo/plugins.txt` to use the snapshot version and run:
+
+ mvn -DskipTests clean install
+ make -C demo run
+
+and verify that the demo works.
+
+After the Maven release completes, update the `docker-workflow` version in `demo/plugins.txt` to the release version and run
+
+ make -C demo run
+
+to sanity check, then
+
+ make -C demo push-latest
License
---
diff --git a/demo/.gitignore b/demo/.gitignore
new file mode 100644
index 000000000..39183a11e
--- /dev/null
+++ b/demo/.gitignore
@@ -0,0 +1,2 @@
+plugins/
+certs/
diff --git a/demo/Dockerfile b/demo/Dockerfile
new file mode 100644
index 000000000..641fb731a
--- /dev/null
+++ b/demo/Dockerfile
@@ -0,0 +1,71 @@
+##
+# The MIT License
+#
+# Copyright (c) 2015, CloudBees, Inc.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+##
+
+FROM jenkins/jenkins
+
+USER root
+
+# Install Docker client
+ENV DOCKER_BUCKET get.docker.com
+ENV DOCKER_VERSION 1.12.6
+
+RUN set -x \
+ && curl -fSL "https://${DOCKER_BUCKET}/builds/Linux/x86_64/docker-$DOCKER_VERSION.tgz" -o docker.tgz \
+ && tar -xzvf docker.tgz \
+ && mv docker/* /usr/local/bin/ \
+ && rmdir docker \
+ && rm docker.tgz \
+ && docker -v
+
+RUN cd /opt && \
+ curl -s https://dlcdn.apache.org/maven/maven-3/3.8.6/binaries/apache-maven-3.8.6-bin.tar.gz | tar xvfz - && \
+ ln -s /opt/apache-maven-3.8.6/bin/mvn /usr/bin && \
+ which mvn && \
+ mvn -version
+
+ADD repo /tmp/repo
+RUN git config --global user.email "demo@jenkins-ci.org" && git config --global user.name "Docker Workflow Demo" && cd /tmp/repo && git init && git add . && git commit -m 'demo'
+
+# Run commands identical to those in Pipeline script to warm up the cache:
+RUN echo 'centralhttp://repo.jenkins-ci.org/public/*' > settings.xml
+RUN mvn -s settings.xml -Dmaven.repo.local=/usr/share/jenkins/ref/jobs/docker-workflow/workspace@tmp/m2repo -f /tmp/repo/app -B -DskipTests clean package && \
+ mvn -s settings.xml -Dmaven.repo.local=/usr/share/jenkins/ref/jobs/docker-workflow/workspace@tmp/m2repo -f /tmp/repo/test -B -Dmaven.test.failure.ignore clean test
+# TODO switch to a persistent volume as in parallel-test-executor-demo, after making sure run-demo.sh starts by deleting snapshots from it
+
+RUN jenkins-plugin-cli --plugins \
+ credentials-binding \
+ docker-workflow \
+ xvnc
+COPY docker-workflow.jpi /usr/share/jenkins/ref/plugins
+
+ADD JENKINS_HOME /usr/share/jenkins/ref
+
+COPY run-demo.sh /usr/local/bin/run-demo.sh
+ENV JAVA_OPTS -Djenkins.install.state=INITIAL_SECURITY_SETUP
+
+RUN chown -R jenkins.jenkins /usr/share/jenkins/ref /tmp/repo
+
+USER jenkins
+
+CMD /usr/local/bin/run-demo.sh
diff --git a/demo/Dockerfile-registry b/demo/Dockerfile-registry
new file mode 100644
index 000000000..235838cfb
--- /dev/null
+++ b/demo/Dockerfile-registry
@@ -0,0 +1,7 @@
+FROM registry:2.5.1
+ADD certs/ca.crt certs/ca.key certs/docker-registry.htpasswd /var/registry/certs/
+ENV REGISTRY_HTTP_TLS_CERTIFICATE /var/registry/certs/ca.crt
+ENV REGISTRY_HTTP_TLS_KEY /var/registry/certs/ca.key
+ENV REGISTRY_AUTH htpasswd
+ENV REGISTRY_AUTH_HTPASSWD_REALM Registry Realm
+ENV REGISTRY_AUTH_HTPASSWD_PATH /var/registry/certs/docker-registry.htpasswd
diff --git a/demo/JENKINS_HOME/config.xml b/demo/JENKINS_HOME/config.xml
new file mode 100644
index 000000000..1aa01d221
--- /dev/null
+++ b/demo/JENKINS_HOME/config.xml
@@ -0,0 +1,34 @@
+
+
+
+ 1.0
+ 2
+ NORMAL
+ true
+
+
+ false
+
+ ${ITEM_ROOTDIR}/workspace
+ ${ITEM_ROOTDIR}/builds
+
+
+
+
+
+ 0
+
+
+
+ All
+ false
+ false
+
+
+
+ All
+ 0
+
+
+
+
diff --git a/demo/JENKINS_HOME/credentials.xml b/demo/JENKINS_HOME/credentials.xml
new file mode 100644
index 000000000..d4c6ffbc1
--- /dev/null
+++ b/demo/JENKINS_HOME/credentials.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+ GLOBAL
+ docker-registry-login
+
+ pipelineuser
+ 123123123
+
+
+
+
+
diff --git a/demo/JENKINS_HOME/jobs/docker-workflow/config.xml b/demo/JENKINS_HOME/jobs/docker-workflow/config.xml
new file mode 100644
index 000000000..86fa2eca2
--- /dev/null
+++ b/demo/JENKINS_HOME/jobs/docker-workflow/config.xml
@@ -0,0 +1,26 @@
+
+
+
+ Demonstrates Jenkins Pipeline integration with Docker based on the Docker Pipeline Plugin.
+ false
+
+
+
+ 2
+
+
+ /tmp/repo
+
+
+
+
+ */master
+
+
+ false
+
+
+
+ flow.groovy
+
+
diff --git a/demo/JENKINS_HOME/jobs/puller/config.xml b/demo/JENKINS_HOME/jobs/puller/config.xml
new file mode 100644
index 000000000..af6b384bd
--- /dev/null
+++ b/demo/JENKINS_HOME/jobs/puller/config.xml
@@ -0,0 +1,27 @@
+
+
+
+ JENKINS-34276 verification; run after docker-workflow #1
+ false
+
+
+
+
+
+
+
+ true
+
+
+
diff --git a/demo/Makefile b/demo/Makefile
new file mode 100644
index 000000000..a77845806
--- /dev/null
+++ b/demo/Makefile
@@ -0,0 +1,65 @@
+##
+# The MIT License
+#
+# Copyright (c) 2015, CloudBees, Inc.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+##
+
+TAG=$(shell date -I -u)
+IMAGE=jenkinsci/docker-workflow-demo
+
+build-registry:
+ ./gen-security-data.sh certs
+ docker build -t registry:docker-workflow-demo -f Dockerfile-registry .
+
+../target/docker-workflow.hpi: ../pom.xml $(shell find ../src ../.mvn)
+ mvn -f .. -Pquick-build clean package
+
+build: ../target/docker-workflow.hpi build-registry
+ docker build -t $(IMAGE):$(TAG) .
+
+# To connect a Java debugger to the Jenkins instance running in the docker container, simply add the following
+# options to the "docker run" command (just after the port mappings):
+#
+# -p 5500:5500 -e JAVA_OPTS=-Xrunjdwp:transport=dt_socket,server=y,address=5500,suspend=n
+#
+# If using boot2docker, you need to tell your remote debugger to use the boot2docker VM ip (ala boot2docker ip).
+
+ifeq ($(shell uname -s),Darwin)
+ STAT_OPT = -f
+else
+ STAT_OPT = -c
+endif
+
+DOCKER_RUN=docker run --rm -p 127.0.0.1:8080:8080 -v /var/run/docker.sock:/var/run/docker.sock --group-add=$(shell stat $(STAT_OPT) %g /var/run/docker.sock)
+
+run: build
+ $(DOCKER_RUN) $(IMAGE):$(TAG)
+
+clean:
+ rm -rf certs plugins
+
+push:
+ docker push $(IMAGE):$(TAG)
+ echo "consider also: make push-latest"
+
+push-latest: push
+ docker tag $(IMAGE):$(TAG) $(IMAGE):latest
+ docker push $(IMAGE):latest
diff --git a/demo/README.md b/demo/README.md
new file mode 100644
index 000000000..040d74c53
--- /dev/null
+++ b/demo/README.md
@@ -0,0 +1,33 @@
+Docker image for Docker Pipeline demo
+=====================================
+This image contains a "Docker Pipeline" Job that demonstrates Jenkins Pipeline integration
+with Docker via this plugin.
+
+```bash
+make run
+```
+
+The "Docker Pipeline" Job simply does the following:
+
+1. Gets the Spring Pet Clinic demonstration application code from GitHub.
+1. Builds the Pet Clinic application in a Docker container.
+1. Builds a runnable Pet Clinic application Docker image.
+1. Runs a Pet Clinic app container (from the Pet Clinic application Docker image) + a second maven3 container that runs automated tests against the Pet Clinic app container.
+ * The 2 containers are linked, allowing the test container to fire requests at the Pet Clinic app container.
+
+The "Docker Pipeline" Job demonstrates how to use the `docker` DSL:
+
+1. Use `docker.image` to define a DSL `Image` object (not to be confused with `build`) that can then be used to perform operations on a Docker image:
+ * use `Image.inside` to run a Docker container and execute commands in it. The build workspace is mounted as the working directory in the container.
+ * use `Image.run` to run a Docker container in detached mode, returning a DSL `Container` object that can be later used to stop the container (via `Container.stop`).
+1. Use `docker.build` to build a Docker image from a `Dockerfile`, returning a DSL `Image` object that can then be used to perform operations on that image (as above).
+
+The `docker` DSL supports some additional capabilities not shown in the "Docker Pipeline" Job:
+
+1. Use the `docker.withRegistry` and `docker.withServer` to register endpoints for the Docker registry and host to be used when executing docker commands.
+ * `docker.withRegistry(, )`
+ * `docker.withServer(, )`
+1. Use the `Image.pull` to pull Docker image layers into the Docker host cache.
+1. Use the `Image.push` to push a Docker image to the associated Docker Registry. See `docker.withRegistry` above.
+
+The image needs to run Docker commands, so it assumes that your Docker daemon is listening to `/var/run/docker.sock` ([discussion](https://github.com/docker/docker/issues/1143)). This is not “Docker-in-Docker”; the container only runs the CLI and connects back to the host to start sister containers. The `run` target also makes reference to file paths on the Docker host, assuming they are where you are running that command, so this target *cannot work* on boot2docker. There may be some way to run this demo using boot2docker; if so, please contribute it.
diff --git a/demo/gen-security-data.sh b/demo/gen-security-data.sh
new file mode 100755
index 000000000..99464d615
--- /dev/null
+++ b/demo/gen-security-data.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+
+##
+# The MIT License
+#
+# Copyright (c) 2015, CloudBees, Inc.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+##
+
+set -e
+
+rm -rfv $1
+mkdir -p $1
+
+pushd $1
+
+docker run --entrypoint htpasswd registry:2.5.1 -Bbn pipelineuser 123123123 > docker-registry.htpasswd
+
+# Create the CA Key and Certificate for signing Certs
+openssl genrsa -des3 -passout pass:x -out ca.key 4096
+openssl rsa -passin pass:x -in ca.key -out ca.key # remove password!
+openssl req -new -x509 -days 365 -key ca.key -out ca.crt -subj "/C=US/ST=California/L=San Jose/O=Jenkins CI/OU=Pipeline Dept/CN=localhost"
diff --git a/demo/repo/app/.springBeans b/demo/repo/app/.springBeans
new file mode 100644
index 000000000..44f18becd
--- /dev/null
+++ b/demo/repo/app/.springBeans
@@ -0,0 +1,20 @@
+
+
+ 1
+
+
+
+
+
+
+ src/main/resources/spring/datasource-config.xml
+ src/main/resources/spring/mvc-core-config.xml
+ src/main/resources/spring/mvc-view-config.xml
+ src/main/resources/spring/business-config.xml
+
+
+ src/main/resources/spring/tools-config.xml
+
+
+
+
diff --git a/demo/repo/app/Dockerfile b/demo/repo/app/Dockerfile
new file mode 100644
index 000000000..290254782
--- /dev/null
+++ b/demo/repo/app/Dockerfile
@@ -0,0 +1,3 @@
+FROM tfennelly/tomcat7
+
+ADD target/petclinic.war /tomcat7/webapps/petclinic.war
diff --git a/demo/repo/app/pom.xml b/demo/repo/app/pom.xml
new file mode 100644
index 000000000..42be1b677
--- /dev/null
+++ b/demo/repo/app/pom.xml
@@ -0,0 +1,448 @@
+
+
+ 4.0.0
+ org.springframework.samples
+ spring-petclinic
+ 1.0.0-SNAPSHOT
+
+ petclinic
+ war
+
+
+
+
+ 1.6
+ UTF-8
+ UTF-8
+
+
+ 4.1.6.RELEASE
+ 1.8.0.RELEASE
+
+
+
+ 2.2
+ 1.2
+ 7.0.30
+ 2.2.7
+
+
+ 4.3.8.Final
+
+
+ 4.3.1.Final
+
+
+ 7.0.42
+ 2.6.10
+ 2.3.2
+
+
+ 1.8.5
+
+
+ 1.1.2
+ 1.7.10
+
+
+ 1.5.0
+
+
+ 4.12
+ 2.0.0
+
+
+ 1.3
+ 1.1.1
+ 2.7
+ 3.2.0.GA
+
+
+
+ 2.3.0
+ 1.10.3
+ 2.0.3-1
+ 0.10.1
+
+ 5.1.22
+
+
+
+
+
+ org.jadira.usertype
+ usertype.core
+ ${jadira-usertype-core.version}
+
+
+ org.apache.tomcat
+ tomcat-servlet-api
+ ${tomcat.servlet.version}
+ provided
+
+
+ javax.servlet.jsp
+ jsp-api
+ 2.1
+ provided
+
+
+
+ org.glassfish.web
+ jstl-impl
+ 1.2
+
+
+ javax.servlet
+ servlet-api
+
+
+
+
+ com.sun.xml.bind
+ jaxb-impl
+ ${jaxb-impl.version}
+ provided
+
+
+
+ org.springframework.data
+ spring-data-jpa
+ ${spring-data-jpa.version}
+
+
+
+ org.springframework
+ spring-jdbc
+ ${spring-framework.version}
+
+
+ org.springframework
+ spring-aop
+ ${spring-framework.version}
+
+
+ org.springframework
+ spring-webmvc
+ ${spring-framework.version}
+
+
+ org.springframework
+ spring-tx
+ ${spring-framework.version}
+
+
+
+ org.springframework
+ spring-context-support
+ ${spring-framework.version}
+
+
+ org.springframework
+ spring-orm
+ ${spring-framework.version}
+
+
+ org.springframework
+ spring-oxm
+ ${spring-framework.version}
+
+
+ commons-lang
+ commons-lang
+
+
+
+
+ org.springframework
+ spring-jms
+ ${spring-framework.version}
+
+
+
+
+
+ org.apache.tomcat
+ tomcat-jdbc
+ ${tomcat-jdbc.version}
+ runtime
+
+
+
+
+ org.slf4j
+ slf4j-api
+ ${slf4j.version}
+ compile
+
+
+ ch.qos.logback
+ logback-classic
+ ${logback.version}
+ runtime
+
+
+
+
+ com.rometools
+ rome
+ ${rome.version}
+
+
+
+
+ joda-time
+ joda-time
+ ${jodatime.version}
+
+
+ joda-time
+ joda-time-hibernate
+ ${jodatime-hibernate.version}
+
+
+ joda-time
+ joda-time-jsptags
+ ${jodatime-jsptags.version}
+
+
+
+
+
+ org.hsqldb
+ hsqldb
+ ${hsqldb.version}
+ runtime
+
+
+
+
+
+
+ org.hibernate
+ hibernate-entitymanager
+ ${hibernate.version}
+
+
+ org.hibernate
+ hibernate-validator
+ ${hibernate-validator.version}
+
+
+
+ org.hibernate
+ hibernate-ehcache
+ ${hibernate.version}
+
+
+ net.sf.ehcache
+ ehcache-core
+ ${ehcache.version}
+
+
+ commons-logging
+ commons-logging
+
+
+
+
+
+ org.webjars
+ bootstrap
+ ${webjars-bootstrap.version}
+
+
+ org.webjars
+ jquery-ui
+ ${webjars-jquery-ui.version}
+
+
+ org.webjars
+ jquery
+ ${webjars-jquery.version}
+
+
+
+
+ org.springframework
+ spring-test
+ ${spring-framework.version}
+ test
+
+
+ junit
+ junit
+ ${junit.version}
+ test
+
+
+ org.assertj
+ assertj-core
+ ${assertj.version}
+
+
+
+ org.aspectj
+ aspectjrt
+ ${aspectj.version}
+
+
+ org.aspectj
+ aspectjweaver
+ ${aspectj.version}
+ runtime
+
+
+
+
+
+ com.github.dandelion
+ datatables-jsp
+ ${dandelion.version}
+
+
+ com.github.dandelion
+ datatables-export-itext
+ ${dandelion.version}
+
+
+
+
+
+
+ org.springframework
+ spring-core
+ ${spring-framework.version}
+
+
+ org.springframework
+ spring-beans
+ ${spring-framework.version}
+
+
+ org.springframework
+ spring-context
+ ${spring-framework.version}
+
+
+ org.springframework
+ spring-orm
+ ${spring-framework.version}
+
+
+ org.springframework
+ spring-aop
+ ${spring-framework.version}
+
+
+ org.springframework
+ spring-tx
+ ${spring-framework.version}
+
+
+
+
+
+
+ install
+
+
+
+ ${project.basedir}/src/test/java
+
+
+ ${project.basedir}/src/test/resources
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.0
+
+
+
+
+ ${java.version}
+ ${java.version}
+ true
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.13
+
+
+ **/*Tests.java
+
+
+
+
+ org.apache.maven.plugins
+ maven-war-plugin
+ 2.3
+
+ petclinic
+
+
+
+ org.apache.maven.plugins
+ maven-eclipse-plugin
+ 2.9
+
+ true
+ true
+ 2.0
+
+ **/*.*
+
+
+
+ org.springframework.ide.eclipse.core.springbuilder
+
+
+ org.eclipse.m2e.core.maven2Builder
+
+
+
+ org.eclipse.jdt.core.javanature
+ org.springframework.ide.eclipse.core.springnature
+ org.eclipse.m2e.core.maven2Nature
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ 2.4
+
+
+ jar-with-dependencies
+
+
+
+
+ org.apache.tomcat.maven
+ tomcat7-maven-plugin
+ 2.0
+
+ tomcat-development-server
+ 9966
+ /petclinic
+
+
+
+
+
+ demopetclinic
+
diff --git a/demo/repo/app/readme.md b/demo/repo/app/readme.md
new file mode 100644
index 000000000..8f9cb72d1
--- /dev/null
+++ b/demo/repo/app/readme.md
@@ -0,0 +1,200 @@
+# Spring PetClinic Sample Application
+
+## What does it look like?
+-spring-petclinic has been deployed here on cloudfoundry: http://demo-spring-petclinic.cfapps.io/
+
+
+## Understanding the Spring Petclinic application with a few diagrams
+See the presentation here
+
+## Running petclinic locally
+```
+ git clone https://github.com/SpringSource/spring-petclinic.git
+ mvn tomcat7:run
+```
+
+You can then access petclinic here: http://localhost:9966/petclinic/
+
+## In case you find a bug/suggested improvement for Spring Petclinic
+Our issue tracker is available here: https://github.com/SpringSource/spring-petclinic/issues
+
+## Working with Petclinic in Eclipse/STS
+
+### prerequisites
+The following items should be installed in your system:
+* Maven 3 (http://www.sonatype.com/books/mvnref-book/reference/installation.html)
+* 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)
+
+Note: when m2e is available, there is an m2 icon in Help -> About dialog.
+If m2e is not there, just follow the install process here: http://eclipse.org/m2e/download/
+
+
+### Steps:
+
+1) In the command line
+```
+git clone https://github.com/SpringSource/spring-petclinic.git
+```
+2) Inside Eclipse
+```
+File -> Import -> Maven -> Existing Maven project
+```
+
+
+## Looking for something in particular?
+
+
+
+
+## Interaction with other open source projects
+
+One of the best parts about working on the Spring Petclinic application is that we have the opportunity to work in direct contact with many Open Source projects. We found some bugs/suggested improvements on various topics such as Spring, Spring Data, Bean Validation and even Eclipse! In many cases, they've been fixed/implemented in just a few days.
+Here is a list of them:
+
+
+
+
Name
+
Issue
+
+
+
+
Spring JDBC: simplify usage of NamedParameterJdbcTemplate
+
+
+
+
diff --git a/demo/repo/app/sonar-project.properties b/demo/repo/app/sonar-project.properties
new file mode 100755
index 000000000..d84ed7c2d
--- /dev/null
+++ b/demo/repo/app/sonar-project.properties
@@ -0,0 +1,13 @@
+# 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
\ No newline at end of file
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/model/BaseEntity.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/model/BaseEntity.java
new file mode 100644
index 000000000..8dfbfe90b
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/model/BaseEntity.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.model;
+
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.MappedSuperclass;
+
+/**
+ * Simple JavaBean domain object with an id property. Used as a base class for objects needing this property.
+ *
+ * @author Ken Krebs
+ * @author Juergen Hoeller
+ */
+@MappedSuperclass
+public class BaseEntity {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ protected Integer id;
+
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ public boolean isNew() {
+ return (this.id == null);
+ }
+
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/model/NamedEntity.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/model/NamedEntity.java
new file mode 100644
index 000000000..cb36a6267
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/model/NamedEntity.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.model;
+
+import javax.persistence.Column;
+import javax.persistence.MappedSuperclass;
+
+
+/**
+ * Simple JavaBean domain object adds a name property to BaseEntity. Used as a base class for objects
+ * needing these properties.
+ *
+ * @author Ken Krebs
+ * @author Juergen Hoeller
+ */
+@MappedSuperclass
+public class NamedEntity extends BaseEntity {
+
+ @Column(name = "name")
+ private String name;
+
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ @Override
+ public String toString() {
+ return this.getName();
+ }
+
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/model/Owner.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/model/Owner.java
new file mode 100644
index 000000000..840a965ed
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/model/Owner.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.model;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import javax.validation.constraints.Digits;
+
+import org.hibernate.validator.constraints.NotEmpty;
+import org.springframework.beans.support.MutableSortDefinition;
+import org.springframework.beans.support.PropertyComparator;
+import org.springframework.core.style.ToStringCreator;
+
+/**
+ * Simple JavaBean domain object representing an owner.
+ *
+ * @author Ken Krebs
+ * @author Juergen Hoeller
+ * @author Sam Brannen
+ * @author Michael Isvy
+ */
+@Entity
+@Table(name = "owners")
+public class Owner extends Person {
+ @Column(name = "address")
+ @NotEmpty
+ private String address;
+
+ @Column(name = "city")
+ @NotEmpty
+ private String city;
+
+ @Column(name = "telephone")
+ @NotEmpty
+ @Digits(fraction = 0, integer = 10)
+ private String telephone;
+
+ @OneToMany(cascade = CascadeType.ALL, mappedBy = "owner")
+ private Set pets;
+
+
+ public String getAddress() {
+ return this.address;
+ }
+
+ public void setAddress(String address) {
+ this.address = address;
+ }
+
+ public String getCity() {
+ return this.city;
+ }
+
+ public void setCity(String city) {
+ this.city = city;
+ }
+
+ public String getTelephone() {
+ return this.telephone;
+ }
+
+ public void setTelephone(String telephone) {
+ this.telephone = telephone;
+ }
+
+ protected void setPetsInternal(Set pets) {
+ this.pets = pets;
+ }
+
+ protected Set getPetsInternal() {
+ if (this.pets == null) {
+ this.pets = new HashSet();
+ }
+ return this.pets;
+ }
+
+ public List getPets() {
+ List sortedPets = new ArrayList(getPetsInternal());
+ PropertyComparator.sort(sortedPets, new MutableSortDefinition("name", true, true));
+ return Collections.unmodifiableList(sortedPets);
+ }
+
+ public void addPet(Pet pet) {
+ getPetsInternal().add(pet);
+ pet.setOwner(this);
+ }
+
+ /**
+ * Return the Pet with the given name, or null if none found for this Owner.
+ *
+ * @param name to test
+ * @return true if pet name is already in use
+ */
+ public Pet getPet(String name) {
+ return getPet(name, false);
+ }
+
+ /**
+ * Return the Pet with the given name, or null if none found for this Owner.
+ *
+ * @param name to test
+ * @return true if pet name is already in use
+ */
+ public Pet getPet(String name, boolean ignoreNew) {
+ name = name.toLowerCase();
+ for (Pet pet : getPetsInternal()) {
+ if (!ignoreNew || !pet.isNew()) {
+ String compName = pet.getName();
+ compName = compName.toLowerCase();
+ if (compName.equals(name)) {
+ return pet;
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringCreator(this)
+
+ .append("id", this.getId())
+ .append("new", this.isNew())
+ .append("lastName", this.getLastName())
+ .append("firstName", this.getFirstName())
+ .append("address", this.address)
+ .append("city", this.city)
+ .append("telephone", this.telephone)
+ .toString();
+ }
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/model/Person.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/model/Person.java
new file mode 100644
index 000000000..d3e03c0dd
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/model/Person.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.model;
+
+import javax.persistence.Column;
+import javax.persistence.MappedSuperclass;
+
+import org.hibernate.validator.constraints.NotEmpty;
+
+/**
+ * Simple JavaBean domain object representing an person.
+ *
+ * @author Ken Krebs
+ */
+@MappedSuperclass
+public class Person extends BaseEntity {
+
+ @Column(name = "first_name")
+ @NotEmpty
+ protected String firstName;
+
+ @Column(name = "last_name")
+ @NotEmpty
+ protected String lastName;
+
+ public String getFirstName() {
+ return this.firstName;
+ }
+
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ }
+
+ public String getLastName() {
+ return this.lastName;
+ }
+
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ }
+
+
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/model/Pet.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/model/Pet.java
new file mode 100644
index 000000000..4bc2b92f7
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/model/Pet.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.model;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+
+import org.hibernate.annotations.Type;
+import org.joda.time.DateTime;
+import org.springframework.beans.support.MutableSortDefinition;
+import org.springframework.beans.support.PropertyComparator;
+import org.springframework.format.annotation.DateTimeFormat;
+
+/**
+ * Simple business object representing a pet.
+ *
+ * @author Ken Krebs
+ * @author Juergen Hoeller
+ * @author Sam Brannen
+ */
+@Entity
+@Table(name = "pets")
+public class Pet extends NamedEntity {
+
+ @Column(name = "birth_date")
+ @Type(type = "org.jadira.usertype.dateandtime.joda.PersistentDateTime")
+ @DateTimeFormat(pattern = "yyyy/MM/dd")
+ private DateTime birthDate;
+
+ @ManyToOne
+ @JoinColumn(name = "type_id")
+ private PetType type;
+
+ @ManyToOne
+ @JoinColumn(name = "owner_id")
+ private Owner owner;
+
+ @OneToMany(cascade = CascadeType.ALL, mappedBy = "pet", fetch = FetchType.EAGER)
+ private Set visits;
+
+
+ public void setBirthDate(DateTime birthDate) {
+ this.birthDate = birthDate;
+ }
+
+ public DateTime getBirthDate() {
+ return this.birthDate;
+ }
+
+ public void setType(PetType type) {
+ this.type = type;
+ }
+
+ public PetType getType() {
+ return this.type;
+ }
+
+ protected void setOwner(Owner owner) {
+ this.owner = owner;
+ }
+
+ public Owner getOwner() {
+ return this.owner;
+ }
+
+ protected void setVisitsInternal(Set visits) {
+ this.visits = visits;
+ }
+
+ protected Set getVisitsInternal() {
+ if (this.visits == null) {
+ this.visits = new HashSet();
+ }
+ return this.visits;
+ }
+
+ public List getVisits() {
+ List sortedVisits = new ArrayList(getVisitsInternal());
+ PropertyComparator.sort(sortedVisits, new MutableSortDefinition("date", false, false));
+ return Collections.unmodifiableList(sortedVisits);
+ }
+
+ public void addVisit(Visit visit) {
+ getVisitsInternal().add(visit);
+ visit.setPet(this);
+ }
+
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/model/PetType.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/model/PetType.java
new file mode 100644
index 000000000..99adf7591
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/model/PetType.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.model;
+
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+/**
+ * @author Juergen Hoeller
+ * Can be Cat, Dog, Hamster...
+ */
+@Entity
+@Table(name = "types")
+public class PetType extends NamedEntity {
+
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/model/Specialty.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/model/Specialty.java
new file mode 100644
index 000000000..6ea209cd4
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/model/Specialty.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.model;
+
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+/**
+ * Models a {@link Vet Vet's} specialty (for example, dentistry).
+ *
+ * @author Juergen Hoeller
+ */
+@Entity
+@Table(name = "specialties")
+public class Specialty extends NamedEntity {
+
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/model/Vet.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/model/Vet.java
new file mode 100644
index 000000000..c58bd85b2
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/model/Vet.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.model;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.JoinTable;
+import javax.persistence.ManyToMany;
+import javax.persistence.Table;
+import javax.xml.bind.annotation.XmlElement;
+
+import org.springframework.beans.support.MutableSortDefinition;
+import org.springframework.beans.support.PropertyComparator;
+
+/**
+ * Simple JavaBean domain object representing a veterinarian.
+ *
+ * @author Ken Krebs
+ * @author Juergen Hoeller
+ * @author Sam Brannen
+ * @author Arjen Poutsma
+ */
+@Entity
+@Table(name = "vets")
+public class Vet extends Person {
+
+ @ManyToMany(fetch = FetchType.EAGER)
+ @JoinTable(name = "vet_specialties", joinColumns = @JoinColumn(name = "vet_id"),
+ inverseJoinColumns = @JoinColumn(name = "specialty_id"))
+ private Set specialties;
+
+
+ protected void setSpecialtiesInternal(Set specialties) {
+ this.specialties = specialties;
+ }
+
+ protected Set getSpecialtiesInternal() {
+ if (this.specialties == null) {
+ this.specialties = new HashSet();
+ }
+ return this.specialties;
+ }
+
+ @XmlElement
+ public List getSpecialties() {
+ List sortedSpecs = new ArrayList(getSpecialtiesInternal());
+ PropertyComparator.sort(sortedSpecs, new MutableSortDefinition("name", true, true));
+ return Collections.unmodifiableList(sortedSpecs);
+ }
+
+ public int getNrOfSpecialties() {
+ return getSpecialtiesInternal().size();
+ }
+
+ public void addSpecialty(Specialty specialty) {
+ getSpecialtiesInternal().add(specialty);
+ }
+
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/model/Vets.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/model/Vets.java
new file mode 100644
index 000000000..e8f44a7bc
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/model/Vets.java
@@ -0,0 +1,44 @@
+/*
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * Simple domain object representing a list of veterinarians. Mostly here to be used for the 'vets' {@link
+ * org.springframework.web.servlet.view.xml.MarshallingView}.
+ *
+ * @author Arjen Poutsma
+ */
+@XmlRootElement
+public class Vets {
+
+ private List vets;
+
+ @XmlElement
+ public List getVetList() {
+ if (vets == null) {
+ vets = new ArrayList();
+ }
+ return vets;
+ }
+
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/model/Visit.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/model/Visit.java
new file mode 100644
index 000000000..ea03bde74
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/model/Visit.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.model;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+import org.hibernate.annotations.Type;
+import org.hibernate.validator.constraints.NotEmpty;
+import org.joda.time.DateTime;
+import org.springframework.format.annotation.DateTimeFormat;
+
+/**
+ * Simple JavaBean domain object representing a visit.
+ *
+ * @author Ken Krebs
+ */
+@Entity
+@Table(name = "visits")
+public class Visit extends BaseEntity {
+
+ /**
+ * Holds value of property date.
+ */
+ @Column(name = "visit_date")
+ @Type(type = "org.jadira.usertype.dateandtime.joda.PersistentDateTime")
+ @DateTimeFormat(pattern = "yyyy/MM/dd")
+ private DateTime date;
+
+ /**
+ * Holds value of property description.
+ */
+ @NotEmpty
+ @Column(name = "description")
+ private String description;
+
+ /**
+ * Holds value of property pet.
+ */
+ @ManyToOne
+ @JoinColumn(name = "pet_id")
+ private Pet pet;
+
+
+ /**
+ * Creates a new instance of Visit for the current date
+ */
+ public Visit() {
+ this.date = new DateTime();
+ }
+
+
+ /**
+ * Getter for property date.
+ *
+ * @return Value of property date.
+ */
+ public DateTime getDate() {
+ return this.date;
+ }
+
+ /**
+ * Setter for property date.
+ *
+ * @param date New value of property date.
+ */
+ public void setDate(DateTime date) {
+ this.date = date;
+ }
+
+ /**
+ * Getter for property description.
+ *
+ * @return Value of property description.
+ */
+ public String getDescription() {
+ return this.description;
+ }
+
+ /**
+ * Setter for property description.
+ *
+ * @param description New value of property description.
+ */
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ /**
+ * Getter for property pet.
+ *
+ * @return Value of property pet.
+ */
+ public Pet getPet() {
+ return this.pet;
+ }
+
+ /**
+ * Setter for property pet.
+ *
+ * @param pet New value of property pet.
+ */
+ public void setPet(Pet pet) {
+ this.pet = pet;
+ }
+
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/model/package-info.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/model/package-info.java
new file mode 100644
index 000000000..2723dc9ab
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/model/package-info.java
@@ -0,0 +1,8 @@
+
+/**
+ *
+ * The classes in this package represent PetClinic's business layer.
+ *
+ */
+package org.springframework.samples.petclinic.model;
+
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/OwnerRepository.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/OwnerRepository.java
new file mode 100644
index 000000000..131833706
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/OwnerRepository.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.
+ */
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.repository;
+
+import java.util.Collection;
+
+import org.springframework.dao.DataAccessException;
+import org.springframework.samples.petclinic.model.BaseEntity;
+import org.springframework.samples.petclinic.model.Owner;
+
+/**
+ * Repository class for Owner domain objects All method names are compliant with Spring Data naming
+ * 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
+ *
+ * @author Ken Krebs
+ * @author Juergen Hoeller
+ * @author Sam Brannen
+ * @author Michael Isvy
+ */
+public interface OwnerRepository {
+
+ /**
+ * Retrieve Owners from the data store by last name, returning all owners whose last name starts
+ * with the given name.
+ *
+ * @param lastName Value to search for
+ * @return a Collection of matching Owners (or an empty Collection if none
+ * found)
+ */
+ Collection findByLastName(String lastName) throws DataAccessException;
+
+ /**
+ * Retrieve an Owner from the data store by id.
+ *
+ * @param id the id to search for
+ * @return the Owner if found
+ * @throws org.springframework.dao.DataRetrievalFailureException
+ * if not found
+ */
+ Owner findById(int id) throws DataAccessException;
+
+
+ /**
+ * Save an Owner to the data store, either inserting or updating it.
+ *
+ * @param owner the Owner to save
+ * @see BaseEntity#isNew
+ */
+ void save(Owner owner) throws DataAccessException;
+
+
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/PetRepository.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/PetRepository.java
new file mode 100644
index 000000000..693b2e5e9
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/PetRepository.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.repository;
+
+import java.util.List;
+
+import org.springframework.dao.DataAccessException;
+import org.springframework.samples.petclinic.model.BaseEntity;
+import org.springframework.samples.petclinic.model.Pet;
+import org.springframework.samples.petclinic.model.PetType;
+
+/**
+ * Repository class for Pet domain objects All method names are compliant with Spring Data naming
+ * 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
+ *
+ * @author Ken Krebs
+ * @author Juergen Hoeller
+ * @author Sam Brannen
+ * @author Michael Isvy
+ */
+public interface PetRepository {
+
+ /**
+ * Retrieve all PetTypes from the data store.
+ *
+ * @return a Collection of PetTypes
+ */
+ List findPetTypes() throws DataAccessException;
+
+ /**
+ * Retrieve a Pet from the data store by id.
+ *
+ * @param id the id to search for
+ * @return the Pet if found
+ * @throws org.springframework.dao.DataRetrievalFailureException
+ * if not found
+ */
+ Pet findById(int id) throws DataAccessException;
+
+ /**
+ * Save a Pet to the data store, either inserting or updating it.
+ *
+ * @param pet the Pet to save
+ * @see BaseEntity#isNew
+ */
+ void save(Pet pet) throws DataAccessException;
+
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/VetRepository.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/VetRepository.java
new file mode 100644
index 000000000..923c3c23d
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/VetRepository.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.repository;
+
+import java.util.Collection;
+
+import org.springframework.dao.DataAccessException;
+import org.springframework.samples.petclinic.model.Vet;
+
+/**
+ * Repository class for Vet domain objects All method names are compliant with Spring Data naming
+ * 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
+ *
+ * @author Ken Krebs
+ * @author Juergen Hoeller
+ * @author Sam Brannen
+ * @author Michael Isvy
+ */
+public interface VetRepository {
+
+ /**
+ * Retrieve all Vets from the data store.
+ *
+ * @return a Collection of Vets
+ */
+ Collection findAll() throws DataAccessException;
+
+
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/VisitRepository.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/VisitRepository.java
new file mode 100644
index 000000000..96b7a208c
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/VisitRepository.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.repository;
+
+import java.util.List;
+
+import org.springframework.dao.DataAccessException;
+import org.springframework.samples.petclinic.model.BaseEntity;
+import org.springframework.samples.petclinic.model.Visit;
+
+/**
+ * Repository class for Visit domain objects All method names are compliant with Spring Data naming
+ * 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
+ *
+ * @author Ken Krebs
+ * @author Juergen Hoeller
+ * @author Sam Brannen
+ * @author Michael Isvy
+ */
+public interface VisitRepository {
+
+ /**
+ * Save a Visit to the data store, either inserting or updating it.
+ *
+ * @param visit the Visit to save
+ * @see BaseEntity#isNew
+ */
+ void save(Visit visit) throws DataAccessException;
+
+ List findByPetId(Integer petId);
+
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcOwnerRepositoryImpl.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcOwnerRepositoryImpl.java
new file mode 100644
index 000000000..4ae19653a
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcOwnerRepositoryImpl.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.repository.jdbc;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.sql.DataSource;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
+import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
+import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
+import org.springframework.orm.ObjectRetrievalFailureException;
+import org.springframework.samples.petclinic.model.Owner;
+import org.springframework.samples.petclinic.model.Pet;
+import org.springframework.samples.petclinic.model.PetType;
+import org.springframework.samples.petclinic.model.Visit;
+import org.springframework.samples.petclinic.repository.OwnerRepository;
+import org.springframework.samples.petclinic.repository.VisitRepository;
+import org.springframework.samples.petclinic.util.EntityUtils;
+import org.springframework.stereotype.Repository;
+
+/**
+ * A simple JDBC-based implementation of the {@link OwnerRepository} interface.
+ *
+ * @author Ken Krebs
+ * @author Juergen Hoeller
+ * @author Rob Harrop
+ * @author Sam Brannen
+ * @author Thomas Risberg
+ * @author Mark Fisher
+ */
+@Repository
+public class JdbcOwnerRepositoryImpl implements OwnerRepository {
+
+ private VisitRepository visitRepository;
+
+ private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
+
+ private SimpleJdbcInsert insertOwner;
+
+ @Autowired
+ public JdbcOwnerRepositoryImpl(DataSource dataSource, NamedParameterJdbcTemplate namedParameterJdbcTemplate,
+ VisitRepository visitRepository) {
+
+ this.insertOwner = new SimpleJdbcInsert(dataSource)
+ .withTableName("owners")
+ .usingGeneratedKeyColumns("id");
+
+ this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
+
+ this.visitRepository = visitRepository;
+ }
+
+
+ /**
+ * Loads {@link Owner Owners} from the data store by last name, returning all owners whose last name starts with
+ * the given name; also loads the {@link Pet Pets} and {@link Visit Visits} for the corresponding owners, if not
+ * already loaded.
+ */
+ @Override
+ public Collection findByLastName(String lastName) throws DataAccessException {
+ Map params = new HashMap();
+ params.put("lastName", lastName + "%");
+ List owners = this.namedParameterJdbcTemplate.query(
+ "SELECT id, first_name, last_name, address, city, telephone FROM owners WHERE last_name like :lastName",
+ params,
+ BeanPropertyRowMapper.newInstance(Owner.class)
+ );
+ loadOwnersPetsAndVisits(owners);
+ return owners;
+ }
+
+ /**
+ * Loads the {@link Owner} with the supplied id; also loads the {@link Pet Pets} and {@link Visit Visits}
+ * for the corresponding owner, if not already loaded.
+ */
+ @Override
+ public Owner findById(int id) throws DataAccessException {
+ Owner owner;
+ try {
+ Map params = new HashMap();
+ params.put("id", id);
+ owner = this.namedParameterJdbcTemplate.queryForObject(
+ "SELECT id, first_name, last_name, address, city, telephone FROM owners WHERE id= :id",
+ params,
+ BeanPropertyRowMapper.newInstance(Owner.class)
+ );
+ } catch (EmptyResultDataAccessException ex) {
+ throw new ObjectRetrievalFailureException(Owner.class, id);
+ }
+ loadPetsAndVisits(owner);
+ return owner;
+ }
+
+ public void loadPetsAndVisits(final Owner owner) {
+ Map params = new HashMap();
+ params.put("id", owner.getId().intValue());
+ final List pets = this.namedParameterJdbcTemplate.query(
+ "SELECT id, name, birth_date, type_id, owner_id FROM pets WHERE owner_id=:id",
+ params,
+ new JdbcPetRowMapper()
+ );
+ for (JdbcPet pet : pets) {
+ owner.addPet(pet);
+ pet.setType(EntityUtils.getById(getPetTypes(), PetType.class, pet.getTypeId()));
+ List visits = this.visitRepository.findByPetId(pet.getId());
+ for (Visit visit : visits) {
+ pet.addVisit(visit);
+ }
+ }
+ }
+
+ @Override
+ public void save(Owner owner) throws DataAccessException {
+ BeanPropertySqlParameterSource parameterSource = new BeanPropertySqlParameterSource(owner);
+ if (owner.isNew()) {
+ Number newKey = this.insertOwner.executeAndReturnKey(parameterSource);
+ owner.setId(newKey.intValue());
+ } else {
+ this.namedParameterJdbcTemplate.update(
+ "UPDATE owners SET first_name=:firstName, last_name=:lastName, address=:address, " +
+ "city=:city, telephone=:telephone WHERE id=:id",
+ parameterSource);
+ }
+ }
+
+ public Collection getPetTypes() throws DataAccessException {
+ return this.namedParameterJdbcTemplate.query(
+ "SELECT id, name FROM types ORDER BY name", new HashMap(),
+ BeanPropertyRowMapper.newInstance(PetType.class));
+ }
+
+ /**
+ * Loads the {@link Pet} and {@link Visit} data for the supplied {@link List} of {@link Owner Owners}.
+ *
+ * @param owners the list of owners for whom the pet and visit data should be loaded
+ * @see #loadPetsAndVisits(Owner)
+ */
+ private void loadOwnersPetsAndVisits(List owners) {
+ for (Owner owner : owners) {
+ loadPetsAndVisits(owner);
+ }
+ }
+
+
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPet.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPet.java
new file mode 100644
index 000000000..f2ed25823
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPet.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.repository.jdbc;
+
+import org.springframework.samples.petclinic.model.Pet;
+
+/**
+ * Subclass of Pet that carries temporary id properties which are only relevant for a JDBC implementation of the
+ * ClinicService.
+ *
+ * @author Juergen Hoeller
+ */
+class JdbcPet extends Pet {
+
+ private int typeId;
+
+ private int ownerId;
+
+
+ public void setTypeId(int typeId) {
+ this.typeId = typeId;
+ }
+
+ public int getTypeId() {
+ return this.typeId;
+ }
+
+ public void setOwnerId(int ownerId) {
+ this.ownerId = ownerId;
+ }
+
+ public int getOwnerId() {
+ return this.ownerId;
+ }
+
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetRepositoryImpl.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetRepositoryImpl.java
new file mode 100644
index 000000000..546451dcc
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetRepositoryImpl.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.repository.jdbc;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.sql.DataSource;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
+import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
+import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
+import org.springframework.orm.ObjectRetrievalFailureException;
+import org.springframework.samples.petclinic.model.Owner;
+import org.springframework.samples.petclinic.model.Pet;
+import org.springframework.samples.petclinic.model.PetType;
+import org.springframework.samples.petclinic.model.Visit;
+import org.springframework.samples.petclinic.repository.OwnerRepository;
+import org.springframework.samples.petclinic.repository.PetRepository;
+import org.springframework.samples.petclinic.repository.VisitRepository;
+import org.springframework.samples.petclinic.util.EntityUtils;
+import org.springframework.stereotype.Repository;
+
+/**
+ * @author Ken Krebs
+ * @author Juergen Hoeller
+ * @author Rob Harrop
+ * @author Sam Brannen
+ * @author Thomas Risberg
+ * @author Mark Fisher
+ */
+@Repository
+public class JdbcPetRepositoryImpl implements PetRepository {
+
+ private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
+
+ private SimpleJdbcInsert insertPet;
+
+ private OwnerRepository ownerRepository;
+
+ private VisitRepository visitRepository;
+
+
+ @Autowired
+ public JdbcPetRepositoryImpl(DataSource dataSource, OwnerRepository ownerRepository, VisitRepository visitRepository) {
+ this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
+
+ this.insertPet = new SimpleJdbcInsert(dataSource)
+ .withTableName("pets")
+ .usingGeneratedKeyColumns("id");
+
+ this.ownerRepository = ownerRepository;
+ this.visitRepository = visitRepository;
+ }
+
+ @Override
+ public List findPetTypes() throws DataAccessException {
+ Map params = new HashMap();
+ return this.namedParameterJdbcTemplate.query(
+ "SELECT id, name FROM types ORDER BY name",
+ params,
+ BeanPropertyRowMapper.newInstance(PetType.class));
+ }
+
+ @Override
+ public Pet findById(int id) throws DataAccessException {
+ JdbcPet pet;
+ try {
+ Map params = new HashMap();
+ params.put("id", id);
+ pet = this.namedParameterJdbcTemplate.queryForObject(
+ "SELECT id, name, birth_date, type_id, owner_id FROM pets WHERE id=:id",
+ params,
+ new JdbcPetRowMapper());
+ } catch (EmptyResultDataAccessException ex) {
+ throw new ObjectRetrievalFailureException(Pet.class, new Integer(id));
+ }
+ Owner owner = this.ownerRepository.findById(pet.getOwnerId());
+ owner.addPet(pet);
+ pet.setType(EntityUtils.getById(findPetTypes(), PetType.class, pet.getTypeId()));
+
+ List visits = this.visitRepository.findByPetId(pet.getId());
+ for (Visit visit : visits) {
+ pet.addVisit(visit);
+ }
+ return pet;
+ }
+
+ @Override
+ public void save(Pet pet) throws DataAccessException {
+ if (pet.isNew()) {
+ Number newKey = this.insertPet.executeAndReturnKey(
+ createPetParameterSource(pet));
+ pet.setId(newKey.intValue());
+ } else {
+ this.namedParameterJdbcTemplate.update(
+ "UPDATE pets SET name=:name, birth_date=:birth_date, type_id=:type_id, " +
+ "owner_id=:owner_id WHERE id=:id",
+ createPetParameterSource(pet));
+ }
+ }
+
+ /**
+ * Creates a {@link MapSqlParameterSource} based on data values from the supplied {@link Pet} instance.
+ */
+ private MapSqlParameterSource createPetParameterSource(Pet pet) {
+ return new MapSqlParameterSource()
+ .addValue("id", pet.getId())
+ .addValue("name", pet.getName())
+ .addValue("birth_date", pet.getBirthDate().toDate())
+ .addValue("type_id", pet.getType().getId())
+ .addValue("owner_id", pet.getOwner().getId());
+ }
+
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetRowMapper.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetRowMapper.java
new file mode 100644
index 000000000..4164f746f
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetRowMapper.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.repository.jdbc;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Date;
+
+import org.joda.time.DateTime;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+
+/**
+ * {@link BeanPropertyRowMapper} implementation mapping data from a {@link ResultSet} to the corresponding properties
+ * of the {@link JdbcPet} class.
+ */
+class JdbcPetRowMapper extends BeanPropertyRowMapper {
+
+ @Override
+ public JdbcPet mapRow(ResultSet rs, int rownum) throws SQLException {
+ JdbcPet pet = new JdbcPet();
+ pet.setId(rs.getInt("id"));
+ pet.setName(rs.getString("name"));
+ Date birthDate = rs.getDate("birth_date");
+ pet.setBirthDate(new DateTime(birthDate));
+ pet.setTypeId(rs.getInt("type_id"));
+ pet.setOwnerId(rs.getInt("owner_id"));
+ return pet;
+ }
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcVetRepositoryImpl.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcVetRepositoryImpl.java
new file mode 100644
index 000000000..9a85bde11
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcVetRepositoryImpl.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.repository.jdbc;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.samples.petclinic.model.Specialty;
+import org.springframework.samples.petclinic.model.Vet;
+import org.springframework.samples.petclinic.repository.VetRepository;
+import org.springframework.samples.petclinic.util.EntityUtils;
+import org.springframework.stereotype.Repository;
+
+/**
+ * A simple JDBC-based implementation of the {@link VetRepository} interface. Uses @Cacheable to cache the result of the
+ * {@link findAll} method
+ *
+ * @author Ken Krebs
+ * @author Juergen Hoeller
+ * @author Rob Harrop
+ * @author Sam Brannen
+ * @author Thomas Risberg
+ * @author Mark Fisher
+ * @author Michael Isvy
+ */
+@Repository
+public class JdbcVetRepositoryImpl implements VetRepository {
+
+ private JdbcTemplate jdbcTemplate;
+
+ @Autowired
+ public JdbcVetRepositoryImpl(JdbcTemplate jdbcTemplate) {
+ this.jdbcTemplate = jdbcTemplate;
+ }
+
+ /**
+ * Refresh the cache of Vets that the ClinicService is holding.
+ *
+ * @see org.springframework.samples.petclinic.model.service.ClinicService#shouldFindVets()
+ */
+ @Override
+ public Collection findAll() throws DataAccessException {
+ List vets = new ArrayList();
+ // Retrieve the list of all vets.
+ vets.addAll(this.jdbcTemplate.query(
+ "SELECT id, first_name, last_name FROM vets ORDER BY last_name,first_name",
+ BeanPropertyRowMapper.newInstance(Vet.class)));
+
+ // Retrieve the list of all possible specialties.
+ final List specialties = this.jdbcTemplate.query(
+ "SELECT id, name FROM specialties",
+ BeanPropertyRowMapper.newInstance(Specialty.class));
+
+ // Build each vet's list of specialties.
+ for (Vet vet : vets) {
+ final List vetSpecialtiesIds = this.jdbcTemplate.query(
+ "SELECT specialty_id FROM vet_specialties WHERE vet_id=?",
+ new BeanPropertyRowMapper() {
+ @Override
+ public Integer mapRow(ResultSet rs, int row) throws SQLException {
+ return Integer.valueOf(rs.getInt(1));
+ }
+ },
+ vet.getId().intValue());
+ for (int specialtyId : vetSpecialtiesIds) {
+ Specialty specialty = EntityUtils.getById(specialties, Specialty.class, specialtyId);
+ vet.addSpecialty(specialty);
+ }
+ }
+ return vets;
+ }
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcVisitRepositoryImpl.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcVisitRepositoryImpl.java
new file mode 100644
index 000000000..b6a004561
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcVisitRepositoryImpl.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.repository.jdbc;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Date;
+import java.util.List;
+
+import javax.sql.DataSource;
+
+import org.joda.time.DateTime;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
+import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
+import org.springframework.samples.petclinic.model.Visit;
+import org.springframework.samples.petclinic.repository.VisitRepository;
+import org.springframework.stereotype.Repository;
+
+/**
+ * A simple JDBC-based implementation of the {@link VisitRepository} interface.
+ *
+ * @author Ken Krebs
+ * @author Juergen Hoeller
+ * @author Rob Harrop
+ * @author Sam Brannen
+ * @author Thomas Risberg
+ * @author Mark Fisher
+ * @author Michael Isvy
+ */
+@Repository
+public class JdbcVisitRepositoryImpl implements VisitRepository {
+
+ private JdbcTemplate jdbcTemplate;
+
+ private SimpleJdbcInsert insertVisit;
+
+ @Autowired
+ public JdbcVisitRepositoryImpl(DataSource dataSource) {
+ this.jdbcTemplate = new JdbcTemplate(dataSource);
+
+ this.insertVisit = new SimpleJdbcInsert(dataSource)
+ .withTableName("visits")
+ .usingGeneratedKeyColumns("id");
+ }
+
+
+ @Override
+ public void save(Visit visit) throws DataAccessException {
+ if (visit.isNew()) {
+ Number newKey = this.insertVisit.executeAndReturnKey(
+ createVisitParameterSource(visit));
+ visit.setId(newKey.intValue());
+ } else {
+ throw new UnsupportedOperationException("Visit update not supported");
+ }
+ }
+
+ public void deletePet(int id) throws DataAccessException {
+ this.jdbcTemplate.update("DELETE FROM pets WHERE id=?", id);
+ }
+
+
+ /**
+ * Creates a {@link MapSqlParameterSource} based on data values from the supplied {@link Visit} instance.
+ */
+ private MapSqlParameterSource createVisitParameterSource(Visit visit) {
+ return new MapSqlParameterSource()
+ .addValue("id", visit.getId())
+ .addValue("visit_date", visit.getDate().toDate())
+ .addValue("description", visit.getDescription())
+ .addValue("pet_id", visit.getPet().getId());
+ }
+
+ @Override
+ public List findByPetId(Integer petId) {
+ final List visits = this.jdbcTemplate.query(
+ "SELECT id, visit_date, description FROM visits WHERE pet_id=?",
+ new BeanPropertyRowMapper() {
+ @Override
+ public Visit mapRow(ResultSet rs, int row) throws SQLException {
+ Visit visit = new Visit();
+ visit.setId(rs.getInt("id"));
+ Date visitDate = rs.getDate("visit_date");
+ visit.setDate(new DateTime(visitDate));
+ visit.setDescription(rs.getString("description"));
+ return visit;
+ }
+ },
+ petId);
+ return visits;
+ }
+
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/jdbc/package-info.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/jdbc/package-info.java
new file mode 100644
index 000000000..edd0bf855
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/jdbc/package-info.java
@@ -0,0 +1,9 @@
+
+/**
+ *
+ * The classes in this package represent the JDBC implementation
+ * of PetClinic's persistence layer.
+ *
+ */
+package org.springframework.samples.petclinic.repository.jdbc;
+
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaOwnerRepositoryImpl.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaOwnerRepositoryImpl.java
new file mode 100644
index 000000000..c7398df5b
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaOwnerRepositoryImpl.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.repository.jpa;
+
+import java.util.Collection;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.persistence.Query;
+
+import org.springframework.orm.hibernate3.support.OpenSessionInViewFilter;
+import org.springframework.samples.petclinic.model.Owner;
+import org.springframework.samples.petclinic.repository.OwnerRepository;
+import org.springframework.stereotype.Repository;
+
+/**
+ * JPA implementation of the {@link OwnerRepository} interface.
+ *
+ * @author Mike Keith
+ * @author Rod Johnson
+ * @author Sam Brannen
+ * @author Michael Isvy
+ * @since 22.4.2006
+ */
+@Repository
+public class JpaOwnerRepositoryImpl implements OwnerRepository {
+
+ @PersistenceContext
+ private EntityManager em;
+
+
+ /**
+ * Important: in the current version of this method, we load Owners with all their Pets and Visits while
+ * we do not need Visits at all and we only need one property from the Pet objects (the 'name' property).
+ * There are some ways to improve it such as:
+ * - creating a Ligtweight class (example here: https://community.jboss.org/wiki/LightweightClass)
+ * - Turning on lazy-loading and using {@link OpenSessionInViewFilter}
+ */
+ @SuppressWarnings("unchecked")
+ public Collection findByLastName(String lastName) {
+ // using 'join fetch' because a single query should load both owners and pets
+ // using 'left join fetch' because it might happen that an owner does not have pets yet
+ Query query = this.em.createQuery("SELECT DISTINCT owner FROM Owner owner left join fetch owner.pets WHERE owner.lastName LIKE :lastName");
+ query.setParameter("lastName", lastName + "%");
+ return query.getResultList();
+ }
+
+ @Override
+ public Owner findById(int id) {
+ // using 'join fetch' because a single query should load both owners and pets
+ // using 'left join fetch' because it might happen that an owner does not have pets yet
+ Query query = this.em.createQuery("SELECT owner FROM Owner owner left join fetch owner.pets WHERE owner.id =:id");
+ query.setParameter("id", id);
+ return (Owner) query.getSingleResult();
+ }
+
+
+ @Override
+ public void save(Owner owner) {
+ if (owner.getId() == null) {
+ this.em.persist(owner);
+ }
+ else {
+ this.em.merge(owner);
+ }
+
+ }
+
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaPetRepositoryImpl.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaPetRepositoryImpl.java
new file mode 100644
index 000000000..84d564da4
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaPetRepositoryImpl.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.repository.jpa;
+
+import java.util.List;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+
+import org.springframework.samples.petclinic.model.Pet;
+import org.springframework.samples.petclinic.model.PetType;
+import org.springframework.samples.petclinic.repository.PetRepository;
+import org.springframework.stereotype.Repository;
+
+/**
+ * JPA implementation of the {@link PetRepository} interface.
+ *
+ * @author Mike Keith
+ * @author Rod Johnson
+ * @author Sam Brannen
+ * @author Michael Isvy
+ * @since 22.4.2006
+ */
+@Repository
+public class JpaPetRepositoryImpl implements PetRepository {
+
+ @PersistenceContext
+ private EntityManager em;
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public List findPetTypes() {
+ return this.em.createQuery("SELECT ptype FROM PetType ptype ORDER BY ptype.name").getResultList();
+ }
+
+ @Override
+ public Pet findById(int id) {
+ return this.em.find(Pet.class, id);
+ }
+
+ @Override
+ public void save(Pet pet) {
+ if (pet.getId() == null) {
+ this.em.persist(pet);
+ }
+ else {
+ this.em.merge(pet);
+ }
+ }
+
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaVetRepositoryImpl.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaVetRepositoryImpl.java
new file mode 100644
index 000000000..e4c222b65
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaVetRepositoryImpl.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.repository.jpa;
+
+import java.util.Collection;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.samples.petclinic.model.Vet;
+import org.springframework.samples.petclinic.repository.VetRepository;
+import org.springframework.stereotype.Repository;
+
+/**
+ * JPA implementation of the {@link VetRepository} interface.
+ *
+ * @author Mike Keith
+ * @author Rod Johnson
+ * @author Sam Brannen
+ * @author Michael Isvy
+ * @since 22.4.2006
+ */
+@Repository
+public class JpaVetRepositoryImpl implements VetRepository {
+
+ @PersistenceContext
+ private EntityManager em;
+
+
+ @Override
+ @Cacheable(value = "vets")
+ @SuppressWarnings("unchecked")
+ public Collection findAll() {
+ return this.em.createQuery("SELECT distinct vet FROM Vet vet left join fetch vet.specialties ORDER BY vet.lastName, vet.firstName").getResultList();
+ }
+
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaVisitRepositoryImpl.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaVisitRepositoryImpl.java
new file mode 100644
index 000000000..3415def96
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaVisitRepositoryImpl.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.repository.jpa;
+
+import java.util.List;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.persistence.Query;
+
+import org.springframework.samples.petclinic.model.Visit;
+import org.springframework.samples.petclinic.repository.VisitRepository;
+import org.springframework.stereotype.Repository;
+
+/**
+ * JPA implementation of the ClinicService interface using EntityManager.
+ *
+ *
The mappings are defined in "orm.xml" located in the META-INF directory.
+ *
+ * @author Mike Keith
+ * @author Rod Johnson
+ * @author Sam Brannen
+ * @author Michael Isvy
+ * @since 22.4.2006
+ */
+@Repository
+public class JpaVisitRepositoryImpl implements VisitRepository {
+
+ @PersistenceContext
+ private EntityManager em;
+
+
+ @Override
+ public void save(Visit visit) {
+ if (visit.getId() == null) {
+ this.em.persist(visit);
+ }
+ else {
+ this.em.merge(visit);
+ }
+ }
+
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public List findByPetId(Integer petId) {
+ Query query = this.em.createQuery("SELECT visit FROM Visit v where v.pets.id= :id");
+ query.setParameter("id", petId);
+ return query.getResultList();
+ }
+
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/jpa/package-info.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/jpa/package-info.java
new file mode 100644
index 000000000..13c8552ed
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/jpa/package-info.java
@@ -0,0 +1,9 @@
+
+/**
+ *
+ * The classes in this package represent the JPA implementation
+ * of PetClinic's persistence layer.
+ *
+ */
+package org.springframework.samples.petclinic.repository.jpa;
+
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataOwnerRepository.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataOwnerRepository.java
new file mode 100644
index 000000000..ca1f709f6
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataOwnerRepository.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.repository.springdatajpa;
+
+import java.util.Collection;
+
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.Repository;
+import org.springframework.data.repository.query.Param;
+import org.springframework.samples.petclinic.model.Owner;
+import org.springframework.samples.petclinic.repository.OwnerRepository;
+
+/**
+ * Spring Data JPA specialization of the {@link OwnerRepository} interface
+ *
+ * @author Michael Isvy
+ * @since 15.1.2013
+ */
+public interface SpringDataOwnerRepository extends OwnerRepository, Repository {
+
+ @Override
+ @Query("SELECT DISTINCT owner FROM Owner owner left join fetch owner.pets WHERE owner.lastName LIKE :lastName%")
+ public Collection findByLastName(@Param("lastName") String lastName);
+
+ @Override
+ @Query("SELECT owner FROM Owner owner left join fetch owner.pets WHERE owner.id =:id")
+ public Owner findById(@Param("id") int id);
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataPetRepository.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataPetRepository.java
new file mode 100644
index 000000000..56a413147
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataPetRepository.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.repository.springdatajpa;
+
+import java.util.List;
+
+import org.springframework.dao.DataAccessException;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.Repository;
+import org.springframework.samples.petclinic.model.Pet;
+import org.springframework.samples.petclinic.model.PetType;
+import org.springframework.samples.petclinic.repository.PetRepository;
+
+/**
+ * Spring Data JPA specialization of the {@link PetRepository} interface
+ *
+ * @author Michael Isvy
+ * @since 15.1.2013
+ */
+public interface SpringDataPetRepository extends PetRepository, Repository {
+
+ @Override
+ @Query("SELECT ptype FROM PetType ptype ORDER BY ptype.name")
+ List findPetTypes() throws DataAccessException;
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataVetRepository.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataVetRepository.java
new file mode 100644
index 000000000..b8211b707
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataVetRepository.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.repository.springdatajpa;
+
+import org.springframework.data.repository.Repository;
+import org.springframework.samples.petclinic.model.Vet;
+import org.springframework.samples.petclinic.repository.VetRepository;
+
+/**
+ * Spring Data JPA specialization of the {@link VetRepository} interface
+ *
+ * @author Michael Isvy
+ * @since 15.1.2013
+ */
+public interface SpringDataVetRepository extends VetRepository, Repository {
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataVisitRepository.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataVisitRepository.java
new file mode 100644
index 000000000..84740224b
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataVisitRepository.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.repository.springdatajpa;
+
+import org.springframework.data.repository.Repository;
+import org.springframework.samples.petclinic.model.Visit;
+import org.springframework.samples.petclinic.repository.VisitRepository;
+
+/**
+ * Spring Data JPA specialization of the {@link VisitRepository} interface
+ *
+ * @author Michael Isvy
+ * @since 15.1.2013
+ */
+public interface SpringDataVisitRepository extends VisitRepository, Repository {
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/service/ClinicService.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/service/ClinicService.java
new file mode 100644
index 000000000..936582129
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/service/ClinicService.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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;
+
+import java.util.Collection;
+
+import org.springframework.dao.DataAccessException;
+import org.springframework.samples.petclinic.model.Owner;
+import org.springframework.samples.petclinic.model.Pet;
+import org.springframework.samples.petclinic.model.PetType;
+import org.springframework.samples.petclinic.model.Vet;
+import org.springframework.samples.petclinic.model.Visit;
+
+
+/**
+ * Mostly used as a facade so all controllers have a single point of entry
+ *
+ * @author Michael Isvy
+ */
+public interface ClinicService {
+
+ Collection findPetTypes() throws DataAccessException;
+
+ Owner findOwnerById(int id) throws DataAccessException;
+
+ Pet findPetById(int id) throws DataAccessException;
+
+ void savePet(Pet pet) throws DataAccessException;
+
+ void saveVisit(Visit visit) throws DataAccessException;
+
+ Collection findVets() throws DataAccessException;
+
+ void saveOwner(Owner owner) throws DataAccessException;
+
+ Collection findOwnerByLastName(String lastName) throws DataAccessException;
+
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/service/ClinicServiceImpl.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/service/ClinicServiceImpl.java
new file mode 100644
index 000000000..0d7ff4d0c
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/service/ClinicServiceImpl.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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;
+
+import java.util.Collection;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.dao.DataAccessException;
+import org.springframework.samples.petclinic.model.Owner;
+import org.springframework.samples.petclinic.model.Pet;
+import org.springframework.samples.petclinic.model.PetType;
+import org.springframework.samples.petclinic.model.Vet;
+import org.springframework.samples.petclinic.model.Visit;
+import org.springframework.samples.petclinic.repository.OwnerRepository;
+import org.springframework.samples.petclinic.repository.PetRepository;
+import org.springframework.samples.petclinic.repository.VetRepository;
+import org.springframework.samples.petclinic.repository.VisitRepository;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Mostly used as a facade for all Petclinic controllers
+ * Also a placeholder for @Transactional and @Cacheable annotations
+ *
+ * @author Michael Isvy
+ */
+@Service
+public class ClinicServiceImpl implements ClinicService {
+
+ private PetRepository petRepository;
+ private VetRepository vetRepository;
+ private OwnerRepository ownerRepository;
+ private VisitRepository visitRepository;
+
+ @Autowired
+ public ClinicServiceImpl(PetRepository petRepository, VetRepository vetRepository, OwnerRepository ownerRepository, VisitRepository visitRepository) {
+ this.petRepository = petRepository;
+ this.vetRepository = vetRepository;
+ this.ownerRepository = ownerRepository;
+ this.visitRepository = visitRepository;
+ }
+
+ @Override
+ @Transactional(readOnly = true)
+ public Collection findPetTypes() throws DataAccessException {
+ return petRepository.findPetTypes();
+ }
+
+ @Override
+ @Transactional(readOnly = true)
+ public Owner findOwnerById(int id) throws DataAccessException {
+ return ownerRepository.findById(id);
+ }
+
+ @Override
+ @Transactional(readOnly = true)
+ public Collection findOwnerByLastName(String lastName) throws DataAccessException {
+ return ownerRepository.findByLastName(lastName);
+ }
+
+ @Override
+ @Transactional
+ public void saveOwner(Owner owner) throws DataAccessException {
+ ownerRepository.save(owner);
+ }
+
+
+ @Override
+ @Transactional
+ public void saveVisit(Visit visit) throws DataAccessException {
+ visitRepository.save(visit);
+ }
+
+
+ @Override
+ @Transactional(readOnly = true)
+ public Pet findPetById(int id) throws DataAccessException {
+ return petRepository.findById(id);
+ }
+
+ @Override
+ @Transactional
+ public void savePet(Pet pet) throws DataAccessException {
+ petRepository.save(pet);
+ }
+
+ @Override
+ @Transactional(readOnly = true)
+ @Cacheable(value = "vets")
+ public Collection findVets() throws DataAccessException {
+ return vetRepository.findAll();
+ }
+
+
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/util/CallMonitoringAspect.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/util/CallMonitoringAspect.java
new file mode 100644
index 000000000..3c699842f
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/util/CallMonitoringAspect.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.util;
+
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.springframework.jmx.export.annotation.ManagedAttribute;
+import org.springframework.jmx.export.annotation.ManagedOperation;
+import org.springframework.jmx.export.annotation.ManagedResource;
+import org.springframework.util.StopWatch;
+
+/**
+ * Simple aspect that monitors call count and call invocation time. It uses JMX annotations and therefore can be
+ * monitored using any JMX console such as the jConsole
+ *
+ * @author Rob Harrop
+ * @author Juergen Hoeller
+ * @author Michael Isvy
+ * @since 2.5
+ */
+@ManagedResource("petclinic:type=CallMonitor")
+@Aspect
+public class CallMonitoringAspect {
+
+ private boolean enabled = true;
+
+ private int callCount = 0;
+
+ private long accumulatedCallTime = 0;
+
+
+ @ManagedAttribute
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ @ManagedAttribute
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ @ManagedOperation
+ public void reset() {
+ this.callCount = 0;
+ this.accumulatedCallTime = 0;
+ }
+
+ @ManagedAttribute
+ public int getCallCount() {
+ return callCount;
+ }
+
+ @ManagedAttribute
+ public long getCallTime() {
+ if (this.callCount > 0)
+ return this.accumulatedCallTime / this.callCount;
+ else
+ return 0;
+ }
+
+
+ @Around("within(@org.springframework.stereotype.Repository *)")
+ public Object invoke(ProceedingJoinPoint joinPoint) throws Throwable {
+ if (this.enabled) {
+ StopWatch sw = new StopWatch(joinPoint.toShortString());
+
+ sw.start("invoke");
+ try {
+ return joinPoint.proceed();
+ } finally {
+ sw.stop();
+ synchronized (this) {
+ this.callCount++;
+ this.accumulatedCallTime += sw.getTotalTimeMillis();
+ }
+ }
+ } else {
+ return joinPoint.proceed();
+ }
+ }
+
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/util/EntityUtils.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/util/EntityUtils.java
new file mode 100644
index 000000000..a18f65c39
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/util/EntityUtils.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.util;
+
+import java.util.Collection;
+
+import org.springframework.orm.ObjectRetrievalFailureException;
+import org.springframework.samples.petclinic.model.BaseEntity;
+
+/**
+ * Utility methods for handling entities. Separate from the BaseEntity class mainly because of dependency on the
+ * ORM-associated ObjectRetrievalFailureException.
+ *
+ * @author Juergen Hoeller
+ * @author Sam Brannen
+ * @see org.springframework.samples.petclinic.model.BaseEntity
+ * @since 29.10.2003
+ */
+public abstract class EntityUtils {
+
+ /**
+ * Look up the entity of the given class with the given id in the given collection.
+ *
+ * @param entities the collection to search
+ * @param entityClass the entity class to look up
+ * @param entityId the entity id to look up
+ * @return the found entity
+ * @throws ObjectRetrievalFailureException
+ * if the entity was not found
+ */
+ public static T getById(Collection entities, Class entityClass, int entityId)
+ throws ObjectRetrievalFailureException {
+ for (T entity : entities) {
+ if (entity.getId().intValue() == entityId && entityClass.isInstance(entity)) {
+ return entity;
+ }
+ }
+ throw new ObjectRetrievalFailureException(entityClass, entityId);
+ }
+
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/web/CrashController.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/web/CrashController.java
new file mode 100644
index 000000000..e413f3f3b
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/web/CrashController.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.web;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+/**
+ * Controller used to showcase what happens when an exception is thrown
+ *
+ * @author Michael Isvy
+ *
+ * Also see how the bean of type 'SimpleMappingExceptionResolver' has been declared inside
+ * /WEB-INF/mvc-core-config.xml
+ */
+@Controller
+public class CrashController {
+
+ @RequestMapping(value = "/oups", method = RequestMethod.GET)
+ public String triggerException() {
+ throw new RuntimeException("Expected: controller used to showcase what " +
+ "happens when an exception is thrown");
+ }
+
+
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java
new file mode 100644
index 000000000..ecbbce559
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.web;
+
+import java.util.Collection;
+import java.util.Map;
+
+import javax.validation.Valid;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.samples.petclinic.model.Owner;
+import org.springframework.samples.petclinic.service.ClinicService;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.bind.WebDataBinder;
+import org.springframework.web.bind.annotation.InitBinder;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.SessionAttributes;
+import org.springframework.web.bind.support.SessionStatus;
+import org.springframework.web.servlet.ModelAndView;
+
+/**
+ * @author Juergen Hoeller
+ * @author Ken Krebs
+ * @author Arjen Poutsma
+ * @author Michael Isvy
+ */
+@Controller
+@SessionAttributes(types = Owner.class)
+public class OwnerController {
+
+ private final ClinicService clinicService;
+
+
+ @Autowired
+ public OwnerController(ClinicService clinicService) {
+ this.clinicService = clinicService;
+ }
+
+ @InitBinder
+ public void setAllowedFields(WebDataBinder dataBinder) {
+ dataBinder.setDisallowedFields("id");
+ }
+
+ @RequestMapping(value = "/owners/new", method = RequestMethod.GET)
+ public String initCreationForm(Map model) {
+ Owner owner = new Owner();
+ model.put("owner", owner);
+ return "owners/createOrUpdateOwnerForm";
+ }
+
+ @RequestMapping(value = "/owners/new", method = RequestMethod.POST)
+ public String processCreationForm(@Valid Owner owner, BindingResult result, SessionStatus status) {
+ if (result.hasErrors()) {
+ return "owners/createOrUpdateOwnerForm";
+ } else {
+ this.clinicService.saveOwner(owner);
+ status.setComplete();
+ return "redirect:/owners/" + owner.getId();
+ }
+ }
+
+ @RequestMapping(value = "/owners/find", method = RequestMethod.GET)
+ public String initFindForm(Map model) {
+ model.put("owner", new Owner());
+ return "owners/findOwners";
+ }
+
+ @RequestMapping(value = "/owners", method = RequestMethod.GET)
+ public String processFindForm(Owner owner, BindingResult result, Map model) {
+
+ // allow parameterless GET request for /owners to return all records
+ if (owner.getLastName() == null) {
+ owner.setLastName(""); // empty string signifies broadest possible search
+ }
+
+ // find owners by last name
+ Collection results = this.clinicService.findOwnerByLastName(owner.getLastName());
+ if (results.isEmpty()) {
+ // no owners found
+ result.rejectValue("lastName", "notFound", "not found");
+ return "owners/findOwners";
+ }
+ else if (results.size() == 1) {
+ // 1 owner found
+ owner = results.iterator().next();
+ return "redirect:/owners/" + owner.getId();
+ }
+ else {
+ // multiple owners found
+ model.put("selections", results);
+ return "owners/ownersList";
+ }
+ }
+
+ @RequestMapping(value = "/owners/{ownerId}/edit", method = RequestMethod.GET)
+ public String initUpdateOwnerForm(@PathVariable("ownerId") int ownerId, Model model) {
+ Owner owner = this.clinicService.findOwnerById(ownerId);
+ model.addAttribute(owner);
+ return "owners/createOrUpdateOwnerForm";
+ }
+
+ @RequestMapping(value = "/owners/{ownerId}/edit", method = RequestMethod.PUT)
+ public String processUpdateOwnerForm(@Valid Owner owner, BindingResult result, SessionStatus status) {
+ if (result.hasErrors()) {
+ return "owners/createOrUpdateOwnerForm";
+ } else {
+ this.clinicService.saveOwner(owner);
+ status.setComplete();
+ return "redirect:/owners/{ownerId}";
+ }
+ }
+
+ /**
+ * Custom handler for displaying an owner.
+ *
+ * @param ownerId the ID of the owner to display
+ * @return a ModelMap with the model attributes for the view
+ */
+ @RequestMapping("/owners/{ownerId}")
+ public ModelAndView showOwner(@PathVariable("ownerId") int ownerId) {
+ ModelAndView mav = new ModelAndView("owners/ownerDetails");
+ mav.addObject(this.clinicService.findOwnerById(ownerId));
+ return mav;
+ }
+
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/web/PetController.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/web/PetController.java
new file mode 100644
index 000000000..ea8aeaaa8
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/web/PetController.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.web;
+
+import java.util.Collection;
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.samples.petclinic.model.Owner;
+import org.springframework.samples.petclinic.model.Pet;
+import org.springframework.samples.petclinic.model.PetType;
+import org.springframework.samples.petclinic.service.ClinicService;
+import org.springframework.stereotype.Controller;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.bind.WebDataBinder;
+import org.springframework.web.bind.annotation.InitBinder;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.SessionAttributes;
+import org.springframework.web.bind.support.SessionStatus;
+
+/**
+ * @author Juergen Hoeller
+ * @author Ken Krebs
+ * @author Arjen Poutsma
+ */
+@Controller
+@SessionAttributes("pet")
+public class PetController {
+
+ private final ClinicService clinicService;
+
+
+ @Autowired
+ public PetController(ClinicService clinicService) {
+ this.clinicService = clinicService;
+ }
+
+ @ModelAttribute("types")
+ public Collection populatePetTypes() {
+ return this.clinicService.findPetTypes();
+ }
+
+ @InitBinder
+ public void setAllowedFields(WebDataBinder dataBinder) {
+ dataBinder.setDisallowedFields("id");
+ }
+
+ @RequestMapping(value = "/owners/{ownerId}/pets/new", method = RequestMethod.GET)
+ public String initCreationForm(@PathVariable("ownerId") int ownerId, Map model) {
+ Owner owner = this.clinicService.findOwnerById(ownerId);
+ Pet pet = new Pet();
+ owner.addPet(pet);
+ model.put("pet", pet);
+ return "pets/createOrUpdatePetForm";
+ }
+
+ @RequestMapping(value = "/owners/{ownerId}/pets/new", method = RequestMethod.POST)
+ public String processCreationForm(@ModelAttribute("pet") Pet pet, BindingResult result, SessionStatus status) {
+ new PetValidator().validate(pet, result);
+ if (result.hasErrors()) {
+ return "pets/createOrUpdatePetForm";
+ } else {
+ this.clinicService.savePet(pet);
+ status.setComplete();
+ return "redirect:/owners/{ownerId}";
+ }
+ }
+
+ @RequestMapping(value = "/owners/*/pets/{petId}/edit", method = RequestMethod.GET)
+ public String initUpdateForm(@PathVariable("petId") int petId, Map model) {
+ Pet pet = this.clinicService.findPetById(petId);
+ model.put("pet", pet);
+ return "pets/createOrUpdatePetForm";
+ }
+
+ @RequestMapping(value = "/owners/{ownerId}/pets/{petId}/edit", method = {RequestMethod.PUT, RequestMethod.POST})
+ public String processUpdateForm(@ModelAttribute("pet") Pet pet, BindingResult result, SessionStatus status) {
+ // we're not using @Valid annotation here because it is easier to define such validation rule in Java
+ new PetValidator().validate(pet, result);
+ if (result.hasErrors()) {
+ return "pets/createOrUpdatePetForm";
+ } else {
+ this.clinicService.savePet(pet);
+ status.setComplete();
+ return "redirect:/owners/{ownerId}";
+ }
+ }
+
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/web/PetTypeFormatter.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/web/PetTypeFormatter.java
new file mode 100644
index 000000000..3d47d2201
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/web/PetTypeFormatter.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.web;
+
+
+import java.text.ParseException;
+import java.util.Collection;
+import java.util.Locale;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.format.Formatter;
+import org.springframework.samples.petclinic.model.PetType;
+import org.springframework.samples.petclinic.service.ClinicService;
+
+/**
+ * Instructs Spring MVC on how to parse and print elements of type 'PetType'. Starting from Spring 3.0, Formatters have
+ * come as an improvement in comparison to legacy PropertyEditors. See the following links for more details: - The
+ * Spring ref doc: http://static.springsource.org/spring/docs/current/spring-framework-reference/html/validation.html#format-Formatter-SPI
+ * - A nice blog entry from Gordon Dickens: http://gordondickens.com/wordpress/2010/09/30/using-spring-3-0-custom-type-converter/
+ *
+ * Also see how the bean 'conversionService' has been declared inside /WEB-INF/mvc-core-config.xml
+ *
+ * @author Mark Fisher
+ * @author Juergen Hoeller
+ * @author Michael Isvy
+ */
+public class PetTypeFormatter implements Formatter {
+
+ private final ClinicService clinicService;
+
+
+ @Autowired
+ public PetTypeFormatter(ClinicService clinicService) {
+ this.clinicService = clinicService;
+ }
+
+ @Override
+ public String print(PetType petType, Locale locale) {
+ return petType.getName();
+ }
+
+ @Override
+ public PetType parse(String text, Locale locale) throws ParseException {
+ Collection findPetTypes = this.clinicService.findPetTypes();
+ for (PetType type : findPetTypes) {
+ if (type.getName().equals(text)) {
+ return type;
+ }
+ }
+ throw new ParseException("type not found: " + text, 0);
+ }
+
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/web/PetValidator.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/web/PetValidator.java
new file mode 100644
index 000000000..ad1ebdf81
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/web/PetValidator.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.web;
+
+import org.springframework.samples.petclinic.model.Pet;
+import org.springframework.util.StringUtils;
+import org.springframework.validation.Errors;
+
+/**
+ * Validator for Pet forms.
+ *
+ * @author Ken Krebs
+ * @author Juergen Hoeller
+ */
+public class PetValidator {
+
+ public void validate(Pet pet, Errors errors) {
+ String name = pet.getName();
+ // name validaation
+ if (!StringUtils.hasLength(name)) {
+ errors.rejectValue("name", "required", "required");
+ } else if (pet.isNew() && pet.getOwner().getPet(name, true) != null) {
+ errors.rejectValue("name", "duplicate", "already exists");
+ }
+
+ // type valication
+ if (pet.isNew() && pet.getType() == null) {
+ errors.rejectValue("type", "required", "required");
+ }
+
+ // type valication
+ if (pet.getBirthDate()==null) {
+ errors.rejectValue("birthDate", "required", "required");
+ }
+ }
+
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/web/VetController.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/web/VetController.java
new file mode 100644
index 000000000..3a6c052ea
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/web/VetController.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.web;
+
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.samples.petclinic.model.Vets;
+import org.springframework.samples.petclinic.service.ClinicService;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+/**
+ * @author Juergen Hoeller
+ * @author Mark Fisher
+ * @author Ken Krebs
+ * @author Arjen Poutsma
+ */
+@Controller
+public class VetController {
+
+ private final ClinicService clinicService;
+
+
+ @Autowired
+ public VetController(ClinicService clinicService) {
+ this.clinicService = clinicService;
+ }
+
+ @RequestMapping("/vets")
+ public String showVetList(Map model) {
+ // Here we are returning an object of type 'Vets' rather than a collection of Vet objects
+ // so it is simpler for Object-Xml mapping
+ Vets vets = new Vets();
+ vets.getVetList().addAll(this.clinicService.findVets());
+ model.put("vets", vets);
+ return "vets/vetList";
+ }
+
+
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/web/VetsAtomView.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/web/VetsAtomView.java
new file mode 100644
index 000000000..bf26c3900
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/web/VetsAtomView.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.web;
+
+import com.rometools.rome.feed.atom.Entry;
+import com.rometools.rome.feed.atom.Feed;
+import com.rometools.rome.feed.atom.Content;
+import org.springframework.samples.petclinic.model.Vet;
+import org.springframework.samples.petclinic.model.Vets;
+import org.springframework.web.servlet.view.feed.AbstractAtomFeedView;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * A view creating a Atom representation from a list of Visit objects.
+ *
+ * @author Alef Arendsen
+ * @author Arjen Poutsma
+ */
+public class VetsAtomView extends AbstractAtomFeedView {
+
+ @Override
+ protected void buildFeedMetadata(Map model, Feed feed, HttpServletRequest request) {
+ feed.setId("tag:springsource.org");
+ feed.setTitle("Veterinarians");
+ //feed.setUpdated(date);
+ }
+
+ @Override
+ protected List buildFeedEntries(Map model,
+ HttpServletRequest request, HttpServletResponse response) throws Exception {
+
+ Vets vets = (Vets) model.get("vets");
+ List vetList = vets.getVetList();
+ List entries = new ArrayList(vetList.size());
+
+ for (Vet vet : vetList) {
+ Entry entry = new Entry();
+ // see http://diveintomark.org/archives/2004/05/28/howto-atom-id#other
+ entry.setId(String.format("tag:springsource.org,%s", vet.getId()));
+ entry.setTitle(String.format("Vet: %s %s", vet.getFirstName(), vet.getLastName()));
+ //entry.setUpdated(visit.getDate().toDate());
+
+ Content summary = new Content();
+ summary.setValue(vet.getSpecialties().toString());
+ entry.setSummary(summary);
+
+ entries.add(entry);
+ }
+ response.setContentType("blabla");
+ return entries;
+
+ }
+
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/web/VisitController.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/web/VisitController.java
new file mode 100644
index 000000000..f3f6f3ffb
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/web/VisitController.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.web;
+
+import java.util.Map;
+
+import javax.validation.Valid;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.samples.petclinic.model.Pet;
+import org.springframework.samples.petclinic.model.Visit;
+import org.springframework.samples.petclinic.service.ClinicService;
+import org.springframework.stereotype.Controller;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.bind.WebDataBinder;
+import org.springframework.web.bind.annotation.InitBinder;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+/**
+ * @author Juergen Hoeller
+ * @author Ken Krebs
+ * @author Arjen Poutsma
+ * @author Michael Isvy
+ */
+@Controller
+public class VisitController {
+
+ private final ClinicService clinicService;
+
+
+ @Autowired
+ public VisitController(ClinicService clinicService) {
+ this.clinicService = clinicService;
+ }
+
+ @InitBinder
+ public void setAllowedFields(WebDataBinder dataBinder) {
+ dataBinder.setDisallowedFields("id");
+ }
+
+ /**
+ * Called before each and every @RequestMapping annotated method.
+ * 2 goals:
+ * - Make sure we always have fresh data
+ * - Since we do not use the session scope, make sure that Pet object always has an id
+ * (Even though id is not part of the form fields)
+ * @param petId
+ * @return Pet
+ */
+ @ModelAttribute("visit")
+ public Visit loadPetWithVisit(@PathVariable("petId") int petId) {
+ Pet pet = this.clinicService.findPetById(petId);
+ Visit visit = new Visit();
+ pet.addVisit(visit);
+ return visit;
+ }
+
+ // Spring MVC calls method loadPetWithVisit(...) before initNewVisitForm is called
+ @RequestMapping(value = "/owners/*/pets/{petId}/visits/new", method = RequestMethod.GET)
+ public String initNewVisitForm(@PathVariable("petId") int petId, Map model) {
+ return "pets/createOrUpdateVisitForm";
+ }
+
+ // Spring MVC calls method loadPetWithVisit(...) before processNewVisitForm is called
+ @RequestMapping(value = "/owners/{ownerId}/pets/{petId}/visits/new", method = RequestMethod.POST)
+ public String processNewVisitForm(@Valid Visit visit, BindingResult result) {
+ if (result.hasErrors()) {
+ return "pets/createOrUpdateVisitForm";
+ } else {
+ this.clinicService.saveVisit(visit);
+ return "redirect:/owners/{ownerId}";
+ }
+ }
+
+ @RequestMapping(value = "/owners/*/pets/{petId}/visits", method = RequestMethod.GET)
+ public String showVisits(@PathVariable int petId, Map model) {
+ model.put("visits", this.clinicService.findPetById(petId).getVisits());
+ return "visitList";
+ }
+
+}
diff --git a/demo/repo/app/src/main/java/org/springframework/samples/petclinic/web/package-info.java b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/web/package-info.java
new file mode 100644
index 000000000..c909ccf7f
--- /dev/null
+++ b/demo/repo/app/src/main/java/org/springframework/samples/petclinic/web/package-info.java
@@ -0,0 +1,8 @@
+
+/**
+ *
+ * The classes in this package represent PetClinic's web presentation layer.
+ *
+ */
+package org.springframework.samples.petclinic.web;
+
diff --git a/demo/repo/app/src/main/java/overview.html b/demo/repo/app/src/main/java/overview.html
new file mode 100644
index 000000000..df4f4d6b7
--- /dev/null
+++ b/demo/repo/app/src/main/java/overview.html
@@ -0,0 +1,7 @@
+
+
+
+ The Spring Data Binding framework, an internal library used by Spring Web Flow.
+
+
+
\ No newline at end of file
diff --git a/demo/repo/app/src/main/java/test.html b/demo/repo/app/src/main/java/test.html
new file mode 100644
index 000000000..9a7a99675
--- /dev/null
+++ b/demo/repo/app/src/main/java/test.html
@@ -0,0 +1,39 @@
+
+
+
+
+
Organisation
+
+
Speakers
+
+
+
+
+
+
Sergiu Bodiu
+
Java Consultant at Bank of America
+ Seasoned consultant experienced in large-scale e-commerce projects, passionate about providing innovative technology solutions to solve complex business problems, have extensive knowledge and experience delivering enterprise wide applications. He is skilled in software design, data modeling, stakeholder management, IT strategic planning, technical know-how and security. Able to design, implement, test and maintain software product components with strong focus on design elegance and software reuse.
+
+
+
+
+
+
Sergiu Bodiu
+
Java Consultant at Bank of America
+ Seasoned consultant experienced in large-scale e-commerce projects, passionate about providing innovative technology solutions to solve complex business problems, have extensive knowledge and experience delivering enterprise wide applications. He is skilled in software design, data modeling, stakeholder management, IT strategic planning, technical know-how and security. Able to design, implement, test and maintain software product components with strong focus on design elegance and software reuse.
+
+
+
+
diff --git a/demo/repo/app/src/main/resources/cache/ehcache.xml b/demo/repo/app/src/main/resources/cache/ehcache.xml
new file mode 100644
index 000000000..32e509953
--- /dev/null
+++ b/demo/repo/app/src/main/resources/cache/ehcache.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
diff --git a/demo/repo/app/src/main/resources/cache/ehcache.xsd b/demo/repo/app/src/main/resources/cache/ehcache.xsd
new file mode 100644
index 000000000..bfc19ddb1
--- /dev/null
+++ b/demo/repo/app/src/main/resources/cache/ehcache.xsd
@@ -0,0 +1,419 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demo/repo/app/src/main/resources/dandelion/datatables/datatables.properties b/demo/repo/app/src/main/resources/dandelion/datatables/datatables.properties
new file mode 100644
index 000000000..08b1e439c
--- /dev/null
+++ b/demo/repo/app/src/main/resources/dandelion/datatables/datatables.properties
@@ -0,0 +1,6 @@
+# ==================================
+# Dandelion-Datatables configuration
+# ==================================
+
+# Disable the asset management of Dandelion-Core for all non-DataTable-related assets
+main.standalone=true
\ No newline at end of file
diff --git a/demo/repo/app/src/main/resources/db/hsqldb/initDB.sql b/demo/repo/app/src/main/resources/db/hsqldb/initDB.sql
new file mode 100644
index 000000000..a16c42dec
--- /dev/null
+++ b/demo/repo/app/src/main/resources/db/hsqldb/initDB.sql
@@ -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(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);
diff --git a/demo/repo/app/src/main/resources/db/hsqldb/populateDB.sql b/demo/repo/app/src/main/resources/db/hsqldb/populateDB.sql
new file mode 100644
index 000000000..16dda3e84
--- /dev/null
+++ b/demo/repo/app/src/main/resources/db/hsqldb/populateDB.sql
@@ -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');
diff --git a/demo/repo/app/src/main/resources/db/mysql/initDB.sql b/demo/repo/app/src/main/resources/db/mysql/initDB.sql
new file mode 100644
index 000000000..b90d1843a
--- /dev/null
+++ b/demo/repo/app/src/main/resources/db/mysql/initDB.sql
@@ -0,0 +1,60 @@
+CREATE DATABASE IF NOT EXISTS petclinic;
+GRANT ALL PRIVILEGES ON petclinic.* TO pc@localhost IDENTIFIED BY 'pc';
+
+USE petclinic;
+
+CREATE TABLE IF NOT EXISTS vets (
+ id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ first_name VARCHAR(30),
+ last_name VARCHAR(30),
+ INDEX(last_name)
+) engine=InnoDB;
+
+CREATE TABLE IF NOT EXISTS specialties (
+ id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(80),
+ INDEX(name)
+) engine=InnoDB;
+
+CREATE TABLE IF NOT EXISTS vet_specialties (
+ vet_id INT(4) UNSIGNED NOT NULL,
+ specialty_id INT(4) UNSIGNED NOT NULL,
+ FOREIGN KEY (vet_id) REFERENCES vets(id),
+ FOREIGN KEY (specialty_id) REFERENCES specialties(id),
+ UNIQUE (vet_id,specialty_id)
+) engine=InnoDB;
+
+CREATE TABLE IF NOT EXISTS types (
+ id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(80),
+ INDEX(name)
+) engine=InnoDB;
+
+CREATE TABLE IF NOT EXISTS owners (
+ id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ first_name VARCHAR(30),
+ last_name VARCHAR(30),
+ address VARCHAR(255),
+ city VARCHAR(80),
+ telephone VARCHAR(20),
+ INDEX(last_name)
+) engine=InnoDB;
+
+CREATE TABLE IF NOT EXISTS pets (
+ id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(30),
+ birth_date DATE,
+ type_id INT(4) UNSIGNED NOT NULL,
+ owner_id INT(4) UNSIGNED NOT NULL,
+ INDEX(name),
+ FOREIGN KEY (owner_id) REFERENCES owners(id),
+ FOREIGN KEY (type_id) REFERENCES types(id)
+) engine=InnoDB;
+
+CREATE TABLE IF NOT EXISTS visits (
+ id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ pet_id INT(4) UNSIGNED NOT NULL,
+ visit_date DATE,
+ description VARCHAR(255),
+ FOREIGN KEY (pet_id) REFERENCES pets(id)
+) engine=InnoDB;
\ No newline at end of file
diff --git a/demo/repo/app/src/main/resources/db/mysql/petclinic_db_setup_mysql.txt b/demo/repo/app/src/main/resources/db/mysql/petclinic_db_setup_mysql.txt
new file mode 100644
index 000000000..66a7c2f20
--- /dev/null
+++ b/demo/repo/app/src/main/resources/db/mysql/petclinic_db_setup_mysql.txt
@@ -0,0 +1,24 @@
+================================================================================
+=== Spring PetClinic sample application - MySQL Configuration ===
+================================================================================
+
+@author Sam Brannen
+@author Costin Leau
+
+--------------------------------------------------------------------------------
+
+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/
+
+2) Download Connector/J, the MySQL JDBC driver (e.g., Connector/J 5.1.x), which
+ can be found here: http://dev.mysql.com/downloads/connector/j/
+ Copy the Connector/J JAR file (e.g., mysql-connector-java-5.1.5-bin.jar) into
+ the db/mysql directory. Alternatively, uncomment the mysql-connector from the
+ Petclinic pom.
+
+3) Create the PetClinic database and user by executing the "db/mysql/createDB.txt"
+ script.
+
+4) Open "src/main/resources/spring/jdbc.properties"; comment out all properties in the
+ "HSQL Settings" section; uncomment all properties in the "MySQL Settings"
+ section.
\ No newline at end of file
diff --git a/demo/repo/app/src/main/resources/db/mysql/populateDB.sql b/demo/repo/app/src/main/resources/db/mysql/populateDB.sql
new file mode 100644
index 000000000..3f1dcf8ea
--- /dev/null
+++ b/demo/repo/app/src/main/resources/db/mysql/populateDB.sql
@@ -0,0 +1,53 @@
+INSERT IGNORE INTO vets VALUES (1, 'James', 'Carter');
+INSERT IGNORE INTO vets VALUES (2, 'Helen', 'Leary');
+INSERT IGNORE INTO vets VALUES (3, 'Linda', 'Douglas');
+INSERT IGNORE INTO vets VALUES (4, 'Rafael', 'Ortega');
+INSERT IGNORE INTO vets VALUES (5, 'Henry', 'Stevens');
+INSERT IGNORE INTO vets VALUES (6, 'Sharon', 'Jenkins');
+
+INSERT IGNORE INTO specialties VALUES (1, 'radiology');
+INSERT IGNORE INTO specialties VALUES (2, 'surgery');
+INSERT IGNORE INTO specialties VALUES (3, 'dentistry');
+
+INSERT IGNORE INTO vet_specialties VALUES (2, 1);
+INSERT IGNORE INTO vet_specialties VALUES (3, 2);
+INSERT IGNORE INTO vet_specialties VALUES (3, 3);
+INSERT IGNORE INTO vet_specialties VALUES (4, 2);
+INSERT IGNORE INTO vet_specialties VALUES (5, 1);
+
+INSERT IGNORE INTO types VALUES (1, 'cat');
+INSERT IGNORE INTO types VALUES (2, 'dog');
+INSERT IGNORE INTO types VALUES (3, 'lizard');
+INSERT IGNORE INTO types VALUES (4, 'snake');
+INSERT IGNORE INTO types VALUES (5, 'bird');
+INSERT IGNORE INTO types VALUES (6, 'hamster');
+
+INSERT IGNORE INTO owners VALUES (1, 'George', 'Franklin', '110 W. Liberty St.', 'Madison', '6085551023');
+INSERT IGNORE INTO owners VALUES (2, 'Betty', 'Davis', '638 Cardinal Ave.', 'Sun Prairie', '6085551749');
+INSERT IGNORE INTO owners VALUES (3, 'Eduardo', 'Rodriquez', '2693 Commerce St.', 'McFarland', '6085558763');
+INSERT IGNORE INTO owners VALUES (4, 'Harold', 'Davis', '563 Friendly St.', 'Windsor', '6085553198');
+INSERT IGNORE INTO owners VALUES (5, 'Peter', 'McTavish', '2387 S. Fair Way', 'Madison', '6085552765');
+INSERT IGNORE INTO owners VALUES (6, 'Jean', 'Coleman', '105 N. Lake St.', 'Monona', '6085552654');
+INSERT IGNORE INTO owners VALUES (7, 'Jeff', 'Black', '1450 Oak Blvd.', 'Monona', '6085555387');
+INSERT IGNORE INTO owners VALUES (8, 'Maria', 'Escobito', '345 Maple St.', 'Madison', '6085557683');
+INSERT IGNORE INTO owners VALUES (9, 'David', 'Schroeder', '2749 Blackhawk Trail', 'Madison', '6085559435');
+INSERT IGNORE INTO owners VALUES (10, 'Carlos', 'Estaban', '2335 Independence La.', 'Waunakee', '6085555487');
+
+INSERT IGNORE INTO pets VALUES (1, 'Leo', '2000-09-07', 1, 1);
+INSERT IGNORE INTO pets VALUES (2, 'Basil', '2002-08-06', 6, 2);
+INSERT IGNORE INTO pets VALUES (3, 'Rosy', '2001-04-17', 2, 3);
+INSERT IGNORE INTO pets VALUES (4, 'Jewel', '2000-03-07', 2, 3);
+INSERT IGNORE INTO pets VALUES (5, 'Iggy', '2000-11-30', 3, 4);
+INSERT IGNORE INTO pets VALUES (6, 'George', '2000-01-20', 4, 5);
+INSERT IGNORE INTO pets VALUES (7, 'Samantha', '1995-09-04', 1, 6);
+INSERT IGNORE INTO pets VALUES (8, 'Max', '1995-09-04', 1, 6);
+INSERT IGNORE INTO pets VALUES (9, 'Lucky', '1999-08-06', 5, 7);
+INSERT IGNORE INTO pets VALUES (10, 'Mulligan', '1997-02-24', 2, 8);
+INSERT IGNORE INTO pets VALUES (11, 'Freddy', '2000-03-09', 5, 9);
+INSERT IGNORE INTO pets VALUES (12, 'Lucky', '2000-06-24', 2, 10);
+INSERT IGNORE INTO pets VALUES (13, 'Sly', '2002-06-08', 1, 10);
+
+INSERT IGNORE INTO visits VALUES (1, 7, '2010-03-04', 'rabies shot');
+INSERT IGNORE INTO visits VALUES (2, 8, '2011-03-04', 'rabies shot');
+INSERT IGNORE INTO visits VALUES (3, 8, '2009-06-04', 'neutered');
+INSERT IGNORE INTO visits VALUES (4, 7, '2008-09-04', 'spayed');
diff --git a/demo/repo/app/src/main/resources/db_readme.txt b/demo/repo/app/src/main/resources/db_readme.txt
new file mode 100644
index 000000000..68e3f245c
--- /dev/null
+++ b/demo/repo/app/src/main/resources/db_readme.txt
@@ -0,0 +1,13 @@
+================================================================================
+=== Spring PetClinic sample application - Database Configuration ===
+================================================================================
+
+@author Costin Leau
+
+--------------------------------------------------------------------------------
+
+In its default configuration, Petclinic uses an in-memory database (HSQLDB) which
+gets populated at startup with data. A similar setup is provided for Mysql in case
+a persistent database configuration is needed.
+Note that whenever the database type is changed, the data-access.properties file needs to
+be updated.
diff --git a/demo/repo/app/src/main/resources/logback.xml b/demo/repo/app/src/main/resources/logback.xml
new file mode 100644
index 000000000..751726a14
--- /dev/null
+++ b/demo/repo/app/src/main/resources/logback.xml
@@ -0,0 +1,23 @@
+
+
+
+
+ true
+
+
+
+
+
+
+
+ %-5level %logger{0} - %msg%n
+
+
+
+
+
+
+
+
+
+
diff --git a/demo/repo/app/src/main/resources/messages/messages.properties b/demo/repo/app/src/main/resources/messages/messages.properties
new file mode 100644
index 000000000..173417a10
--- /dev/null
+++ b/demo/repo/app/src/main/resources/messages/messages.properties
@@ -0,0 +1,8 @@
+welcome=Welcome
+required=is required
+notFound=has not been found
+duplicate=is already in use
+nonNumeric=must be all numeric
+duplicateFormSubmission=Duplicate form submission is not allowed
+typeMismatch.date=invalid date
+typeMismatch.birthDate=invalid date
diff --git a/demo/repo/app/src/main/resources/messages/messages_de.properties b/demo/repo/app/src/main/resources/messages/messages_de.properties
new file mode 100644
index 000000000..124bee48b
--- /dev/null
+++ b/demo/repo/app/src/main/resources/messages/messages_de.properties
@@ -0,0 +1,8 @@
+welcome=Willkommen
+required=muss angegeben werden
+notFound=wurde nicht gefunden
+duplicate=ist bereits vergeben
+nonNumeric=darf nur numerisch sein
+duplicateFormSubmission=Wiederholtes Absenden des Formulars ist nicht erlaubt
+typeMismatch.date=ungltiges Datum
+typeMismatch.birthDate=ungltiges Datum
diff --git a/demo/repo/app/src/main/resources/messages/messages_en.properties b/demo/repo/app/src/main/resources/messages/messages_en.properties
new file mode 100644
index 000000000..05d519bb8
--- /dev/null
+++ b/demo/repo/app/src/main/resources/messages/messages_en.properties
@@ -0,0 +1 @@
+# This file is intentionally empty. Message look-ups will fall back to the default "messages.properties" file.
\ No newline at end of file
diff --git a/demo/repo/app/src/main/resources/spring/business-config.xml b/demo/repo/app/src/main/resources/spring/business-config.xml
new file mode 100644
index 000000000..99cf4c1d3
--- /dev/null
+++ b/demo/repo/app/src/main/resources/spring/business-config.xml
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demo/repo/app/src/main/resources/spring/data-access.properties b/demo/repo/app/src/main/resources/spring/data-access.properties
new file mode 100644
index 000000000..c1cc3cefd
--- /dev/null
+++ b/demo/repo/app/src/main/resources/spring/data-access.properties
@@ -0,0 +1,41 @@
+# Properties file with JDBC and JPA settings.
+#
+# Applied by from
+# various application context XML files (e.g., "applicationContext-*.xml").
+# Targeted at system administrators, to avoid touching the context XML files.
+
+#-------------------------------------------------------------------------------
+# HSQL Settings
+
+jdbc.driverClassName=org.hsqldb.jdbcDriver
+jdbc.url=jdbc:hsqldb:mem:petclinic
+jdbc.username=sa
+jdbc.password=
+
+# Properties that control the population of schema and data for a new data source
+jdbc.initLocation=classpath:db/hsqldb/initDB.sql
+jdbc.dataLocation=classpath:db/hsqldb/populateDB.sql
+
+# Property that determines which database to use with an AbstractJpaVendorAdapter
+jpa.database=HSQL
+
+jpa.showSql=true
+
+#-------------------------------------------------------------------------------
+# MySQL Settings
+
+#jdbc.driverClassName=com.mysql.jdbc.Driver
+#jdbc.url=jdbc:mysql://localhost:3306/petclinic
+#jdbc.username=root
+#jdbc.password=
+
+# Properties that control the population of schema and data for a new data source
+#jdbc.initLocation=classpath:db/mysql/initDB.sql
+#jdbc.dataLocation=classpath:db/mysql/populateDB.sql
+
+# Property that determines which Hibernate dialect to use
+# (only applied with "applicationContext-hibernate.xml")
+#hibernate.dialect=org.hibernate.dialect.MySQLDialect
+
+# Property that determines which database to use with an AbstractJpaVendorAdapter
+#jpa.database=MYSQL
diff --git a/demo/repo/app/src/main/resources/spring/datasource-config.xml b/demo/repo/app/src/main/resources/spring/datasource-config.xml
new file mode 100644
index 000000000..f74129963
--- /dev/null
+++ b/demo/repo/app/src/main/resources/spring/datasource-config.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demo/repo/app/src/main/resources/spring/mvc-core-config.xml b/demo/repo/app/src/main/resources/spring/mvc-core-config.xml
new file mode 100644
index 000000000..eab2062dc
--- /dev/null
+++ b/demo/repo/app/src/main/resources/spring/mvc-core-config.xml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/demo/repo/app/src/main/resources/spring/mvc-view-config.xml b/demo/repo/app/src/main/resources/spring/mvc-view-config.xml
new file mode 100644
index 000000000..205f7ffce
--- /dev/null
+++ b/demo/repo/app/src/main/resources/spring/mvc-view-config.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/demo/repo/app/src/main/resources/spring/tools-config.xml b/demo/repo/app/src/main/resources/spring/tools-config.xml
new file mode 100644
index 000000000..1be7e3b6c
--- /dev/null
+++ b/demo/repo/app/src/main/resources/spring/tools-config.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demo/repo/app/src/main/webapp/WEB-INF/jsp/exception.jsp b/demo/repo/app/src/main/webapp/WEB-INF/jsp/exception.jsp
new file mode 100644
index 000000000..876fb04a3
--- /dev/null
+++ b/demo/repo/app/src/main/webapp/WEB-INF/jsp/exception.jsp
@@ -0,0 +1,31 @@
+
+
+<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+
+
+
+
+
+
+
+
+
diff --git a/demo/repo/app/src/main/webapp/WEB-INF/no-spring-config-files-there.txt b/demo/repo/app/src/main/webapp/WEB-INF/no-spring-config-files-there.txt
new file mode 100644
index 000000000..45fb7bf0a
--- /dev/null
+++ b/demo/repo/app/src/main/webapp/WEB-INF/no-spring-config-files-there.txt
@@ -0,0 +1,4 @@
+All Spring config files (including Spring MVC ones) are inside src/main/resource.
+There are mostly 2 reasons to that:
+- All Spring config files are grouped into one single place
+- It is simpler to reference them from inside JUnit tests
\ No newline at end of file
diff --git a/demo/repo/app/src/main/webapp/WEB-INF/tags/inputField.tag b/demo/repo/app/src/main/webapp/WEB-INF/tags/inputField.tag
new file mode 100644
index 000000000..796dc91c0
--- /dev/null
+++ b/demo/repo/app/src/main/webapp/WEB-INF/tags/inputField.tag
@@ -0,0 +1,19 @@
+<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
+<%@ attribute name="name" required="true" rtexprvalue="true"
+ description="Name of corresponding property in bean object" %>
+<%@ attribute name="label" required="true" rtexprvalue="true"
+ description="Label appears in red color if input is considered as invalid after submission" %>
+
+
+
+
+
+
+
+
+ ${status.errorMessage}
+
+
+
\ No newline at end of file
diff --git a/demo/repo/app/src/main/webapp/WEB-INF/tags/selectField.tag b/demo/repo/app/src/main/webapp/WEB-INF/tags/selectField.tag
new file mode 100644
index 000000000..f93256ac8
--- /dev/null
+++ b/demo/repo/app/src/main/webapp/WEB-INF/tags/selectField.tag
@@ -0,0 +1,23 @@
+<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
+<%@ attribute name="name" required="true" rtexprvalue="true"
+ description="Name of corresponding property in bean object" %>
+<%@ attribute name="label" required="true" rtexprvalue="true"
+ description="Label appears in red color if input is considered as invalid after submission" %>
+<%@ attribute name="names" required="true" rtexprvalue="true" type="java.util.List"
+ description="Names in the list" %>
+<%@ attribute name="size" required="true" rtexprvalue="true"
+ description="Size of Select" %>
+
+
+
+
+
+
+
+
+ ${status.errorMessage}
+
+
+
\ No newline at end of file
diff --git a/demo/repo/app/src/main/webapp/WEB-INF/web.xml b/demo/repo/app/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 000000000..360e3698a
--- /dev/null
+++ b/demo/repo/app/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,129 @@
+
+
+
+ Spring PetClinic
+ Spring PetClinic sample application
+
+
+ spring.profiles.active
+ jpa
+
+
+
+
+
+
+
+
+
+
+ contextConfigLocation
+ classpath:spring/business-config.xml, classpath:spring/tools-config.xml
+
+
+
+ org.springframework.web.context.ContextLoaderListener
+
+
+
+
+ petclinic
+ org.springframework.web.servlet.DispatcherServlet
+
+ contextConfigLocation
+ classpath:spring/mvc-core-config.xml
+
+ 1
+
+
+
+ petclinic
+ /
+
+
+
+
+ dandelionServlet
+ com.github.dandelion.core.web.DandelionServlet
+ 2
+
+
+ dandelionServlet
+ /dandelion-assets/*
+
+
+
+
+ dandelionFilter
+ com.github.dandelion.core.web.DandelionFilter
+
+
+ dandelionFilter
+ /*
+
+
+
+
+ httpMethodFilter
+ org.springframework.web.filter.HiddenHttpMethodFilter
+
+
+
+ httpMethodFilter
+ petclinic
+
+
+
+
+ encodingFilter
+ org.springframework.web.filter.CharacterEncodingFilter
+
+ encoding
+ UTF-8
+
+
+ forceEncoding
+ true
+
+
+
+
+ encodingFilter
+ /*
+
+
+
+
+ datatables
+ com.github.dandelion.datatables.core.web.filter.DatatablesFilter
+
+
+ datatables
+ /*
+
+
+
+
+
\ No newline at end of file
diff --git a/demo/repo/app/src/main/webapp/resources/css/petclinic.css b/demo/repo/app/src/main/webapp/resources/css/petclinic.css
new file mode 100644
index 000000000..36ad67082
--- /dev/null
+++ b/demo/repo/app/src/main/webapp/resources/css/petclinic.css
@@ -0,0 +1,21 @@
+.container {
+ padding-top: 10px;
+ margin-left: 50px;
+ width: 700px;
+}
+
+.form-horizontal {
+ width: 100%;
+}
+
+input[type="text"] {
+ height: 25px;
+}
+
+.navbar .nav > li > a {
+ color: #000000;
+}
+
+.form-horizontal .control-label {
+ text-align: left;
+}
diff --git a/demo/repo/app/src/main/webapp/resources/images/banner-graphic.png b/demo/repo/app/src/main/webapp/resources/images/banner-graphic.png
new file mode 100644
index 000000000..e6d01d588
Binary files /dev/null and b/demo/repo/app/src/main/webapp/resources/images/banner-graphic.png differ
diff --git a/demo/repo/app/src/main/webapp/resources/images/bullet-arrow.png b/demo/repo/app/src/main/webapp/resources/images/bullet-arrow.png
new file mode 100644
index 000000000..5909c25b3
Binary files /dev/null and b/demo/repo/app/src/main/webapp/resources/images/bullet-arrow.png differ
diff --git a/demo/repo/app/src/main/webapp/resources/images/pets.png b/demo/repo/app/src/main/webapp/resources/images/pets.png
new file mode 100644
index 000000000..0fe63c282
Binary files /dev/null and b/demo/repo/app/src/main/webapp/resources/images/pets.png differ
diff --git a/demo/repo/app/src/main/webapp/resources/images/spring-pivotal-logo.png b/demo/repo/app/src/main/webapp/resources/images/spring-pivotal-logo.png
new file mode 100644
index 000000000..1840af274
Binary files /dev/null and b/demo/repo/app/src/main/webapp/resources/images/spring-pivotal-logo.png differ
diff --git a/demo/repo/app/src/main/webapp/resources/images/springsource-logo.png b/demo/repo/app/src/main/webapp/resources/images/springsource-logo.png
new file mode 100644
index 000000000..e170f8abf
Binary files /dev/null and b/demo/repo/app/src/main/webapp/resources/images/springsource-logo.png differ
diff --git a/demo/repo/app/src/main/webapp/resources/images/submit-bg.png b/demo/repo/app/src/main/webapp/resources/images/submit-bg.png
new file mode 100644
index 000000000..10a94371b
Binary files /dev/null and b/demo/repo/app/src/main/webapp/resources/images/submit-bg.png differ
diff --git a/demo/repo/app/src/test/java/org/springframework/samples/petclinic/model/ValidatorTests.java b/demo/repo/app/src/test/java/org/springframework/samples/petclinic/model/ValidatorTests.java
new file mode 100644
index 000000000..0c80d06fb
--- /dev/null
+++ b/demo/repo/app/src/test/java/org/springframework/samples/petclinic/model/ValidatorTests.java
@@ -0,0 +1,49 @@
+package org.springframework.samples.petclinic.model;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Locale;
+import java.util.Set;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.Validator;
+
+import org.assertj.core.api.Assertions;
+import org.junit.Assert;
+import org.junit.Test;
+import org.springframework.context.i18n.LocaleContextHolder;
+import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
+
+/**
+ *
+ * @author Michael Isvy
+ * Simple test to make sure that Bean Validation is working
+ * (useful when upgrading to a new version of Hibernate Validator/ Bean Validation)
+ *
+ */
+public class ValidatorTests {
+
+ private Validator createValidator() {
+ LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();
+ localValidatorFactoryBean.afterPropertiesSet();
+ return localValidatorFactoryBean;
+ }
+
+ @Test
+ public void shouldNotValidateWhenFirstNameEmpty() {
+
+ LocaleContextHolder.setLocale(Locale.ENGLISH);
+ Person person = new Person();
+ person.setFirstName("");
+ person.setLastName("smith");
+
+ Validator validator = createValidator();
+ Set> constraintViolations = validator.validate(person);
+
+ Assert.assertEquals(1, constraintViolations.size());
+ ConstraintViolation violation = constraintViolations.iterator().next();
+ assertThat(violation.getPropertyPath().toString()).isEqualTo("firstName");
+ assertThat(violation.getMessage()).isEqualTo("may not be empty");
+ }
+
+}
diff --git a/demo/repo/app/src/test/java/org/springframework/samples/petclinic/service/AbstractClinicServiceTests.java b/demo/repo/app/src/test/java/org/springframework/samples/petclinic/service/AbstractClinicServiceTests.java
new file mode 100644
index 000000000..5088c7d46
--- /dev/null
+++ b/demo/repo/app/src/test/java/org/springframework/samples/petclinic/service/AbstractClinicServiceTests.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Collection;
+
+import org.joda.time.DateTime;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.samples.petclinic.model.Owner;
+import org.springframework.samples.petclinic.model.Pet;
+import org.springframework.samples.petclinic.model.PetType;
+import org.springframework.samples.petclinic.model.Vet;
+import org.springframework.samples.petclinic.model.Visit;
+import org.springframework.samples.petclinic.util.EntityUtils;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ *
Base class for {@link ClinicService} integration tests.
Subclasses should specify Spring context
+ * configuration using {@link ContextConfiguration @ContextConfiguration} annotation
+ * AbstractclinicServiceTests and its subclasses benefit from the following services provided by the Spring
+ * TestContext Framework:
Spring IoC container caching which spares us unnecessary set up
+ * time between test execution.
Dependency Injection of test fixture instances, meaning that
+ * we don't need to perform application context lookups. See the use of {@link Autowired @Autowired} on the {@link
+ * AbstractclinicServiceTests#clinicService clinicService} instance variable, which uses autowiring by
+ * type.
Transaction management, 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.
An {@link org.springframework.context.ApplicationContext
+ * ApplicationContext} is also inherited and can be used for explicit bean lookup if necessary.
+ *
+ * @author Ken Krebs
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @author Sam Brannen
+ * @author Michael Isvy
+ */
+public abstract class AbstractClinicServiceTests {
+
+ @Autowired
+ protected ClinicService clinicService;
+
+ @Test
+ public void shouldFindOwnersByLastName() {
+ Collection owners = this.clinicService.findOwnerByLastName("Davis");
+ assertThat(owners.size()).isEqualTo(2);
+
+ owners = this.clinicService.findOwnerByLastName("Daviss");
+ assertThat(owners.isEmpty());
+ }
+
+ @Test
+ public void shouldFindSingleOwnerWithPet() {
+ Owner owner = this.clinicService.findOwnerById(1);
+ assertThat(owner.getLastName()).startsWith("Franklin");
+ assertThat(owner.getPets().size()).isEqualTo(1);
+ }
+
+ @Test
+ @Transactional
+ public void shouldInsertOwner() {
+ Collection owners = this.clinicService.findOwnerByLastName("Schultz");
+ int found = owners.size();
+
+ Owner owner = new Owner();
+ owner.setFirstName("Sam");
+ owner.setLastName("Schultz");
+ owner.setAddress("4, Evans Street");
+ owner.setCity("Wollongong");
+ owner.setTelephone("4444444444");
+ this.clinicService.saveOwner(owner);
+ assertThat(owner.getId().longValue()).isNotEqualTo(0);
+
+ owners = this.clinicService.findOwnerByLastName("Schultz");
+ assertThat(owners.size()).isEqualTo(found + 1);
+ }
+
+ @Test
+ @Transactional
+ public void shouldUpdateOwner() {
+ Owner owner = this.clinicService.findOwnerById(1);
+ String oldLastName = owner.getLastName();
+ String newLastName = oldLastName + "X";
+
+ owner.setLastName(newLastName);
+ this.clinicService.saveOwner(owner);
+
+ // retrieving new name from database
+ owner = this.clinicService.findOwnerById(1);
+ assertThat(owner.getLastName()).isEqualTo(newLastName);
+ }
+
+ @Test
+ public void shouldFindPetWithCorrectId() {
+ Pet pet7 = this.clinicService.findPetById(7);
+ assertThat(pet7.getName()).startsWith("Samantha");
+ assertThat(pet7.getOwner().getFirstName()).isEqualTo("Jean");
+
+ }
+
+ @Test
+ public void shouldFindAllPetTypes() {
+ Collection petTypes = this.clinicService.findPetTypes();
+
+ PetType petType1 = EntityUtils.getById(petTypes, PetType.class, 1);
+ assertThat(petType1.getName()).isEqualTo("cat");
+ PetType petType4 = EntityUtils.getById(petTypes, PetType.class, 4);
+ assertThat(petType4.getName()).isEqualTo("snake");
+ }
+
+ @Test
+ @Transactional
+ public void shouldInsertPetIntoDatabaseAndGenerateId() {
+ Owner owner6 = this.clinicService.findOwnerById(6);
+ int found = owner6.getPets().size();
+
+ Pet pet = new Pet();
+ pet.setName("bowser");
+ Collection types = this.clinicService.findPetTypes();
+ pet.setType(EntityUtils.getById(types, PetType.class, 2));
+ pet.setBirthDate(new DateTime());
+ owner6.addPet(pet);
+ assertThat(owner6.getPets().size()).isEqualTo(found + 1);
+
+ this.clinicService.savePet(pet);
+ this.clinicService.saveOwner(owner6);
+
+ owner6 = this.clinicService.findOwnerById(6);
+ assertThat(owner6.getPets().size()).isEqualTo(found + 1);
+ // checks that id has been generated
+ assertThat(pet.getId()).isNotNull();
+ }
+
+ @Test
+ @Transactional
+ public void sholdUpdatePetName() throws Exception {
+ Pet pet7 = this.clinicService.findPetById(7);
+ String oldName = pet7.getName();
+
+ String newName = oldName + "X";
+ pet7.setName(newName);
+ this.clinicService.savePet(pet7);
+
+ pet7 = this.clinicService.findPetById(7);
+ assertThat(pet7.getName()).isEqualTo(newName);
+ }
+
+ @Test
+ public void shouldFindVets() {
+ Collection vets = this.clinicService.findVets();
+
+ Vet vet = EntityUtils.getById(vets, Vet.class, 3);
+ assertThat(vet.getLastName()).isEqualTo("Douglas");
+ assertThat(vet.getNrOfSpecialties()).isEqualTo(2);
+ assertThat(vet.getSpecialties().get(0).getName()).isEqualTo("dentistry");
+ assertThat(vet.getSpecialties().get(1).getName()).isEqualTo("surgery");
+ }
+
+ @Test
+ @Transactional
+ public void shouldAddNewVisitForPet() {
+ Pet pet7 = this.clinicService.findPetById(7);
+ int found = pet7.getVisits().size();
+ Visit visit = new Visit();
+ pet7.addVisit(visit);
+ visit.setDescription("test");
+ this.clinicService.saveVisit(visit);
+ this.clinicService.savePet(pet7);
+
+ pet7 = this.clinicService.findPetById(7);
+ assertThat(pet7.getVisits().size()).isEqualTo(found + 1);
+ assertThat(visit.getId()).isNotNull();
+ }
+
+
+}
diff --git a/demo/repo/app/src/test/java/org/springframework/samples/petclinic/service/ClinicServiceJdbcTests.java b/demo/repo/app/src/test/java/org/springframework/samples/petclinic/service/ClinicServiceJdbcTests.java
new file mode 100644
index 000000000..49e57ea40
--- /dev/null
+++ b/demo/repo/app/src/test/java/org/springframework/samples/petclinic/service/ClinicServiceJdbcTests.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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;
+
+import org.junit.runner.RunWith;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ *
Integration test using the jdbc profile.
+ * @see AbstractClinicServiceTests AbstractClinicServiceTests for more details.
Integration test using the jpa profile.
+ * @see AbstractClinicServiceTests AbstractClinicServiceTests for more details.
+ *
+ * @author Rod Johnson
+ * @author Sam Brannen
+ * @author Michael Isvy
+ */
+
+@ContextConfiguration(locations = {"classpath:spring/business-config.xml"})
+@RunWith(SpringJUnit4ClassRunner.class)
+@ActiveProfiles("jpa")
+public class ClinicServiceJpaTests extends AbstractClinicServiceTests {
+
+}
\ No newline at end of file
diff --git a/demo/repo/app/src/test/java/org/springframework/samples/petclinic/service/ClinicServiceSpringDataJpaTests.java b/demo/repo/app/src/test/java/org/springframework/samples/petclinic/service/ClinicServiceSpringDataJpaTests.java
new file mode 100644
index 000000000..e01dda551
--- /dev/null
+++ b/demo/repo/app/src/test/java/org/springframework/samples/petclinic/service/ClinicServiceSpringDataJpaTests.java
@@ -0,0 +1,20 @@
+
+package org.springframework.samples.petclinic.service;
+
+import org.junit.runner.RunWith;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ *
Integration test using the 'Spring Data' profile.
+ * @see AbstractClinicServiceTests AbstractClinicServiceTests for more details.
+ * @author Michael Isvy
+ */
+
+@ContextConfiguration(locations = {"classpath:spring/business-config.xml"})
+@RunWith(SpringJUnit4ClassRunner.class)
+@ActiveProfiles("spring-data-jpa")
+public class ClinicServiceSpringDataJpaTests extends AbstractClinicServiceTests {
+
+}
\ No newline at end of file
diff --git a/demo/repo/app/src/test/java/org/springframework/samples/petclinic/web/VisitsViewTests-config.xml b/demo/repo/app/src/test/java/org/springframework/samples/petclinic/web/VisitsViewTests-config.xml
new file mode 100644
index 000000000..8458ba2ec
--- /dev/null
+++ b/demo/repo/app/src/test/java/org/springframework/samples/petclinic/web/VisitsViewTests-config.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
diff --git a/demo/repo/app/src/test/java/org/springframework/samples/petclinic/web/VisitsViewTests.java b/demo/repo/app/src/test/java/org/springframework/samples/petclinic/web/VisitsViewTests.java
new file mode 100644
index 000000000..b2e953c25
--- /dev/null
+++ b/demo/repo/app/src/test/java/org/springframework/samples/petclinic/web/VisitsViewTests.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ * http://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.web;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.xpath;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.ResultActions;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.context.WebApplicationContext;
+
+
+/**
+ * @author Arjen Poutsma
+ * @author Michael Isvy
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@WebAppConfiguration
+@ContextConfiguration("VisitsViewTests-config.xml")
+@ActiveProfiles("jdbc")
+public class VisitsViewTests {
+
+ @Autowired
+ private WebApplicationContext webApplicationContext;
+
+ private MockMvc mockMvc;
+
+ @Before
+ public void setup() {
+ this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build();
+ }
+
+ @Test
+ public void shouldFindVisitsInXmlFormat() throws Exception {
+ ResultActions actions = this.mockMvc.perform(get("/vets.xml").accept(MediaType.TEXT_XML));
+ actions.andDo(print()); // action is logged into the console
+
+ // TODO: there seems to be a conflict between this code and the new namespace
+ // actions.andExpect(status().isOk());
+ //actions.andExpect(content().contentType("application/xml"));
+ // actions.andExpect(xpath("/vets/vetList[id=1]/firstName").string(containsString("James")));
+
+ }
+}
diff --git a/demo/repo/flow.groovy b/demo/repo/flow.groovy
new file mode 100644
index 000000000..5c1f008a8
--- /dev/null
+++ b/demo/repo/flow.groovy
@@ -0,0 +1,63 @@
+node {
+ git '/tmp/repo'
+
+ def maven = docker.image('maven:3.3.9-jdk-8'); // https://registry.hub.docker.com/_/maven/
+
+ stage('Mirror') {
+ // First make sure the slave has this image.
+ // (If you could set your registry below to mirror Docker Hub,
+ // this would be unnecessary as maven.inside would pull the image.)
+ maven.pull()
+ }
+
+ // We are pushing to a private secure Docker registry in this demo.
+ // 'docker-registry-login' is the username/password credentials ID as defined in Jenkins Credentials.
+ // This is used to authenticate the Docker client to the registry.
+ docker.withRegistry('https://localhost/', 'docker-registry-login') {
+
+ stage('Build') {
+ // Spin up a Maven container to build the petclinic app from source.
+ // First set up a shared Maven repo so we don't need to download all dependencies on every build.
+ maven.inside {
+ sh "mvn -o -Dmaven.repo.local=${pwd tmp: true}/m2repo -f app -B -DskipTests clean package"
+ // The app .war and Dockerfile are now available in the workspace. See below.
+ }
+ }
+
+ def pcImg
+ stage('Bake Docker image') {
+ // Use the spring-petclinic Dockerfile (see above 'maven.inside()' block)
+ // to build a container that can run the app.
+ // The Dockerfile is in the app subdir of the active workspace
+ // (see above maven.inside() block), so we specify that.
+ // The Dockerfile expects the petclinic.war file to be in the 'target' dir
+ // relative to its own directory, which will be the case.
+ pcImg = docker.build("examplecorp/spring-petclinic:${env.BUILD_TAG}", 'app')
+
+ // Let us tag and push the newly built image. Will tag using the image name provided
+ // in the 'docker.build' call above (which included the build number on the tag).
+ pcImg.push();
+ }
+
+ stage('Test Image') {
+ // Spin up a Maven + Xvnc test container, linking it to the petclinic app container
+ // allowing the Maven tests to send HTTP requests between the containers.
+ def testImg = docker.build('examplecorp/spring-petclinic-tests:snapshot', 'test')
+ // Run the petclinic app in its own Docker container.
+ pcImg.withRun {petclinic ->
+ testImg.inside("--link=${petclinic.id}:petclinic") {
+ // https://github.com/jenkinsci/workflow-plugin/blob/master/basic-steps/CORE-STEPS.md#build-wrappers
+ wrap([$class: 'Xvnc', takeScreenshot: true, useXauthority: true]) {
+ sh "mvn -o -Dmaven.repo.local=${pwd tmp: true}/m2repo -f test -B clean test"
+ }
+ }
+ }
+ input "How do you like ${env.BUILD_URL}artifact/screenshot.jpg?"
+ }
+
+ stage('Promote Image') {
+ // All the tests passed. We can now retag and push the 'latest' image.
+ pcImg.push('latest')
+ }
+ }
+}
diff --git a/demo/repo/test/Dockerfile b/demo/repo/test/Dockerfile
new file mode 100644
index 000000000..1d10302f7
--- /dev/null
+++ b/demo/repo/test/Dockerfile
@@ -0,0 +1,8 @@
+FROM maven:3.3.9-jdk-8
+RUN apt-get update
+# adapted from https://github.com/jenkinsci/acceptance-test-harness/blob/a4adf775ebebb8cd21caca493f558b7ba9b79757/src/main/resources/org/jenkinsci/test/acceptance/docker/fixtures/XvncSlaveContainer/Dockerfile#L2-13
+RUN apt-get install -y vnc4server imagemagick iceweasel
+RUN mkdir /tmp/.X11-unix && chmod 1777 /tmp/.X11-unix/
+ENV XAUTHORITY /root/.Xauthority
+RUN mkdir /root/.vnc && (echo changeme; echo changeme) | vncpasswd /root/.vnc/passwd
+RUN touch /root/.vnc/xstartup && chmod a+x /root/.vnc/xstartup
diff --git a/demo/repo/test/README.md b/demo/repo/test/README.md
new file mode 100644
index 000000000..773bd5e06
--- /dev/null
+++ b/demo/repo/test/README.md
@@ -0,0 +1 @@
+Tests for demo Docker image produced by https://github.com/tfennelly/spring-petclinic.git.
\ No newline at end of file
diff --git a/demo/repo/test/pom.xml b/demo/repo/test/pom.xml
new file mode 100644
index 000000000..ed2745f42
--- /dev/null
+++ b/demo/repo/test/pom.xml
@@ -0,0 +1,35 @@
+
+ 4.0.0
+
+ org.jenkins-ci.demo
+ spring-petclinic-tests
+ 1.0
+ jar
+
+ spring-petclinic-tests
+ http://maven.apache.org
+
+
+ 3.0
+
+
+
+ UTF-8
+
+
+
+
+ junit
+ junit
+ 4.12
+ test
+
+
+ org.seleniumhq.selenium
+ selenium-firefox-driver
+ 2.53.0
+ test
+
+
+
diff --git a/demo/repo/test/src/test/java/org/jenkinsci/demo/PetClinicAppTest.java b/demo/repo/test/src/test/java/org/jenkinsci/demo/PetClinicAppTest.java
new file mode 100644
index 000000000..991134359
--- /dev/null
+++ b/demo/repo/test/src/test/java/org/jenkinsci/demo/PetClinicAppTest.java
@@ -0,0 +1,22 @@
+package org.jenkinsci.demo;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.IOException;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.firefox.FirefoxDriver;
+
+public class PetClinicAppTest {
+
+ @Test
+ public void testApp() throws IOException {
+ WebDriver webDriver = new FirefoxDriver();
+ webDriver.get(getAppUrl());
+ Assert.assertEquals("PetClinic :: a Spring Framework demonstration", webDriver.getTitle());
+ }
+
+ private String getAppUrl() {
+ return "http://petclinic:8080/petclinic";
+ }
+}
diff --git a/demo/run-demo.sh b/demo/run-demo.sh
new file mode 100755
index 000000000..69f532500
--- /dev/null
+++ b/demo/run-demo.sh
@@ -0,0 +1,50 @@
+#!/bin/bash
+
+##
+# The MIT License
+#
+# Copyright (c) 2015, CloudBees, Inc.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+##
+
+#
+# Install a private registry that can be used by the demo to push images to.
+#
+
+echo '*************** Installing a local Docker Registry Service for the demo ***************'
+echo '*************** Please sit tight for a minute ***************'
+
+cont1=$(docker run -d -p 443:5000 --name registry --restart=always registry:docker-workflow-demo)
+# TODO would be natural to switch to Compose
+trap "docker rm -f $cont1" EXIT
+
+# Note that this https://github.com/docker/docker/issues/23177 workaround is useless since the Docker CLI does not do the hostname resolution, the server does:
+# echo $(docker inspect -f '{{.NetworkSettings.Gateway}}' $HOSTNAME) docker.example.com >> /etc/hosts
+
+echo '*************** Docker Registry Service running now ***************'
+
+# In case some tagged images were left over from a previous run using a cache:
+(docker images -q examplecorp/spring-petclinic; docker images -q localhost/examplecorp/spring-petclinic) | xargs docker rmi --no-prune=true --force
+
+#
+# Now run Jenkins.
+#
+#
+/usr/local/bin/run.sh