diff --git a/db/build.xml b/db/build.xml new file mode 100644 index 0000000000000000000000000000000000000000..b6d09936b012c84d09f9dd451186f61ab1f61d53 --- /dev/null +++ b/db/build.xml @@ -0,0 +1,85 @@ +<?xml version="1.0"?> + +<project name="setupDB" basedir="." default="all"> + + <target name="dropHSQLTables" if="useHSQL"> + <echo message="Drop tables using: ${db.driver} ${db.url}" /> + <sql driver="${db.driver}" url="${db.url}" userid="${db.user}" password="${db.pw}" onerror="continue"> + <classpath> + <fileset dir="${spring.root}/lib"> + <include name="hsqldb/hsqldb.jar" /> + </fileset> + </classpath> + <transaction src="${db.dir}/dropTables.txt" /> + </sql> + </target> + + <target name="createHSQLTables" if="useHSQL"> + <echo message="Create tables using: ${db.driver} ${db.url}" /> + <sql driver="${db.driver}" url="${db.url}" userid="${db.user}" password="${db.pw}" onerror="continue"> + <classpath> + <fileset dir="${spring.root}/lib"> + <include name="hsqldb/hsqldb.jar" /> + </fileset> + </classpath> + <transaction src="${db.dir}/hsqldb/initDB.txt" /> + </sql> + </target> + + <target name="dropMYSQLTables" if="useMYSQL"> + <echo message="Dropping tables using: ${db.driver} ${db.url}" /> + <sql driver="${db.driver}" url="${db.url}" userid="${db.user}" password="${db.pw}" onerror="continue"> + <classpath> + <fileset dir="${db.dir}/mysql"> + <include name="mysql*.jar" /> + </fileset> + </classpath> + <transaction src="${db.dir}/dropTables.txt" /> + </sql> + </target> + + <target name="createMYSQLTables" if="useMYSQL"> + <echo message="Creating tables using: ${db.driver} ${db.url}" /> + <sql driver="${db.driver}" url="${db.url}" userid="${db.user}" password="${db.pw}" onerror="continue"> + <classpath> + <fileset dir="${db.dir}/mysql"> + <include name="mysql*.jar" /> + </fileset> + </classpath> + <transaction src="${db.dir}/mysql/initDB.txt" /> + </sql> + </target> + + <target name="emptyTables"> + <echo message="Emptying tables using: ${db.driver} ${db.url}" /> + <sql driver="${db.driver}" url="${db.url}" userid="${db.user}" password="${db.pw}"> + <classpath> + <fileset dir="${spring.root}/lib"> + <include name="hsqldb/hsqldb.jar" /> + </fileset> + <fileset dir="${db.dir}/mysql"> + <include name="mysql*.jar" /> + </fileset> + </classpath> + <transaction src="${db.dir}/emptyDB.txt" /> + </sql> + </target> + + <target name="populateTables"> + <echo message="Populating tables using: ${db.driver} ${db.url}" /> + <sql driver="${db.driver}" url="${db.url}" userid="${db.user}" password="${db.pw}"> + <classpath> + <fileset dir="${spring.root}/lib"> + <include name="hsqldb/hsqldb.jar" /> + </fileset> + <fileset dir="${db.dir}/mysql"> + <include name="mysql*.jar" /> + </fileset> + </classpath> + <transaction src="${db.dir}/populateDB.txt" /> + </sql> + </target> + + <target name="all" depends="dropHSQLTables,createHSQLTables,dropMYSQLTables,createMYSQLTables,emptyTables,populateTables" /> + +</project> \ No newline at end of file diff --git a/db/dropTables.txt b/db/dropTables.txt new file mode 100644 index 0000000000000000000000000000000000000000..90ae6329f90859d5945753ba28441d489b0384f7 --- /dev/null +++ b/db/dropTables.txt @@ -0,0 +1,7 @@ +DROP TABLE visits; +DROP TABLE pets; +DROP TABLE owners; +DROP TABLE types; +DROP TABLE vet_specialties; +DROP TABLE specialties; +DROP TABLE vets; diff --git a/db/emptyDB.txt b/db/emptyDB.txt new file mode 100644 index 0000000000000000000000000000000000000000..d5dd8728c0eea31ff1187e82abec8a3c3d077ea1 --- /dev/null +++ b/db/emptyDB.txt @@ -0,0 +1,7 @@ +DELETE FROM vets; +DELETE FROM specialties; +DELETE FROM vet_specialties; +DELETE FROM types; +DELETE FROM owners; +DELETE FROM pets; +DELETE FROM visits; diff --git a/db/mysql/createDB.txt b/db/mysql/createDB.txt new file mode 100644 index 0000000000000000000000000000000000000000..5b4b3859e9aedc6bc4fac16fc3e82297eccb7da0 --- /dev/null +++ b/db/mysql/createDB.txt @@ -0,0 +1,3 @@ +CREATE DATABASE petclinic; + +GRANT ALL PRIVILEGES ON petclinic.* TO pc@localhost IDENTIFIED BY 'pc'; \ No newline at end of file diff --git a/db/mysql/dropDB.txt b/db/mysql/dropDB.txt new file mode 100644 index 0000000000000000000000000000000000000000..e1209da0e5eb48f7aaef810f0a2bb7b8a9fc12fc --- /dev/null +++ b/db/mysql/dropDB.txt @@ -0,0 +1 @@ +DROP DATABASE petclinic; diff --git a/db/mysql/initDB.txt b/db/mysql/initDB.txt new file mode 100644 index 0000000000000000000000000000000000000000..0007ee3a3396b50887b9bd4a202c45857cb85e29 --- /dev/null +++ b/db/mysql/initDB.txt @@ -0,0 +1,58 @@ +USE petclinic; + +CREATE TABLE 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 specialties ( + id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(80), + INDEX(name) +) engine=InnoDB; + +CREATE TABLE vet_specialties ( + vet_id INT(4) UNSIGNED NOT NULL, + specialty_id INT(4) UNSIGNED NOT NULL +) engine=InnoDB; +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 INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(80), + INDEX(name) +) engine=InnoDB; + +CREATE TABLE 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 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) +) engine=InnoDB; +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 INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + pet_id INT(4) UNSIGNED NOT NULL, + visit_date DATE, + description VARCHAR(255), + INDEX(pet_id) +) engine=InnoDB; +ALTER TABLE visits ADD CONSTRAINT fk_visits_pets FOREIGN KEY (pet_id) REFERENCES pets(id); diff --git a/db/mysql/petclinic_db_setup_mysql.txt b/db/mysql/petclinic_db_setup_mysql.txt new file mode 100644 index 0000000000000000000000000000000000000000..66727e190aa96fb238d9ccd099ea2327789a69f9 --- /dev/null +++ b/db/mysql/petclinic_db_setup_mysql.txt @@ -0,0 +1,22 @@ +================================================================================ +=== Spring PetClinic sample application - MySQL Configuration === +================================================================================ + +@author Sam Brannen + +-------------------------------------------------------------------------------- + +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. + +3) Create the PetClinic database and user by executing the "db/mysql/createDB.txt" + script. + +4) Open "src/main/resources/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/db/mysql/petclinic_tomcat_mysql.xml b/db/mysql/petclinic_tomcat_mysql.xml new file mode 100644 index 0000000000000000000000000000000000000000..d1c5a3b04e3ba2d597e8369d3f57ed0058095eb5 --- /dev/null +++ b/db/mysql/petclinic_tomcat_mysql.xml @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8" ?> + +<Context path="/petclinic" docBase="petclinic" debug="4" reloadable="true"> + <Logger className="org.apache.catalina.logger.FileLogger" prefix="localhost_petclinic_log." suffix=".txt" timestamp="true"/> + + <!-- Define a database connection pool for MYSQL --> + <Resource name="jdbc/petclinicMYSQL" auth="Container" type="javax.sql.DataSource"/> + <ResourceParams name="jdbc/petclinicMYSQL"> + <parameter> + <name>factory</name> + <value>org.apache.commons.dbcp.BasicDataSourceFactory</value> + </parameter> + + <parameter> + <name>driverClassName</name> + <value>org.gjt.mm.mysql.Driver</value> + </parameter> + <!-- + The JDBC connection url for connecting to your MySQL dB. + The autoReconnect=true argument to the url makes sure that the + mm.mysql JDBC Driver will automatically reconnect if mysqld closed the + connection. mysqld by default closes idle connections after 8 hours. + --> + <parameter> + <name>url</name> + <value>jdbc:mysql://localhost:3306/petclinic?autoReconnect=true</value> + </parameter> + <parameter> + <name>username</name> + <value>pc</value> + </parameter> + <parameter> + <name>password</name> + <value>pc</value> + </parameter> + + <parameter> + <name>maxActive</name> + <value>50</value> + </parameter> + <parameter> + <name>maxIdle</name> + <value>10</value> + </parameter> + <parameter> + <name>maxWait</name> + <value>10000</value> + </parameter> + <parameter> + <name>removeAbandoned</name> + <value>true</value> + </parameter> + <parameter> + <name>removeAbandonedTimeout</name> + <value>60</value> + </parameter> + <parameter> + <name>logAbandoned</name> + <value>true</value> + </parameter> + </ResourceParams> + + +</Context> diff --git a/db/petclinic_tomcat_all.xml b/db/petclinic_tomcat_all.xml new file mode 100644 index 0000000000000000000000000000000000000000..ed45c5cd339ba2ebb863aefda5a047ca54788698 --- /dev/null +++ b/db/petclinic_tomcat_all.xml @@ -0,0 +1,112 @@ +<?xml version="1.0" encoding="UTF-8" ?> + +<Context path="/petclinic" docBase="petclinic" debug="4" reloadable="true"> + <Logger className="org.apache.catalina.logger.FileLogger" prefix="localhost_petclinic_log." suffix=".txt" timestamp="true"/> + + <!-- Define a database connection pool for HSQL --> + <!-- NOTE: make sure that a copy of hsqldb.jar is in the TOMCAT common/lib directory --> + <Resource name="jdbc/petclinicHSQL" auth="Container" type="javax.sql.DataSource"/> + <ResourceParams name="jdbc/petclinicHSQL"> + <parameter> + <name>factory</name> + <value>org.apache.commons.dbcp.BasicDataSourceFactory</value> + </parameter> + + <parameter> + <name>driverClassName</name> + <value>org.hsqldb.jdbcDriver</value> + </parameter> + <parameter> + <name>url</name> + <value>jdbc:hsqldb:hsql://localhost:9001</value> + </parameter> + <parameter> + <name>username</name> + <value>sa</value> + </parameter> + + <parameter> + <name>maxActive</name> + <value>50</value> + </parameter> + <parameter> + <name>maxIdle</name> + <value>10</value> + </parameter> + <parameter> + <name>maxWait</name> + <value>10000</value> + </parameter> + <parameter> + <name>removeAbandoned</name> + <value>true</value> + </parameter> + <parameter> + <name>removeAbandonedTimeout</name> + <value>60</value> + </parameter> + <parameter> + <name>logAbandoned</name> + <value>true</value> + </parameter> + </ResourceParams> + + <!-- Define a database connection pool for MYSQL --> + <Resource name="jdbc/petclinicMYSQL" auth="Container" type="javax.sql.DataSource"/> + <ResourceParams name="jdbc/petclinicMYSQL"> + <parameter> + <name>factory</name> + <value>org.apache.commons.dbcp.BasicDataSourceFactory</value> + </parameter> + + <parameter> + <name>driverClassName</name> + <value>org.gjt.mm.mysql.Driver</value> + </parameter> + <!-- + The JDBC connection url for connecting to your MySQL dB. + The autoReconnect=true argument to the url makes sure that the + mm.mysql JDBC Driver will automatically reconnect if mysqld closed the + connection. mysqld by default closes idle connections after 8 hours. + --> + <parameter> + <name>url</name> + <value>jdbc:mysql://localhost:3306/petclinic?autoReconnect=true</value> + </parameter> + <parameter> + <name>username</name> + <value>pc</value> + </parameter> + <parameter> + <name>password</name> + <value>pc</value> + </parameter> + + <parameter> + <name>maxActive</name> + <value>50</value> + </parameter> + <parameter> + <name>maxIdle</name> + <value>10</value> + </parameter> + <parameter> + <name>maxWait</name> + <value>10000</value> + </parameter> + <parameter> + <name>removeAbandoned</name> + <value>true</value> + </parameter> + <parameter> + <name>removeAbandonedTimeout</name> + <value>60</value> + </parameter> + <parameter> + <name>logAbandoned</name> + <value>true</value> + </parameter> + </ResourceParams> + + +</Context> diff --git a/db/populateDB.txt b/db/populateDB.txt new file mode 100644 index 0000000000000000000000000000000000000000..1bf0c4a6edb05ac0f8bb2382d19367d8b15e85cf --- /dev/null +++ b/db/populateDB.txt @@ -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', '2000-09-07', 1, 1); +INSERT INTO pets VALUES (2, 'Basil', '2002-08-06', 6, 2); +INSERT INTO pets VALUES (3, 'Rosy', '2001-04-17', 2, 3); +INSERT INTO pets VALUES (4, 'Jewel', '2000-03-07', 2, 3); +INSERT INTO pets VALUES (5, 'Iggy', '2000-11-30', 3, 4); +INSERT INTO pets VALUES (6, 'George', '2000-01-20', 4, 5); +INSERT INTO pets VALUES (7, 'Samantha', '1995-09-04', 1, 6); +INSERT INTO pets VALUES (8, 'Max', '1995-09-04', 1, 6); +INSERT INTO pets VALUES (9, 'Lucky', '1999-08-06', 5, 7); +INSERT INTO pets VALUES (10, 'Mulligan', '1997-02-24', 2, 8); +INSERT INTO pets VALUES (11, 'Freddy', '2000-03-09', 5, 9); +INSERT INTO pets VALUES (12, 'Lucky', '2000-06-24', 2, 10); +INSERT INTO pets VALUES (13, 'Sly', '2002-06-08', 1, 10); + +INSERT INTO visits VALUES (1, 7, '1996-03-04', 'rabies shot'); +INSERT INTO visits VALUES (2, 8, '1996-03-04', 'rabies shot'); +INSERT INTO visits VALUES (3, 8, '1996-06-04', 'neutered'); +INSERT INTO visits VALUES (4, 7, '1996-09-04', 'spayed'); diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..d9b79897e2e09978ce9abeb5d9319dd632547abe --- /dev/null +++ b/pom.xml @@ -0,0 +1,233 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>org.springframework.samples</groupId> + <artifactId>org.springframework.samples.petclinic</artifactId> + <name>petclinic-classic</name> + <packaging>war</packaging> + <version>1.0.0-SNAPSHOT</version> + <properties> + <spring.version>3.0.0.RC2</spring.version> + <slf4j.version>1.5.6</slf4j.version> + </properties> + <dependencies> + <dependency> + <groupId>com.oracle.toplink.essentials</groupId> + <artifactId>com.springsource.oracle.toplink.essentials</artifactId> + <version>2.0.0.b41-beta2</version> + </dependency> + <dependency> + <groupId>com.sun.syndication</groupId> + <artifactId>com.springsource.com.sun.syndication</artifactId> + <version>1.0.0</version> + </dependency> + <dependency> + <groupId>javax.persistence</groupId> + <artifactId>com.springsource.javax.persistence</artifactId> + <version>1.0.0</version> + </dependency> + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>com.springsource.javax.servlet</artifactId> + <version>2.5.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>com.springsource.javax.servlet.jsp</artifactId> + <version>2.1.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>com.springsource.javax.servlet.jsp.jstl</artifactId> + <version>1.2.0</version> + </dependency> + <dependency> + <groupId>javax.xml.bind</groupId> + <artifactId>com.springsource.javax.xml.bind</artifactId> + <version>2.1.7</version> + </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>com.springsource.org.apache.commons.dbcp</artifactId> + <version>1.2.2.osgi</version> + </dependency> + <dependency> + <groupId>org.apache.log4j</groupId> + <artifactId>com.springsource.org.apache.log4j</artifactId> + <version>1.2.15</version> + </dependency> + <dependency> + <groupId>org.apache.openjpa</groupId> + <artifactId>com.springsource.org.apache.openjpa</artifactId> + <version>1.1.0</version> + </dependency> + <dependency> + <groupId>org.apache.taglibs</groupId> + <artifactId>com.springsource.org.apache.taglibs.standard</artifactId> + <version>1.1.2</version> + </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>com.springsource.org.aspectj.weaver</artifactId> + <version>1.6.3.RELEASE</version> + </dependency> + <dependency> + <groupId>org.hibernate</groupId> + <artifactId>com.springsource.org.hibernate</artifactId> + <version>3.3.1.GA</version> + </dependency> + <dependency> + <groupId>org.hibernate</groupId> + <artifactId>com.springsource.org.hibernate.ejb</artifactId> + <version>3.4.0.GA</version> + </dependency> + <dependency> + <groupId>org.hsqldb</groupId> + <artifactId>com.springsource.org.hsqldb</artifactId> + <version>1.8.0.9</version> + </dependency> + <dependency> + <groupId>org.jdom</groupId> + <artifactId>com.springsource.org.jdom</artifactId> + <version>1.0.0</version> + </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>org.springframework.asm</artifactId> + <version>${spring.version}</version> + </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>org.springframework.orm</artifactId> + <version>${spring.version}</version> + </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>org.springframework.oxm</artifactId> + <version>${spring.version}</version> + </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>org.springframework.web.servlet</artifactId> + <version>${spring.version}</version> + </dependency> + <!-- Test dependencies --> + <dependency> + <groupId>org.junit</groupId> + <artifactId>com.springsource.org.junit</artifactId> + <version>4.5.0</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>org.springframework.test</artifactId> + <version>${spring.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>javax.transaction</groupId> + <artifactId>com.springsource.javax.transaction</artifactId> + <version>1.1.0</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.hibernate</groupId> + <artifactId>com.springsource.org.hibernate.annotations</artifactId> + <version>3.4.0.GA</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>com.springsource.slf4j.org.apache.commons.logging</artifactId> + <version>${slf4j.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>com.springsource.slf4j.api</artifactId> + <version>${slf4j.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>com.springsource.slf4j.log4j</artifactId> + <version>${slf4j.version}</version> + <scope>provided</scope> + <exclusions> + <exclusion> + <groupId>log4j</groupId> + <artifactId>log4j</artifactId> + </exclusion> + <exclusion> + <groupId>org.apache.log4j</groupId> + <artifactId>com.springsource.org.apache.log4j</artifactId> + </exclusion> + </exclusions> + </dependency> + </dependencies> + <repositories> + <repository> + <id>com.springsource.repository.bundles.release</id> + <name>SpringSource Enterprise Bundle Repository - SpringSource Releases</name> + <url>http://repository.springsource.com/maven/bundles/release</url> + </repository> + <repository> + <id>com.springsource.repository.bundles.milestone</id> + <name>SpringSource Enterprise Bundle Repository - SpringSource Milestones</name> + <url>http://repository.springsource.com/maven/bundles/milestone</url> + </repository> + <repository> + <id>com.springsource.repository.bundles.external</id> + <name>SpringSource Enterprise Bundle Repository - External Releases</name> + <url>http://repository.springsource.com/maven/bundles/external</url> + </repository> + <repository> + <id>com.springsource.repository.bundles.snapshot</id> + <name>SpringSource Enterprise Bundle Repository - Snapshot Releases</name> + <url>http://repository.springsource.com/maven/bundles/snapshot</url> + </repository> + <repository> + <id>spring-release</id> + <name>Spring Portfolio Release Repository</name> + <url>http://maven.springframework.org/release</url> + </repository> + <repository> + <id>spring-external</id> + <name>Spring Portfolio External Repository</name> + <url>http://maven.springframework.org/external</url> + </repository> + <repository> + <id>spring-milestone</id> + <name>Spring Portfolio Milestone Repository</name> + <url>http://maven.springframework.org/milestone</url> + </repository> + </repositories> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <configuration> + <source>1.5</source> + <target>1.5</target> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-dependency-plugin</artifactId> + <executions> + <execution> + <id>install</id> + <phase>install</phase> + <goals> + <goal>sources</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> +</project> \ No newline at end of file diff --git a/readme.txt b/readme.txt index aee19b7ffee3809815d379f75d6452d0de611ec2..ae1fb2511bc74353ff05a861875e6289132ce043 100644 --- a/readme.txt +++ b/readme.txt @@ -44,22 +44,20 @@ pooling. === Build and Deployment ========================================================================== -The Spring PetClinic sample application is built using Spring Build, which -is a custom build solution based on Ant and Ivy for dependency management. -For deployment, the web application needs to be built with Apache Ant 1.6 -or higher. When the project is first built, Spring Build will use Ivy to -automatically download all required dependencies. Thus the initial build +The Spring PetClinic sample application is built using Maven. +When the project is first built, Maven will automatically download all required +dependencies (if these haven't been downloaded before). Thus the initial build may take a few minutes depending on the speed of your Internet connection, but subsequent builds will be much faster. Available build commands: -- ant clean --> cleans the project -- ant clean test --> cleans the project and runs all tests -- ant clean jar --> cleans the project and builds the WAR +- mvn clean --> cleans the project +- mvn clean test --> cleans the project and runs all tests +- mvn clean package --> cleans the project and builds the WAR -After building the project with "ant clean jar", you will find the -resulting WAR file in the "target/artifacts" directory. By default, an +After building the project with "mvn clean package", you will find the +resulting WAR file in the "target/" directory. By default, an embedded HSQLDB instance in configured. No other steps are necessary to get the data source up and running: you can simply deploy the built WAR file directly to your Servlet container. diff --git a/src/main/java/org/springframework/samples/petclinic/BaseEntity.java b/src/main/java/org/springframework/samples/petclinic/BaseEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..bb68af4fc01f870b25db873e05091890b96fca29 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/BaseEntity.java @@ -0,0 +1,27 @@ +package org.springframework.samples.petclinic; + +/** + * Simple JavaBean domain object with an id property. + * Used as a base class for objects needing this property. + * + * @author Ken Krebs + * @author Juergen Hoeller + */ +public class BaseEntity { + + private 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/src/main/java/org/springframework/samples/petclinic/Clinic.java b/src/main/java/org/springframework/samples/petclinic/Clinic.java new file mode 100644 index 0000000000000000000000000000000000000000..e48e8507b352c7bc757d1853217bd947ea5e12fb --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/Clinic.java @@ -0,0 +1,82 @@ +package org.springframework.samples.petclinic; + +import java.util.Collection; + +import org.springframework.dao.DataAccessException; + +/** + * The high-level PetClinic business interface. + * + * <p>This is basically a data access object. + * PetClinic doesn't have a dedicated business facade. + * + * @author Ken Krebs + * @author Juergen Hoeller + * @author Sam Brannen + */ +public interface Clinic { + + /** + * Retrieve all <code>Vet</code>s from the data store. + * @return a <code>Collection</code> of <code>Vet</code>s + */ + Collection<Vet> getVets() throws DataAccessException; + + /** + * Retrieve all <code>PetType</code>s from the data store. + * @return a <code>Collection</code> of <code>PetType</code>s + */ + Collection<PetType> getPetTypes() throws DataAccessException; + + /** + * Retrieve <code>Owner</code>s from the data store by last name, + * returning all owners whose last name <i>starts</i> with the given name. + * @param lastName Value to search for + * @return a <code>Collection</code> of matching <code>Owner</code>s + * (or an empty <code>Collection</code> if none found) + */ + Collection<Owner> findOwners(String lastName) throws DataAccessException; + + /** + * Retrieve an <code>Owner</code> from the data store by id. + * @param id the id to search for + * @return the <code>Owner</code> if found + * @throws org.springframework.dao.DataRetrievalFailureException if not found + */ + Owner loadOwner(int id) throws DataAccessException; + + /** + * Retrieve a <code>Pet</code> from the data store by id. + * @param id the id to search for + * @return the <code>Pet</code> if found + * @throws org.springframework.dao.DataRetrievalFailureException if not found + */ + Pet loadPet(int id) throws DataAccessException; + + /** + * Save an <code>Owner</code> to the data store, either inserting or updating it. + * @param owner the <code>Owner</code> to save + * @see BaseEntity#isNew + */ + void storeOwner(Owner owner) throws DataAccessException; + + /** + * Save a <code>Pet</code> to the data store, either inserting or updating it. + * @param pet the <code>Pet</code> to save + * @see BaseEntity#isNew + */ + void storePet(Pet pet) throws DataAccessException; + + /** + * Save a <code>Visit</code> to the data store, either inserting or updating it. + * @param visit the <code>Visit</code> to save + * @see BaseEntity#isNew + */ + void storeVisit(Visit visit) throws DataAccessException; + + /** + * Deletes a <code>Pet</code> from the data store. + */ + void deletePet(int id) throws DataAccessException; + +} diff --git a/src/main/java/org/springframework/samples/petclinic/NamedEntity.java b/src/main/java/org/springframework/samples/petclinic/NamedEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..40c5931d86d1ac03f157ced1e63a189274820567 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/NamedEntity.java @@ -0,0 +1,28 @@ +package org.springframework.samples.petclinic; + +/** + * Simple JavaBean domain object adds a name property to <code>BaseEntity</code>. + * Used as a base class for objects needing these properties. + * + * @author Ken Krebs + * @author Juergen Hoeller + */ +public class NamedEntity extends BaseEntity { + + 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/src/main/java/org/springframework/samples/petclinic/Owner.java b/src/main/java/org/springframework/samples/petclinic/Owner.java new file mode 100644 index 0000000000000000000000000000000000000000..75ea3bc064c570ff96f17b7fc8de77bfd64d3342 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/Owner.java @@ -0,0 +1,127 @@ +package org.springframework.samples.petclinic; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +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 + */ +public class Owner extends Person { + + private String address; + + private String city; + + private String telephone; + + private Set<Pet> 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<Pet> pets) { + this.pets = pets; + } + + protected Set<Pet> getPetsInternal() { + if (this.pets == null) { + this.pets = new HashSet<Pet>(); + } + return this.pets; + } + + public List<Pet> getPets() { + List<Pet> sortedPets = new ArrayList<Pet>(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/src/main/java/org/springframework/samples/petclinic/Person.java b/src/main/java/org/springframework/samples/petclinic/Person.java new file mode 100644 index 0000000000000000000000000000000000000000..da7974a7de77a6e64815952f8466c6cbf51bcb1e --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/Person.java @@ -0,0 +1,32 @@ +package org.springframework.samples.petclinic; + +/** + * Simple JavaBean domain object representing an person. + * + * @author Ken Krebs + */ +public class Person extends BaseEntity { + + private String firstName; + + private 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/src/main/java/org/springframework/samples/petclinic/Pet.java b/src/main/java/org/springframework/samples/petclinic/Pet.java new file mode 100644 index 0000000000000000000000000000000000000000..f5294b5ca9f3385ae9f4aea883abb48dd2e7402a --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/Pet.java @@ -0,0 +1,77 @@ +package org.springframework.samples.petclinic; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.springframework.beans.support.MutableSortDefinition; +import org.springframework.beans.support.PropertyComparator; + +/** + * Simple JavaBean business object representing a pet. + * + * @author Ken Krebs + * @author Juergen Hoeller + * @author Sam Brannen + */ +public class Pet extends NamedEntity { + + private Date birthDate; + + private PetType type; + + private Owner owner; + + private Set<Visit> visits; + + + public void setBirthDate(Date birthDate) { + this.birthDate = birthDate; + } + + public Date 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<Visit> visits) { + this.visits = visits; + } + + protected Set<Visit> getVisitsInternal() { + if (this.visits == null) { + this.visits = new HashSet<Visit>(); + } + return this.visits; + } + + public List<Visit> getVisits() { + List<Visit> sortedVisits = new ArrayList<Visit>(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/src/main/java/org/springframework/samples/petclinic/PetType.java b/src/main/java/org/springframework/samples/petclinic/PetType.java new file mode 100644 index 0000000000000000000000000000000000000000..aaadc5c44d099c5d8c3726bbe411a2b335aab2bc --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/PetType.java @@ -0,0 +1,8 @@ +package org.springframework.samples.petclinic; + +/** + * @author Juergen Hoeller + */ +public class PetType extends NamedEntity { + +} diff --git a/src/main/java/org/springframework/samples/petclinic/Specialty.java b/src/main/java/org/springframework/samples/petclinic/Specialty.java new file mode 100644 index 0000000000000000000000000000000000000000..d19ccaba96e42d5f15360f8d2cb4984373c717a3 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/Specialty.java @@ -0,0 +1,10 @@ +package org.springframework.samples.petclinic; + +/** + * Models a {@link Vet Vet's} specialty (for example, dentistry). + * + * @author Juergen Hoeller + */ +public class Specialty extends NamedEntity { + +} diff --git a/src/main/java/org/springframework/samples/petclinic/Vet.java b/src/main/java/org/springframework/samples/petclinic/Vet.java new file mode 100644 index 0000000000000000000000000000000000000000..9c7c8da0b1c32e46cd84a7a8311ba457148dcd89 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/Vet.java @@ -0,0 +1,52 @@ +package org.springframework.samples.petclinic; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +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 + */ +public class Vet extends Person { + + private Set<Specialty> specialties; + + + protected void setSpecialtiesInternal(Set<Specialty> specialties) { + this.specialties = specialties; + } + + protected Set<Specialty> getSpecialtiesInternal() { + if (this.specialties == null) { + this.specialties = new HashSet<Specialty>(); + } + return this.specialties; + } + + @XmlElement + public List<Specialty> getSpecialties() { + List<Specialty> sortedSpecs = new ArrayList<Specialty>(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/src/main/java/org/springframework/samples/petclinic/Vets.java b/src/main/java/org/springframework/samples/petclinic/Vets.java new file mode 100644 index 0000000000000000000000000000000000000000..2e3b25e270a436efc43b2a68a3a93d988764ca84 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/Vets.java @@ -0,0 +1,43 @@ +/* + * Copyright 2002-2009 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; + +import java.util.ArrayList; +import java.util.List; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * Simple JavaBean 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<Vet> vets; + + @XmlElement + public List<Vet> getVetList() { + if (vets == null) { + vets = new ArrayList<Vet>(); + } + return vets; + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/Visit.java b/src/main/java/org/springframework/samples/petclinic/Visit.java new file mode 100644 index 0000000000000000000000000000000000000000..c42bdcee5e5d3c9bdbdf4c140bf9b45d4683ad1f --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/Visit.java @@ -0,0 +1,70 @@ +package org.springframework.samples.petclinic; + +import java.util.Date; + +/** + * Simple JavaBean domain object representing a visit. + * + * @author Ken Krebs + */ +public class Visit extends BaseEntity { + + /** Holds value of property date. */ + private Date date; + + /** Holds value of property description. */ + private String description; + + /** Holds value of property pet. */ + private Pet pet; + + + /** Creates a new instance of Visit for the current date */ + public Visit() { + this.date = new Date(); + } + + + /** Getter for property date. + * @return Value of property date. + */ + public Date getDate() { + return this.date; + } + + /** Setter for property date. + * @param date New value of property date. + */ + public void setDate(Date 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/src/main/java/org/springframework/samples/petclinic/aspects/AbstractTraceAspect.java b/src/main/java/org/springframework/samples/petclinic/aspects/AbstractTraceAspect.java new file mode 100644 index 0000000000000000000000000000000000000000..26d32359fc445e43d6fe7282048a1b2c23a396c1 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/aspects/AbstractTraceAspect.java @@ -0,0 +1,31 @@ +package org.springframework.samples.petclinic.aspects; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.annotation.Pointcut; + +/** + * Aspect to illustrate Spring-driven load-time weaving. + * + * @author Ramnivas Laddad + * @since 2.5 + */ +@Aspect +public abstract class AbstractTraceAspect { + + private static final Log logger = LogFactory.getLog(AbstractTraceAspect.class); + + @Pointcut + public abstract void traced(); + + @Before("traced()") + public void trace(JoinPoint.StaticPart jpsp) { + if (logger.isTraceEnabled()) { + logger.trace("Entering " + jpsp.getSignature().toLongString()); + } + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/aspects/CallMonitoringAspect.java b/src/main/java/org/springframework/samples/petclinic/aspects/CallMonitoringAspect.java new file mode 100644 index 0000000000000000000000000000000000000000..2de4cb41d0a15d94aa0c4b8f39a3accd30ba5558 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/aspects/CallMonitoringAspect.java @@ -0,0 +1,81 @@ +package org.springframework.samples.petclinic.aspects; + +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 AspectJ aspect that monitors call count and call invocation time. + * Implements the CallMonitor management interface. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.5 + */ +@ManagedResource("petclinic:type=CallMonitor") +@Aspect +public class CallMonitoringAspect { + + private boolean isEnabled = true; + + private int callCount = 0; + + private long accumulatedCallTime = 0; + + + @ManagedAttribute + public void setEnabled(boolean enabled) { + isEnabled = enabled; + } + + @ManagedAttribute + public boolean isEnabled() { + return isEnabled; + } + + @ManagedOperation + public void reset() { + this.callCount = 0; + this.accumulatedCallTime = 0; + } + + @ManagedAttribute + public int getCallCount() { + return callCount; + } + + @ManagedAttribute + public long getCallTime() { + return (this.callCount > 0 ? this.accumulatedCallTime / this.callCount : 0); + } + + + @Around("within(@org.springframework.stereotype.Service *)") + public Object invoke(ProceedingJoinPoint joinPoint) throws Throwable { + if (this.isEnabled) { + 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/src/main/java/org/springframework/samples/petclinic/aspects/UsageLogAspect.java b/src/main/java/org/springframework/samples/petclinic/aspects/UsageLogAspect.java new file mode 100644 index 0000000000000000000000000000000000000000..e326abfb755ef1e45b3d84b3f827cf042194f7e8 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/aspects/UsageLogAspect.java @@ -0,0 +1,48 @@ +package org.springframework.samples.petclinic.aspects; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; + +/** + * Sample AspectJ annotation-style aspect that saves + * every owner name requested to the clinic. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 2.0 + */ +@Aspect +public class UsageLogAspect { + + private int historySize = 100; + + // Of course saving all names is not suitable for + // production use, but this is a simple example. + private List<String> namesRequested = new ArrayList<String>(this.historySize); + + + public synchronized void setHistorySize(int historySize) { + this.historySize = historySize; + this.namesRequested = new ArrayList<String>(historySize); + } + + @Before("execution(* *.findOwners(String)) && args(name)") + public synchronized void logNameRequest(String name) { + // Not the most efficient implementation, + // but we're aiming to illustrate the power of + // @AspectJ AOP, not write perfect code here :-) + if (this.namesRequested.size() > this.historySize) { + this.namesRequested.remove(0); + } + this.namesRequested.add(name); + } + + public synchronized List<String> getNamesRequested() { + return Collections.unmodifiableList(this.namesRequested); + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/config/DbcpDataSourceFactory.java b/src/main/java/org/springframework/samples/petclinic/config/DbcpDataSourceFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..0454161e65fa0274952230973204e7d547178044 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/config/DbcpDataSourceFactory.java @@ -0,0 +1,261 @@ +/* + * Copyright 2002-2008 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.config; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.StringWriter; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; + +import javax.sql.DataSource; + +import org.apache.commons.dbcp.BasicDataSource; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.core.io.Resource; + +/** + * A factory that creates a data source fit for use in a system environment. Creates a DBCP simple data source + * from the provided connection properties. + * + * This factory returns a fully-initialized DataSource implementation. When the DataSource is returned, callers are + * guaranteed that the database schema and data will have been loaded by that time. + * + * Is a FactoryBean, for exposing the fully-initialized DataSource as a Spring bean. See {@link #getObject()}. + * + * @author Chris Beams + * @author Scott Andrews + */ +public class DbcpDataSourceFactory implements FactoryBean<DataSource>, DisposableBean { + + // configurable properties + + private String driverClassName; + + private String url; + + private String username; + + private String password; + + private boolean populate; + + private Resource schemaLocation; + + private Resource dataLocation; + + private Resource dropLocation; + + /** + * The object created by this factory. + */ + private BasicDataSource dataSource; + + public void setDriverClassName(String driverClassName) { + this.driverClassName = driverClassName; + } + + /** + * The data source connection URL + */ + public void setUrl(String url) { + this.url = url; + } + + /** + * The data source username + */ + public void setUsername(String username) { + this.username = username; + } + + /** + *The data source password + */ + public void setPassword(String password) { + this.password = password; + } + + /** + * Indicates that the data base should be populated from the schema and data locations + */ + public void setPopulate(boolean populate) { + this.populate = populate; + } + + /** + * Sets the location of the file containing the schema DDL to export to the database. + * @param schemaLocation the location of the database schema DDL + */ + public void setSchemaLocation(Resource schemaLocation) { + this.schemaLocation = schemaLocation; + } + + /** + * Sets the location of the file containing the data to load into the database. + * @param testDataLocation the location of the data file + */ + public void setDataLocation(Resource testDataLocation) { + this.dataLocation = testDataLocation; + } + + /** + * Sets the location of the file containing the drop scripts for the database. + * @param testDataLocation the location of the data file + */ + public void setDropLocation(Resource testDropLocation) { + this.dropLocation = testDropLocation; + } + + // implementing FactoryBean + + // this method is called by Spring to expose the DataSource as a bean + public DataSource getObject() throws Exception { + if (dataSource == null) { + initDataSource(); + } + return dataSource; + } + + public Class<DataSource> getObjectType() { + return DataSource.class; + } + + public boolean isSingleton() { + return true; + } + + // implementing DisposableBean + + public void destroy() throws Exception { + dataSource.close(); + } + + // internal helper methods + + // encapsulates the steps involved in initializing the data source: creating it, and populating it + private void initDataSource() { + // create the database source first + this.dataSource = createDataSource(); + + if (this.populate) { + // now populate the database by loading the schema and data + populateDataSource(); + } + } + + private BasicDataSource createDataSource() { + BasicDataSource dataSource = new BasicDataSource(); + dataSource.setDriverClassName(this.driverClassName); + dataSource.setUrl(this.url); + dataSource.setUsername(this.username); + dataSource.setPassword(this.password); + return dataSource; + } + + private void populateDataSource() { + DatabasePopulator populator = new DatabasePopulator(dataSource); + if (dropLocation != null) { + try { + populator.populate(this.dropLocation); + } + catch (Exception e) { + // ignore + } + } + populator.populate(this.schemaLocation); + populator.populate(this.dataLocation); + } + + /** + * Populates a in memory data source with data. + */ + private class DatabasePopulator { + + private DataSource dataSource; + + /** + * Creates a new database populator. + * @param dataSource the data source that will be populated. + */ + public DatabasePopulator(DataSource dataSource) { + this.dataSource = dataSource; + } + + /** + * Populate the database executing the statements in the provided resource against the database + * @param sqlFile spring resource containing SQL to run against the db + */ + public void populate(Resource sqlFile) { + Connection connection = null; + try { + connection = dataSource.getConnection(); + try { + String sql = parseSqlIn(sqlFile); + executeSql(sql, connection); + } catch (IOException e) { + throw new RuntimeException("I/O exception occurred accessing the database schema file", e); + } catch (SQLException e) { + throw new RuntimeException("SQL exception occurred exporting database schema", e); + } + } catch (SQLException e) { + throw new RuntimeException("SQL exception occurred acquiring connection", e); + } finally { + if (connection != null) { + try { + connection.close(); + } catch (SQLException e) { + } + } + } + } + + // utility method to read a .sql txt input stream + private String parseSqlIn(Resource resource) throws IOException { + InputStream is = null; + try { + is = resource.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + + StringWriter sw = new StringWriter(); + BufferedWriter writer = new BufferedWriter(sw); + + for (int c=reader.read(); c != -1; c=reader.read()) { + writer.write(c); + } + writer.flush(); + return sw.toString(); + + } finally { + if (is != null) { + is.close(); + } + } + } + + // utility method to run the parsed sql + private void executeSql(String sql, Connection connection) throws SQLException { + Statement statement = connection.createStatement(); + statement.execute(sql); + } + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/hibernate/HibernateClinic.java b/src/main/java/org/springframework/samples/petclinic/hibernate/HibernateClinic.java new file mode 100644 index 0000000000000000000000000000000000000000..4116385628173b1e9e167390b87460cfea492981 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/hibernate/HibernateClinic.java @@ -0,0 +1,98 @@ +package org.springframework.samples.petclinic.hibernate; + +import java.util.Collection; + +import org.hibernate.SessionFactory; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataAccessException; +import org.springframework.samples.petclinic.Clinic; +import org.springframework.samples.petclinic.Owner; +import org.springframework.samples.petclinic.Pet; +import org.springframework.samples.petclinic.PetType; +import org.springframework.samples.petclinic.Vet; +import org.springframework.samples.petclinic.Visit; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +/** + * Hibernate implementation of the Clinic interface. + * + * <p>The mappings are defined in "petclinic.hbm.xml", located in the root of the + * class path. + * + * <p>Note that transactions are declared with annotations and that some methods + * contain "readOnly = true" which is an optimization that is particularly + * valuable when using Hibernate (to suppress unnecessary flush attempts for + * read-only operations). + * + * @author Juergen Hoeller + * @author Sam Brannen + * @author Mark Fisher + * @since 19.10.2003 + */ +@Repository +@Transactional +public class HibernateClinic implements Clinic { + + private SessionFactory sessionFactory; + + @Autowired + public HibernateClinic(SessionFactory sessionFactory) { + this.sessionFactory = sessionFactory; + } + + @Transactional(readOnly = true) + @SuppressWarnings("unchecked") + public Collection<Vet> getVets() { + return sessionFactory.getCurrentSession().createQuery("from Vet vet order by vet.lastName, vet.firstName").list(); + } + + @Transactional(readOnly = true) + @SuppressWarnings("unchecked") + public Collection<PetType> getPetTypes() { + return sessionFactory.getCurrentSession().createQuery("from PetType type order by type.name").list(); + } + + @Transactional(readOnly = true) + @SuppressWarnings("unchecked") + public Collection<Owner> findOwners(String lastName) { + return sessionFactory.getCurrentSession().createQuery("from Owner owner where owner.lastName like :lastName") + .setString("lastName", lastName + "%").list(); + } + + @Transactional(readOnly = true) + public Owner loadOwner(int id) { + return (Owner) sessionFactory.getCurrentSession().load(Owner.class, id); + } + + @Transactional(readOnly = true) + public Pet loadPet(int id) { + return (Pet) sessionFactory.getCurrentSession().load(Pet.class, id); + } + + public void storeOwner(Owner owner) { + // Note: Hibernate3's merge operation does not reassociate the object + // with the current Hibernate Session. Instead, it will always copy the + // state over to a registered representation of the entity. In case of a + // new entity, it will register a copy as well, but will not update the + // id of the passed-in object. To still update the ids of the original + // objects too, we need to register Spring's + // IdTransferringMergeEventListener on our SessionFactory. + sessionFactory.getCurrentSession().merge(owner); + } + + public void storePet(Pet pet) { + sessionFactory.getCurrentSession().merge(pet); + } + + public void storeVisit(Visit visit) { + sessionFactory.getCurrentSession().merge(visit); + } + + public void deletePet(int id) throws DataAccessException { + Pet pet = loadPet(id); + sessionFactory.getCurrentSession().delete(pet); + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/hibernate/package-info.java b/src/main/java/org/springframework/samples/petclinic/hibernate/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..e39ebac4735c0d4e82b4d14074b2b19fe9c551fd --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/hibernate/package-info.java @@ -0,0 +1,9 @@ + +/** + * + * The classes in this package represent the Hibernate implementation + * of PetClinic's persistence layer. + * + */ +package org.springframework.samples.petclinic.hibernate; + diff --git a/src/main/java/org/springframework/samples/petclinic/jdbc/JdbcPet.java b/src/main/java/org/springframework/samples/petclinic/jdbc/JdbcPet.java new file mode 100644 index 0000000000000000000000000000000000000000..963ffdfe0332731705a806d57074514e53ae4bb3 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/jdbc/JdbcPet.java @@ -0,0 +1,35 @@ +package org.springframework.samples.petclinic.jdbc; + +import org.springframework.samples.petclinic.Pet; + +/** + * Subclass of Pet that carries temporary id properties which + * are only relevant for a JDBC implmentation of the Clinic. + * + * @author Juergen Hoeller + * @see SimpleJdbcClinic + */ +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/src/main/java/org/springframework/samples/petclinic/jdbc/SimpleJdbcClinic.java b/src/main/java/org/springframework/samples/petclinic/jdbc/SimpleJdbcClinic.java new file mode 100644 index 0000000000000000000000000000000000000000..587acecb9516802dd890dbe2942dd8a69715744c --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/jdbc/SimpleJdbcClinic.java @@ -0,0 +1,342 @@ +package org.springframework.samples.petclinic.jdbc; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import javax.sql.DataSource; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.simple.ParameterizedBeanPropertyRowMapper; +import org.springframework.jdbc.core.simple.ParameterizedRowMapper; +import org.springframework.jdbc.core.simple.SimpleJdbcInsert; +import org.springframework.jdbc.core.simple.SimpleJdbcTemplate; +import org.springframework.jmx.export.annotation.ManagedOperation; +import org.springframework.jmx.export.annotation.ManagedResource; +import org.springframework.orm.ObjectRetrievalFailureException; +import org.springframework.samples.petclinic.Clinic; +import org.springframework.samples.petclinic.Owner; +import org.springframework.samples.petclinic.Pet; +import org.springframework.samples.petclinic.PetType; +import org.springframework.samples.petclinic.Specialty; +import org.springframework.samples.petclinic.Vet; +import org.springframework.samples.petclinic.Visit; +import org.springframework.samples.petclinic.util.EntityUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * A simple JDBC-based implementation of the {@link Clinic} interface. + * + * <p>This class uses Java 5 language features and the {@link SimpleJdbcTemplate} + * plus {@link SimpleJdbcInsert}. It also takes advantage of classes like + * {@link BeanPropertySqlParameterSource} and + * {@link ParameterizedBeanPropertyRowMapper} which provide automatic mapping + * between JavaBean properties and JDBC parameters or query results. + * + * <p>SimpleJdbcClinic is a rewrite of the AbstractJdbcClinic which was the base + * class for JDBC implementations of the Clinic interface for Spring 2.0. + * + * @author Ken Krebs + * @author Juergen Hoeller + * @author Rob Harrop + * @author Sam Brannen + * @author Thomas Risberg + * @author Mark Fisher + */ +@Service +@ManagedResource("petclinic:type=Clinic") +public class SimpleJdbcClinic implements Clinic, SimpleJdbcClinicMBean { + + private final Log logger = LogFactory.getLog(getClass()); + + private SimpleJdbcTemplate simpleJdbcTemplate; + + private SimpleJdbcInsert insertOwner; + private SimpleJdbcInsert insertPet; + private SimpleJdbcInsert insertVisit; + + private final List<Vet> vets = new ArrayList<Vet>(); + + + @Autowired + public void init(DataSource dataSource) { + this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource); + + this.insertOwner = new SimpleJdbcInsert(dataSource) + .withTableName("owners") + .usingGeneratedKeyColumns("id"); + this.insertPet = new SimpleJdbcInsert(dataSource) + .withTableName("pets") + .usingGeneratedKeyColumns("id"); + this.insertVisit = new SimpleJdbcInsert(dataSource) + .withTableName("visits") + .usingGeneratedKeyColumns("id"); + } + + + /** + * Refresh the cache of Vets that the Clinic is holding. + * @see org.springframework.samples.petclinic.Clinic#getVets() + */ + @ManagedOperation + @Transactional(readOnly = true) + public void refreshVetsCache() throws DataAccessException { + synchronized (this.vets) { + this.logger.info("Refreshing vets cache"); + + // Retrieve the list of all vets. + this.vets.clear(); + this.vets.addAll(this.simpleJdbcTemplate.query( + "SELECT id, first_name, last_name FROM vets ORDER BY last_name,first_name", + ParameterizedBeanPropertyRowMapper.newInstance(Vet.class))); + + // Retrieve the list of all possible specialties. + final List<Specialty> specialties = this.simpleJdbcTemplate.query( + "SELECT id, name FROM specialties", + ParameterizedBeanPropertyRowMapper.newInstance(Specialty.class)); + + // Build each vet's list of specialties. + for (Vet vet : this.vets) { + final List<Integer> vetSpecialtiesIds = this.simpleJdbcTemplate.query( + "SELECT specialty_id FROM vet_specialties WHERE vet_id=?", + new ParameterizedRowMapper<Integer>() { + 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); + } + } + } + } + + + // START of Clinic implementation section ******************************* + + @Transactional(readOnly = true) + public Collection<Vet> getVets() throws DataAccessException { + synchronized (this.vets) { + if (this.vets.isEmpty()) { + refreshVetsCache(); + } + return this.vets; + } + } + + @Transactional(readOnly = true) + public Collection<PetType> getPetTypes() throws DataAccessException { + return this.simpleJdbcTemplate.query( + "SELECT id, name FROM types ORDER BY name", + ParameterizedBeanPropertyRowMapper.newInstance(PetType.class)); + } + + /** + * Loads {@link Owner Owners} from the data store by last name, returning + * all owners whose last name <i>starts</i> with the given name; also loads + * the {@link Pet Pets} and {@link Visit Visits} for the corresponding + * owners, if not already loaded. + */ + @Transactional(readOnly = true) + public Collection<Owner> findOwners(String lastName) throws DataAccessException { + List<Owner> owners = this.simpleJdbcTemplate.query( + "SELECT id, first_name, last_name, address, city, telephone FROM owners WHERE last_name like ?", + ParameterizedBeanPropertyRowMapper.newInstance(Owner.class), + lastName + "%"); + loadOwnersPetsAndVisits(owners); + return owners; + } + + /** + * Loads the {@link Owner} with the supplied <code>id</code>; also loads + * the {@link Pet Pets} and {@link Visit Visits} for the corresponding + * owner, if not already loaded. + */ + @Transactional(readOnly = true) + public Owner loadOwner(int id) throws DataAccessException { + Owner owner; + try { + owner = this.simpleJdbcTemplate.queryForObject( + "SELECT id, first_name, last_name, address, city, telephone FROM owners WHERE id=?", + ParameterizedBeanPropertyRowMapper.newInstance(Owner.class), + id); + } + catch (EmptyResultDataAccessException ex) { + throw new ObjectRetrievalFailureException(Owner.class, new Integer(id)); + } + loadPetsAndVisits(owner); + return owner; + } + + @Transactional(readOnly = true) + public Pet loadPet(int id) throws DataAccessException { + JdbcPet pet; + try { + pet = this.simpleJdbcTemplate.queryForObject( + "SELECT id, name, birth_date, type_id, owner_id FROM pets WHERE id=?", + new JdbcPetRowMapper(), + id); + } + catch (EmptyResultDataAccessException ex) { + throw new ObjectRetrievalFailureException(Pet.class, new Integer(id)); + } + Owner owner = loadOwner(pet.getOwnerId()); + owner.addPet(pet); + pet.setType(EntityUtils.getById(getPetTypes(), PetType.class, pet.getTypeId())); + loadVisits(pet); + return pet; + } + + @Transactional + public void storeOwner(Owner owner) throws DataAccessException { + if (owner.isNew()) { + Number newKey = this.insertOwner.executeAndReturnKey( + new BeanPropertySqlParameterSource(owner)); + owner.setId(newKey.intValue()); + } + else { + this.simpleJdbcTemplate.update( + "UPDATE owners SET first_name=:firstName, last_name=:lastName, address=:address, " + + "city=:city, telephone=:telephone WHERE id=:id", + new BeanPropertySqlParameterSource(owner)); + } + } + + @Transactional + public void storePet(Pet pet) throws DataAccessException { + if (pet.isNew()) { + Number newKey = this.insertPet.executeAndReturnKey( + createPetParameterSource(pet)); + pet.setId(newKey.intValue()); + } + else { + this.simpleJdbcTemplate.update( + "UPDATE pets SET name=:name, birth_date=:birth_date, type_id=:type_id, " + + "owner_id=:owner_id WHERE id=:id", + createPetParameterSource(pet)); + } + } + + @Transactional + public void storeVisit(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.simpleJdbcTemplate.update("DELETE FROM pets WHERE id=?", id); + } + + // END of Clinic implementation section ************************************ + + + /** + * 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()) + .addValue("type_id", pet.getType().getId()) + .addValue("owner_id", pet.getOwner().getId()); + } + + /** + * 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()) + .addValue("description", visit.getDescription()) + .addValue("pet_id", visit.getPet().getId()); + } + + /** + * Loads the {@link Visit} data for the supplied {@link Pet}. + */ + private void loadVisits(JdbcPet pet) { + final List<Visit> visits = this.simpleJdbcTemplate.query( + "SELECT id, visit_date, description FROM visits WHERE pet_id=?", + new ParameterizedRowMapper<Visit>() { + public Visit mapRow(ResultSet rs, int row) throws SQLException { + Visit visit = new Visit(); + visit.setId(rs.getInt("id")); + visit.setDate(rs.getTimestamp("visit_date")); + visit.setDescription(rs.getString("description")); + return visit; + } + }, + pet.getId().intValue()); + for (Visit visit : visits) { + pet.addVisit(visit); + } + } + + /** + * Loads the {@link Pet} and {@link Visit} data for the supplied + * {@link Owner}. + */ + private void loadPetsAndVisits(final Owner owner) { + final List<JdbcPet> pets = this.simpleJdbcTemplate.query( + "SELECT id, name, birth_date, type_id, owner_id FROM pets WHERE owner_id=?", + new JdbcPetRowMapper(), + owner.getId().intValue()); + for (JdbcPet pet : pets) { + owner.addPet(pet); + pet.setType(EntityUtils.getById(getPetTypes(), PetType.class, pet.getTypeId())); + loadVisits(pet); + } + } + + /** + * 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<Owner> owners) { + for (Owner owner : owners) { + loadPetsAndVisits(owner); + } + } + + /** + * {@link ParameterizedRowMapper} implementation mapping data from a + * {@link ResultSet} to the corresponding properties of the {@link JdbcPet} class. + */ + private class JdbcPetRowMapper implements ParameterizedRowMapper<JdbcPet> { + + public JdbcPet mapRow(ResultSet rs, int rownum) throws SQLException { + JdbcPet pet = new JdbcPet(); + pet.setId(rs.getInt("id")); + pet.setName(rs.getString("name")); + pet.setBirthDate(rs.getDate("birth_date")); + pet.setTypeId(rs.getInt("type_id")); + pet.setOwnerId(rs.getInt("owner_id")); + return pet; + } + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/jdbc/SimpleJdbcClinicMBean.java b/src/main/java/org/springframework/samples/petclinic/jdbc/SimpleJdbcClinicMBean.java new file mode 100644 index 0000000000000000000000000000000000000000..c9a7a78478be439d5bdb84c25c94450a1495dd28 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/jdbc/SimpleJdbcClinicMBean.java @@ -0,0 +1,20 @@ +package org.springframework.samples.petclinic.jdbc; + +/** + * Interface that defines a cache refresh operation. + * To be exposed for management via JMX. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @see SimpleJdbcClinic + */ +public interface SimpleJdbcClinicMBean { + + /** + * Refresh the cache of Vets that the Clinic is holding. + * @see org.springframework.samples.petclinic.Clinic#getVets() + * @see SimpleJdbcClinic#refreshVetsCache() + */ + void refreshVetsCache(); + +} diff --git a/src/main/java/org/springframework/samples/petclinic/jdbc/package-info.java b/src/main/java/org/springframework/samples/petclinic/jdbc/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..6ec278b44653530cfa12a275e08c1af6f8bb05e8 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/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.jdbc; + diff --git a/src/main/java/org/springframework/samples/petclinic/jpa/EntityManagerClinic.java b/src/main/java/org/springframework/samples/petclinic/jpa/EntityManagerClinic.java new file mode 100644 index 0000000000000000000000000000000000000000..92fe1e247711fc679d2771c1f98c51abcacad46b --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/jpa/EntityManagerClinic.java @@ -0,0 +1,96 @@ +package org.springframework.samples.petclinic.jpa; + +import java.util.Collection; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.Query; + +import org.springframework.samples.petclinic.Clinic; +import org.springframework.samples.petclinic.Owner; +import org.springframework.samples.petclinic.Pet; +import org.springframework.samples.petclinic.PetType; +import org.springframework.samples.petclinic.Vet; +import org.springframework.samples.petclinic.Visit; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.dao.DataAccessException; + +/** + * JPA implementation of the Clinic interface using EntityManager. + * + * <p>The mappings are defined in "orm.xml" located in the META-INF directory. + * + * @author Mike Keith + * @author Rod Johnson + * @author Sam Brannen + * @since 22.4.2006 + */ +@Repository +@Transactional +public class EntityManagerClinic implements Clinic { + + @PersistenceContext + private EntityManager em; + + + @Transactional(readOnly = true) + @SuppressWarnings("unchecked") + public Collection<Vet> getVets() { + return this.em.createQuery("SELECT vet FROM Vet vet ORDER BY vet.lastName, vet.firstName").getResultList(); + } + + @Transactional(readOnly = true) + @SuppressWarnings("unchecked") + public Collection<PetType> getPetTypes() { + return this.em.createQuery("SELECT ptype FROM PetType ptype ORDER BY ptype.name").getResultList(); + } + + @Transactional(readOnly = true) + @SuppressWarnings("unchecked") + public Collection<Owner> findOwners(String lastName) { + Query query = this.em.createQuery("SELECT owner FROM Owner owner WHERE owner.lastName LIKE :lastName"); + query.setParameter("lastName", lastName + "%"); + return query.getResultList(); + } + + @Transactional(readOnly = true) + public Owner loadOwner(int id) { + return this.em.find(Owner.class, id); + } + + @Transactional(readOnly = true) + public Pet loadPet(int id) { + return this.em.find(Pet.class, id); + } + + public void storeOwner(Owner owner) { + // Consider returning the persistent object here, for exposing + // a newly assigned id using any persistence provider... + Owner merged = this.em.merge(owner); + this.em.flush(); + owner.setId(merged.getId()); + } + + public void storePet(Pet pet) { + // Consider returning the persistent object here, for exposing + // a newly assigned id using any persistence provider... + Pet merged = this.em.merge(pet); + this.em.flush(); + pet.setId(merged.getId()); + } + + public void storeVisit(Visit visit) { + // Consider returning the persistent object here, for exposing + // a newly assigned id using any persistence provider... + Visit merged = this.em.merge(visit); + this.em.flush(); + visit.setId(merged.getId()); + } + + public void deletePet(int id) throws DataAccessException { + Pet pet = loadPet(id); + this.em.remove(pet); + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/jpa/package-info.java b/src/main/java/org/springframework/samples/petclinic/jpa/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..8093784ec542da7114b13ef5bb8b233d877261ab --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/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.jpa; + diff --git a/src/main/java/org/springframework/samples/petclinic/package-info.java b/src/main/java/org/springframework/samples/petclinic/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..f2cc3691593e623aee75ac6ac542e2b16e31bdf0 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/package-info.java @@ -0,0 +1,8 @@ + +/** + * + * The classes in this package represent PetClinic's business layer. + * + */ +package org.springframework.samples.petclinic; + diff --git a/src/main/java/org/springframework/samples/petclinic/toplink/EssentialsHSQLPlatformWithNativeSequence.java b/src/main/java/org/springframework/samples/petclinic/toplink/EssentialsHSQLPlatformWithNativeSequence.java new file mode 100644 index 0000000000000000000000000000000000000000..1086591decd45594794ee8188800480997cbeacf --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/toplink/EssentialsHSQLPlatformWithNativeSequence.java @@ -0,0 +1,56 @@ +package org.springframework.samples.petclinic.toplink; + +import java.io.IOException; +import java.io.Writer; + +import oracle.toplink.essentials.exceptions.ValidationException; +import oracle.toplink.essentials.platform.database.HSQLPlatform; +import oracle.toplink.essentials.queryframework.ValueReadQuery; + +/** + * Subclass of the TopLink Essentials default HSQLPlatform class, using native + * HSQLDB identity columns for id generation. + * + * <p>Necessary for PetClinic's default data model, which relies on identity + * columns: this is uniformly used across all persistence layer implementations + * (JDBC, Hibernate, and JPA). + * + * @author Juergen Hoeller + * @author <a href="mailto:james.x.clark@oracle.com">James Clark</a> + * @since 1.2 + */ +public class EssentialsHSQLPlatformWithNativeSequence extends HSQLPlatform { + + private static final long serialVersionUID = -55658009691346735L; + + + public EssentialsHSQLPlatformWithNativeSequence() { + // setUsesNativeSequencing(true); + } + + @Override + public boolean supportsNativeSequenceNumbers() { + return true; + } + + @Override + public boolean shouldNativeSequenceAcquireValueAfterInsert() { + return true; + } + + @Override + public ValueReadQuery buildSelectQueryForNativeSequence() { + return new ValueReadQuery("CALL IDENTITY()"); + } + + @Override + public void printFieldIdentityClause(Writer writer) throws ValidationException { + try { + writer.write(" IDENTITY"); + } + catch (IOException ex) { + throw ValidationException.fileError(ex); + } + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/toplink/package-info.java b/src/main/java/org/springframework/samples/petclinic/toplink/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..3bcc9add7db5061f164945fb223e0bf995996cf6 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/toplink/package-info.java @@ -0,0 +1,10 @@ + +/** + * + * The classes in this package provide support for using the TopLink + * implementation with PetClinic's EntityManagerClinic. + * + * + */ +package org.springframework.samples.petclinic.toplink; + diff --git a/src/main/java/org/springframework/samples/petclinic/util/EntityUtils.java b/src/main/java/org/springframework/samples/petclinic/util/EntityUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..16df5fa9a916c1d86339b1b3ae228541477036c7 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/util/EntityUtils.java @@ -0,0 +1,41 @@ + +package org.springframework.samples.petclinic.util; + +import java.util.Collection; + +import org.springframework.orm.ObjectRetrievalFailureException; +import org.springframework.samples.petclinic.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 + * @since 29.10.2003 + * @see org.springframework.samples.petclinic.BaseEntity + */ +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 extends BaseEntity> T getById(Collection<T> entities, Class<T> entityClass, int entityId) + throws ObjectRetrievalFailureException { + for (T entity : entities) { + if (entity.getId().intValue() == entityId && entityClass.isInstance(entity)) { + return entity; + } + } + throw new ObjectRetrievalFailureException(entityClass, new Integer(entityId)); + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/validation/OwnerValidator.java b/src/main/java/org/springframework/samples/petclinic/validation/OwnerValidator.java new file mode 100644 index 0000000000000000000000000000000000000000..04b6b7d58afd411579ef99b864db8e97e63e3921 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/validation/OwnerValidator.java @@ -0,0 +1,43 @@ +package org.springframework.samples.petclinic.validation; + +import org.springframework.samples.petclinic.Owner; +import org.springframework.util.StringUtils; +import org.springframework.validation.Errors; + +/** + * <code>Validator</code> for <code>Owner</code> forms. + * + * @author Ken Krebs + * @author Juergen Hoeller + */ +public class OwnerValidator { + + public void validate(Owner owner, Errors errors) { + if (!StringUtils.hasLength(owner.getFirstName())) { + errors.rejectValue("firstName", "required", "required"); + } + if (!StringUtils.hasLength(owner.getLastName())) { + errors.rejectValue("lastName", "required", "required"); + } + if (!StringUtils.hasLength(owner.getAddress())) { + errors.rejectValue("address", "required", "required"); + } + if (!StringUtils.hasLength(owner.getCity())) { + errors.rejectValue("city", "required", "required"); + } + + String telephone = owner.getTelephone(); + if (!StringUtils.hasLength(telephone)) { + errors.rejectValue("telephone", "required", "required"); + } + else { + for (int i = 0; i < telephone.length(); ++i) { + if ((Character.isDigit(telephone.charAt(i))) == false) { + errors.rejectValue("telephone", "nonNumeric", "non-numeric"); + break; + } + } + } + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/validation/PetValidator.java b/src/main/java/org/springframework/samples/petclinic/validation/PetValidator.java new file mode 100644 index 0000000000000000000000000000000000000000..8ad6eb0ac03507e727ec62b9d8e26017cff4108f --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/validation/PetValidator.java @@ -0,0 +1,25 @@ +package org.springframework.samples.petclinic.validation; + +import org.springframework.samples.petclinic.Pet; +import org.springframework.util.StringUtils; +import org.springframework.validation.Errors; + +/** + * <code>Validator</code> for <code>Pet</code> forms. + * + * @author Ken Krebs + * @author Juergen Hoeller + */ +public class PetValidator { + + public void validate(Pet pet, Errors errors) { + String name = pet.getName(); + 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"); + } + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/validation/VisitValidator.java b/src/main/java/org/springframework/samples/petclinic/validation/VisitValidator.java new file mode 100644 index 0000000000000000000000000000000000000000..35c80bafa10f289717dc81e731bf57b33f6e701b --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/validation/VisitValidator.java @@ -0,0 +1,21 @@ +package org.springframework.samples.petclinic.validation; + +import org.springframework.samples.petclinic.Visit; +import org.springframework.util.StringUtils; +import org.springframework.validation.Errors; + +/** + * <code>Validator</code> for <code>Visit</code> forms. + * + * @author Ken Krebs + * @author Juergen Hoeller + */ +public class VisitValidator { + + public void validate(Visit visit, Errors errors) { + if (!StringUtils.hasLength(visit.getDescription())) { + errors.rejectValue("description", "required", "required"); + } + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/validation/package-info.java b/src/main/java/org/springframework/samples/petclinic/validation/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..7db2ee521d74f681ce92b1e15ee502db00c67179 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/validation/package-info.java @@ -0,0 +1,9 @@ + +/** + * + * The classes in this package represent the set of Validator objects + * the Business Layer makes available to the Presentation Layer. + * + */ +package org.springframework.samples.petclinic.validation; + diff --git a/src/main/java/org/springframework/samples/petclinic/web/AddOwnerForm.java b/src/main/java/org/springframework/samples/petclinic/web/AddOwnerForm.java new file mode 100644 index 0000000000000000000000000000000000000000..cd830aff6fbe14d1f03793af926fcb72c638e007 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/web/AddOwnerForm.java @@ -0,0 +1,65 @@ + +package org.springframework.samples.petclinic.web; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.samples.petclinic.Clinic; +import org.springframework.samples.petclinic.Owner; +import org.springframework.samples.petclinic.validation.OwnerValidator; +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.ModelAttribute; +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; + +/** + * JavaBean form controller that is used to add a new <code>Owner</code> to the + * system. + * + * @author Juergen Hoeller + * @author Ken Krebs + * @author Arjen Poutsma + */ +@Controller +@RequestMapping("/owners/new") +@SessionAttributes(types = Owner.class) +public class AddOwnerForm { + + private final Clinic clinic; + + + @Autowired + public AddOwnerForm(Clinic clinic) { + this.clinic = clinic; + } + + @InitBinder + public void setAllowedFields(WebDataBinder dataBinder) { + dataBinder.setDisallowedFields("id"); + } + + @RequestMapping(method = RequestMethod.GET) + public String setupForm(Model model) { + Owner owner = new Owner(); + model.addAttribute(owner); + return "owners/form"; + } + + @RequestMapping(method = RequestMethod.POST) + public String processSubmit(@ModelAttribute Owner owner, BindingResult result, SessionStatus status) { + new OwnerValidator().validate(owner, result); + if (result.hasErrors()) { + return "owners/form"; + } + else { + this.clinic.storeOwner(owner); + status.setComplete(); + return "redirect:/owners/" + owner.getId(); + } + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/web/AddPetForm.java b/src/main/java/org/springframework/samples/petclinic/web/AddPetForm.java new file mode 100644 index 0000000000000000000000000000000000000000..586cf3d67c5eead7b5144b9dc7f0d740e7586f34 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/web/AddPetForm.java @@ -0,0 +1,77 @@ + +package org.springframework.samples.petclinic.web; + +import java.util.Collection; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.samples.petclinic.Clinic; +import org.springframework.samples.petclinic.Owner; +import org.springframework.samples.petclinic.Pet; +import org.springframework.samples.petclinic.PetType; +import org.springframework.samples.petclinic.validation.PetValidator; +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.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; + +/** + * JavaBean form controller that is used to add a new <code>Pet</code> to the + * system. + * + * @author Juergen Hoeller + * @author Ken Krebs + * @author Arjen Poutsma + */ +@Controller +@RequestMapping("/owners/{ownerId}/pets/new") +@SessionAttributes("pet") +public class AddPetForm { + + private final Clinic clinic; + + + @Autowired + public AddPetForm(Clinic clinic) { + this.clinic = clinic; + } + + @ModelAttribute("types") + public Collection<PetType> populatePetTypes() { + return this.clinic.getPetTypes(); + } + + @InitBinder + public void setAllowedFields(WebDataBinder dataBinder) { + dataBinder.setDisallowedFields("id"); + } + + @RequestMapping(method = RequestMethod.GET) + public String setupForm(@PathVariable("ownerId") int ownerId, Model model) { + Owner owner = this.clinic.loadOwner(ownerId); + Pet pet = new Pet(); + owner.addPet(pet); + model.addAttribute("pet", pet); + return "pets/form"; + } + + @RequestMapping(method = RequestMethod.POST) + public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result, SessionStatus status) { + new PetValidator().validate(pet, result); + if (result.hasErrors()) { + return "pets/form"; + } + else { + this.clinic.storePet(pet); + status.setComplete(); + return "redirect:/owners/" + pet.getOwner().getId(); + } + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/web/AddVisitForm.java b/src/main/java/org/springframework/samples/petclinic/web/AddVisitForm.java new file mode 100644 index 0000000000000000000000000000000000000000..68368644026b9e1d1ffba76c11644325c984829f --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/web/AddVisitForm.java @@ -0,0 +1,69 @@ + +package org.springframework.samples.petclinic.web; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.samples.petclinic.Clinic; +import org.springframework.samples.petclinic.Pet; +import org.springframework.samples.petclinic.Visit; +import org.springframework.samples.petclinic.validation.VisitValidator; +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.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; + +/** + * JavaBean form controller that is used to add a new <code>Visit</code> to the + * system. + * + * @author Juergen Hoeller + * @author Ken Krebs + * @author Arjen Poutsma + */ +@Controller +@RequestMapping("/owners/*/pets/{petId}/visits/new") +@SessionAttributes("visit") +public class AddVisitForm { + + private final Clinic clinic; + + + @Autowired + public AddVisitForm(Clinic clinic) { + this.clinic = clinic; + } + + @InitBinder + public void setAllowedFields(WebDataBinder dataBinder) { + dataBinder.setDisallowedFields("id"); + } + + @RequestMapping(method = RequestMethod.GET) + public String setupForm(@PathVariable("petId") int petId, Model model) { + Pet pet = this.clinic.loadPet(petId); + Visit visit = new Visit(); + pet.addVisit(visit); + model.addAttribute("visit", visit); + return "pets/visitForm"; + } + + @RequestMapping(method = RequestMethod.POST) + public String processSubmit(@ModelAttribute("visit") Visit visit, BindingResult result, SessionStatus status) { + new VisitValidator().validate(visit, result); + if (result.hasErrors()) { + return "pets/visitForm"; + } + else { + this.clinic.storeVisit(visit); + status.setComplete(); + return "redirect:/owners/" + visit.getPet().getOwner().getId(); + } + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/web/ClinicBindingInitializer.java b/src/main/java/org/springframework/samples/petclinic/web/ClinicBindingInitializer.java new file mode 100644 index 0000000000000000000000000000000000000000..2d2a8bdc15f3d4d07b394e2e823401fb0101109f --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/web/ClinicBindingInitializer.java @@ -0,0 +1,37 @@ +package org.springframework.samples.petclinic.web; + +import java.text.SimpleDateFormat; +import java.util.Date; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.propertyeditors.CustomDateEditor; +import org.springframework.beans.propertyeditors.StringTrimmerEditor; +import org.springframework.samples.petclinic.Clinic; +import org.springframework.samples.petclinic.PetType; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.support.WebBindingInitializer; +import org.springframework.web.context.request.WebRequest; + +/** + * Shared WebBindingInitializer for PetClinic's custom editors. + * + * <p>Alternatively, such init-binder code may be put into + * {@link org.springframework.web.bind.annotation.InitBinder} + * annotated methods on the controller classes themselves. + * + * @author Juergen Hoeller + */ +public class ClinicBindingInitializer implements WebBindingInitializer { + + @Autowired + private Clinic clinic; + + public void initBinder(WebDataBinder binder, WebRequest request) { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); + dateFormat.setLenient(false); + binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false)); + binder.registerCustomEditor(String.class, new StringTrimmerEditor(false)); + binder.registerCustomEditor(PetType.class, new PetTypeEditor(this.clinic)); + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/web/ClinicController.java b/src/main/java/org/springframework/samples/petclinic/web/ClinicController.java new file mode 100644 index 0000000000000000000000000000000000000000..e93ae8f66633fd8ec26784c0cb9a646c53c88277 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/web/ClinicController.java @@ -0,0 +1,89 @@ + +package org.springframework.samples.petclinic.web; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.samples.petclinic.Clinic; +import org.springframework.samples.petclinic.Vets; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +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.servlet.ModelAndView; + +/** + * Annotation-driven <em>MultiActionController</em> that handles all non-form + * URL's. + * + * @author Juergen Hoeller + * @author Mark Fisher + * @author Ken Krebs + * @author Arjen Poutsma + */ +@Controller +public class ClinicController { + + private final Clinic clinic; + + + @Autowired + public ClinicController(Clinic clinic) { + this.clinic = clinic; + } + + /** + * Custom handler for the welcome view. + * <p> + * Note that this handler relies on the RequestToViewNameTranslator to + * determine the logical view name based on the request URL: "/welcome.do" + * -> "welcome". + */ + @RequestMapping("/") + public String welcomeHandler() { + return "welcome"; + } + + /** + * Custom handler for displaying vets. + * + * <p>Note that this handler returns a plain {@link ModelMap} object instead of + * a ModelAndView, thus leveraging convention-based model attribute names. + * It relies on the RequestToViewNameTranslator to determine the logical + * view name based on the request URL: "/vets.do" -> "vets". + * + * @return a ModelMap with the model attributes for the view + */ + @RequestMapping("/vets") + public ModelMap vetsHandler() { + Vets vets = new Vets(); + vets.getVetList().addAll(this.clinic.getVets()); + return new ModelMap(vets); + } + + /** + * 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 ownerHandler(@PathVariable("ownerId") int ownerId) { + ModelAndView mav = new ModelAndView("owners/show"); + mav.addObject(this.clinic.loadOwner(ownerId)); + return mav; + } + + /** + * Custom handler for displaying an list of visits. + * + * @param petId the ID of the pet whose visits to display + * @return a ModelMap with the model attributes for the view + */ + @RequestMapping(value="/owners/*/pets/{petId}/visits", method=RequestMethod.GET) + public ModelAndView visitsHandler(@PathVariable int petId) { + ModelAndView mav = new ModelAndView("visits"); + mav.addObject("visits", this.clinic.loadPet(petId).getVisits()); + return mav; + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/web/EditOwnerForm.java b/src/main/java/org/springframework/samples/petclinic/web/EditOwnerForm.java new file mode 100644 index 0000000000000000000000000000000000000000..0b65de51bf69e1ab01b225422ccd1769c0a56210 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/web/EditOwnerForm.java @@ -0,0 +1,65 @@ + +package org.springframework.samples.petclinic.web; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.samples.petclinic.Clinic; +import org.springframework.samples.petclinic.Owner; +import org.springframework.samples.petclinic.validation.OwnerValidator; +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.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; + +/** + * JavaBean Form controller that is used to edit an existing <code>Owner</code>. + * + * @author Juergen Hoeller + * @author Ken Krebs + * @author Arjen Poutsma + */ +@Controller +@RequestMapping("/owners/{ownerId}/edit") +@SessionAttributes(types = Owner.class) +public class EditOwnerForm { + + private final Clinic clinic; + + + @Autowired + public EditOwnerForm(Clinic clinic) { + this.clinic = clinic; + } + + @InitBinder + public void setAllowedFields(WebDataBinder dataBinder) { + dataBinder.setDisallowedFields("id"); + } + + @RequestMapping(method = RequestMethod.GET) + public String setupForm(@PathVariable("ownerId") int ownerId, Model model) { + Owner owner = this.clinic.loadOwner(ownerId); + model.addAttribute(owner); + return "owners/form"; + } + + @RequestMapping(method = RequestMethod.PUT) + public String processSubmit(@ModelAttribute Owner owner, BindingResult result, SessionStatus status) { + new OwnerValidator().validate(owner, result); + if (result.hasErrors()) { + return "owners/form"; + } + else { + this.clinic.storeOwner(owner); + status.setComplete(); + return "redirect:/owners/" + owner.getId(); + } + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/web/EditPetForm.java b/src/main/java/org/springframework/samples/petclinic/web/EditPetForm.java new file mode 100644 index 0000000000000000000000000000000000000000..1a7fd6ed3c624249e47d59896cb72545724c7aa4 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/web/EditPetForm.java @@ -0,0 +1,80 @@ + +package org.springframework.samples.petclinic.web; + +import java.util.Collection; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.samples.petclinic.Clinic; +import org.springframework.samples.petclinic.Pet; +import org.springframework.samples.petclinic.PetType; +import org.springframework.samples.petclinic.validation.PetValidator; +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.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; + +/** + * JavaBean Form controller that is used to edit an existing <code>Pet</code>. + * + * @author Juergen Hoeller + * @author Ken Krebs + * @author Arjen Poutsma + */ +@Controller +@RequestMapping("/owners/*/pets/{petId}/edit") +@SessionAttributes("pet") +public class EditPetForm { + + private final Clinic clinic; + + + @Autowired + public EditPetForm(Clinic clinic) { + this.clinic = clinic; + } + + @ModelAttribute("types") + public Collection<PetType> populatePetTypes() { + return this.clinic.getPetTypes(); + } + + @InitBinder + public void setAllowedFields(WebDataBinder dataBinder) { + dataBinder.setDisallowedFields("id"); + } + + @RequestMapping(method = RequestMethod.GET) + public String setupForm(@PathVariable("petId") int petId, Model model) { + Pet pet = this.clinic.loadPet(petId); + model.addAttribute("pet", pet); + return "pets/form"; + } + + @RequestMapping(method = { RequestMethod.PUT, RequestMethod.POST }) + public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result, SessionStatus status) { + new PetValidator().validate(pet, result); + if (result.hasErrors()) { + return "pets/form"; + } + else { + this.clinic.storePet(pet); + status.setComplete(); + return "redirect:/owners/" + pet.getOwner().getId(); + } + } + + @RequestMapping(method = RequestMethod.DELETE) + public String deletePet(@PathVariable int petId) { + Pet pet = this.clinic.loadPet(petId); + this.clinic.deletePet(petId); + return "redirect:/owners/" + pet.getOwner().getId(); + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/web/FindOwnersForm.java b/src/main/java/org/springframework/samples/petclinic/web/FindOwnersForm.java new file mode 100644 index 0000000000000000000000000000000000000000..eb93fabad0a44a970f14a62be79c2da92c58d56c --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/web/FindOwnersForm.java @@ -0,0 +1,74 @@ + +package org.springframework.samples.petclinic.web; + +import java.util.Collection; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.samples.petclinic.Clinic; +import org.springframework.samples.petclinic.Owner; +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.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +/** + * JavaBean Form controller that is used to search for <code>Owner</code>s by + * last name. + * + * @author Juergen Hoeller + * @author Ken Krebs + * @author Arjen Poutsma + */ +@Controller +public class FindOwnersForm { + + private final Clinic clinic; + + + @Autowired + public FindOwnersForm(Clinic clinic) { + this.clinic = clinic; + } + + @InitBinder + public void setAllowedFields(WebDataBinder dataBinder) { + dataBinder.setDisallowedFields("id"); + } + + @RequestMapping(value = "/owners/search", method = RequestMethod.GET) + public String setupForm(Model model) { + model.addAttribute("owner", new Owner()); + return "owners/search"; + } + + @RequestMapping(value = "/owners", method = RequestMethod.GET) + public String processSubmit(Owner owner, BindingResult result, Model 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<Owner> results = this.clinic.findOwners(owner.getLastName()); + if (results.size() < 1) { + // no owners found + result.rejectValue("lastName", "notFound", "not found"); + return "owners/search"; + } + if (results.size() > 1) { + // multiple owners found + model.addAttribute("selections", results); + return "owners/list"; + } + else { + // 1 owner found + owner = results.iterator().next(); + return "redirect:/owners/" + owner.getId(); + } + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/web/PetTypeEditor.java b/src/main/java/org/springframework/samples/petclinic/web/PetTypeEditor.java new file mode 100644 index 0000000000000000000000000000000000000000..812b648d7111087fe206bfa5ac3b9a0da31f3b10 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/web/PetTypeEditor.java @@ -0,0 +1,30 @@ +package org.springframework.samples.petclinic.web; + +import java.beans.PropertyEditorSupport; + +import org.springframework.samples.petclinic.Clinic; +import org.springframework.samples.petclinic.PetType; + +/** + * @author Mark Fisher + * @author Juergen Hoeller + */ +public class PetTypeEditor extends PropertyEditorSupport { + + private final Clinic clinic; + + + public PetTypeEditor(Clinic clinic) { + this.clinic = clinic; + } + + @Override + public void setAsText(String text) throws IllegalArgumentException { + for (PetType type : this.clinic.getPetTypes()) { + if (type.getName().equals(text)) { + setValue(type); + } + } + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/web/VisitsAtomView.java b/src/main/java/org/springframework/samples/petclinic/web/VisitsAtomView.java new file mode 100644 index 0000000000000000000000000000000000000000..e9da832e4d71c29227d70fd68f49795536ac3ed7 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/web/VisitsAtomView.java @@ -0,0 +1,82 @@ +/* + * Copyright 2002-2009 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.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.sun.syndication.feed.atom.Content; +import com.sun.syndication.feed.atom.Entry; +import com.sun.syndication.feed.atom.Feed; + +import org.springframework.samples.petclinic.Visit; +import org.springframework.web.servlet.view.feed.AbstractAtomFeedView; + +/** + * A view creating a Atom representation from a list of Visit objects. + * + * @author Alef Arendsen + * @author Arjen Poutsma + */ +public class VisitsAtomView extends AbstractAtomFeedView { + + @Override + protected void buildFeedMetadata(Map<String, Object> model, Feed feed, HttpServletRequest request) { + feed.setId("tag:springsource.com"); + feed.setTitle("Pet Clinic Visits"); + @SuppressWarnings("unchecked") + List<Visit> visits = (List<Visit>) model.get("visits"); + for (Visit visit : visits) { + Date date = visit.getDate(); + if (feed.getUpdated() == null || date.compareTo(feed.getUpdated()) > 0) { + feed.setUpdated(date); + } + } + } + + @Override + protected List<Entry> buildFeedEntries(Map<String, Object> model, + HttpServletRequest request, HttpServletResponse response) throws Exception { + + @SuppressWarnings("unchecked") + List<Visit> visits = (List<Visit>) model.get("visits"); + List<Entry> entries = new ArrayList<Entry>(visits.size()); + + for (Visit visit : visits) { + Entry entry = new Entry(); + String date = String.format("%1$tY-%1$tm-%1$td", visit.getDate()); + // see http://diveintomark.org/archives/2004/05/28/howto-atom-id#other + entry.setId(String.format("tag:springsource.com,%s:%d", date, visit.getId())); + entry.setTitle(String.format("%s visit on %s", visit.getPet().getName(), date)); + entry.setUpdated(visit.getDate()); + + Content summary = new Content(); + summary.setValue(visit.getDescription()); + entry.setSummary(summary); + + entries.add(entry); + } + + return entries; + + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/web/package-info.java b/src/main/java/org/springframework/samples/petclinic/web/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..c909ccf7f046b781d90619e48754247afe2fed18 --- /dev/null +++ b/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/src/main/java/overview.html b/src/main/java/overview.html new file mode 100644 index 0000000000000000000000000000000000000000..1eb7a2e8c194f8fac74dd8a8eabc4ccbe753d8d8 --- /dev/null +++ b/src/main/java/overview.html @@ -0,0 +1,7 @@ +<html> +<body> +<p> +The Spring Data Binding framework, an internal library used by Spring Web Flow. +</p> +</body> +</html> \ No newline at end of file diff --git a/src/main/resources/jdbc.properties b/src/main/resources/jdbc.properties new file mode 100644 index 0000000000000000000000000000000000000000..e039cdf1b6c9599c1bbf54a49de043f7425990b6 --- /dev/null +++ b/src/main/resources/jdbc.properties @@ -0,0 +1,63 @@ +# Properties file with JDBC and JPA settings. +# +# Applied by <context:property-placeholder location="jdbc.properties"/> from +# various application context XML files (e.g., "applicationContext-*.xml"). +# Targeted at system administrators, to avoid touching the context XML files. + + +#------------------------------------------------------------------------------- +# Common Settings + +hibernate.generate_statistics=true +hibernate.show_sql=true +jpa.showSql=true + + +#------------------------------------------------------------------------------- +# 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.populate=true +jdbc.schemaLocation=classpath:/META-INF/hsqldb/initDB.txt +jdbc.dataLocation=classpath:/META-INF/hsqldb/populateDB.txt +jdbc.dropLocation=classpath:/META-INF/hsqldb/dropTables.txt + +# Property that determines which Hibernate dialect to use +# (only applied with "applicationContext-hibernate.xml") +hibernate.dialect=org.hibernate.dialect.HSQLDialect + +# Property that determines which JPA DatabasePlatform to use with TopLink Essentials +jpa.databasePlatform=org.springframework.samples.petclinic.toplink.EssentialsHSQLPlatformWithNativeSequence + +# Property that determines which database to use with an AbstractJpaVendorAdapter +jpa.database=HSQL + + +#------------------------------------------------------------------------------- +# MySQL Settings + +#jdbc.driverClassName=com.mysql.jdbc.Driver +#jdbc.url=jdbc:mysql://localhost:3306/petclinic +#jdbc.username=pc +#jdbc.password=pc + +# Properties that control the population of schema and data for a new data source +#jdbc.populate=false +#jdbc.schemaLocation= +#jdbc.dataLocation= +#jdbc.dropLocation= + +# Property that determines which Hibernate dialect to use +# (only applied with "applicationContext-hibernate.xml") +#hibernate.dialect=org.hibernate.dialect.MySQLDialect + +# Property that determines which JPA DatabasePlatform to use with TopLink Essentials +#jpa.databasePlatform=oracle.toplink.essentials.platform.database.MySQL4Platform + +# Property that determines which database to use with an AbstractJpaVendorAdapter +#jpa.database=MYSQL diff --git a/src/main/resources/log4j.properties b/src/main/resources/log4j.properties new file mode 100644 index 0000000000000000000000000000000000000000..ebee551aa88864fb8a3ae65dcd1ebe88b66ac4ba --- /dev/null +++ b/src/main/resources/log4j.properties @@ -0,0 +1,18 @@ +# For JBoss: Avoid to setup Log4J outside $JBOSS_HOME/server/default/deploy/log4j.xml! +# For all other servers: Comment out the Log4J listener in web.xml to activate Log4J. +log4j.rootLogger=INFO, stdout, logfile + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n + +log4j.appender.logfile=org.apache.log4j.RollingFileAppender +log4j.appender.logfile.File=${petclinic.root}/WEB-INF/petclinic.log +log4j.appender.logfile.MaxFileSize=512KB +# Keep three backup files. +log4j.appender.logfile.MaxBackupIndex=3 +# Pattern to output: date priority [category] - message +log4j.appender.logfile.layout=org.apache.log4j.PatternLayout +log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n + +log4j.logger.org.springframework.samples.petclinic.aspects=DEBUG diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties new file mode 100644 index 0000000000000000000000000000000000000000..173417a1014824791ee97decfc43084273fd0723 --- /dev/null +++ b/src/main/resources/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/src/main/resources/messages_de.properties b/src/main/resources/messages_de.properties new file mode 100644 index 0000000000000000000000000000000000000000..124bee48b2c863f62b80367890392fb0775efcfd --- /dev/null +++ b/src/main/resources/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=ungültiges Datum +typeMismatch.birthDate=ungültiges Datum diff --git a/src/main/resources/messages_en.properties b/src/main/resources/messages_en.properties new file mode 100644 index 0000000000000000000000000000000000000000..05d519bb86d059e73ce879196658c54c1e8cd7dc --- /dev/null +++ b/src/main/resources/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/src/main/resources/petclinic.hbm.xml b/src/main/resources/petclinic.hbm.xml new file mode 100644 index 0000000000000000000000000000000000000000..f9a993cf1212fac6075a393b3760273f085a2158 --- /dev/null +++ b/src/main/resources/petclinic.hbm.xml @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN" + "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> + +<!-- + - Mapping file for the Hibernate implementation of the Clinic interface. + --> +<hibernate-mapping auto-import="true" default-lazy="false"> + + <class name="org.springframework.samples.petclinic.Vet" table="vets"> + <id name="id" column="id"> + <generator class="identity"/> + </id> + <property name="firstName" column="first_name"/> + <property name="lastName" column="last_name"/> + <set name="specialtiesInternal" table="vet_specialties"> + <key column="vet_id"/> + <many-to-many column="specialty_id" class="org.springframework.samples.petclinic.Specialty"/> + </set> + </class> + + <class name="org.springframework.samples.petclinic.Specialty" table="specialties"> + <id name="id" column="id"> + <generator class="identity"/> + </id> + <property name="name" column="name"/> + </class> + + <class name="org.springframework.samples.petclinic.Owner" table="owners"> + <id name="id" column="id"> + <generator class="identity"/> + </id> + <property name="firstName" column="first_name"/> + <property name="lastName" column="last_name"/> + <property name="address" column="address"/> + <property name="city" column="city"/> + <property name="telephone" column="telephone"/> + <set name="petsInternal" inverse="true" cascade="all"> + <key column="owner_id"/> + <one-to-many class="org.springframework.samples.petclinic.Pet"/> + </set> + </class> + + <class name="org.springframework.samples.petclinic.Pet" table="pets"> + <id name="id" column="id"> + <generator class="identity"/> + </id> + <property name="name" column="name"/> + <property name="birthDate" column="birth_date" type="date"/> + <many-to-one name="owner" column="owner_id" class="org.springframework.samples.petclinic.Owner"/> + <many-to-one name="type" column="type_id" class="org.springframework.samples.petclinic.PetType"/> + <set name="visitsInternal" inverse="true" cascade="all"> + <key column="pet_id"/> + <one-to-many class="org.springframework.samples.petclinic.Visit"/> + </set> + </class> + + <class name="org.springframework.samples.petclinic.PetType" table="types"> + <id name="id" column="id"> + <generator class="identity"/> + </id> + <property name="name" column="name"/> + </class> + + <class name="org.springframework.samples.petclinic.Visit" table="visits"> + <id name="id" column="id"> + <generator class="identity"/> + </id> + <property name="date" column="visit_date" type="date"/> + <property name="description" column="description"/> + <many-to-one name="pet" column="pet_id" class="org.springframework.samples.petclinic.Pet"/> + </class> + +</hibernate-mapping> diff --git a/src/main/webapp/META-INF/aop.xml b/src/main/webapp/META-INF/aop.xml new file mode 100644 index 0000000000000000000000000000000000000000..b49ffd8b19a86e9cca472aeb1f4da133e2a0ccf9 --- /dev/null +++ b/src/main/webapp/META-INF/aop.xml @@ -0,0 +1,18 @@ +<?xml version="1.0"?> + +<!-- Custom aspects for the PetClinic sample application --> +<aspectj> + + <weaver> + <include within="org.springframework.samples.petclinic..*"/> + </weaver> + + <aspects> + <aspect name="org.springframework.samples.petclinic.aspects.UsageLogAspect"/> + <concrete-aspect name="org.springframework.samples.petclinic.aspects.ApplicationTraceAspect" + extends="org.springframework.samples.petclinic.aspects.AbstractTraceAspect"> + <pointcut name="traced" expression="execution(* org.springframework.samples..*.*(..))"/> + </concrete-aspect> + </aspects> + +</aspectj> diff --git a/src/main/webapp/META-INF/context.xml b/src/main/webapp/META-INF/context.xml new file mode 100644 index 0000000000000000000000000000000000000000..d5deabaf701f03c27f6346e0cc375876778aefc0 --- /dev/null +++ b/src/main/webapp/META-INF/context.xml @@ -0,0 +1,7 @@ +<!-- Tomcat context descriptor used for specifying a custom ClassLoader --> +<Context path="/petclinic" reloadable="false"> + <!-- please note that useSystemClassLoaderAsParent is available since Tomcat 5.5.20 / remove if previous versions are being used --> + <!-- + <Loader loaderClass="org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader" useSystemClassLoaderAsParent="false"/> + --> +</Context> diff --git a/src/main/webapp/META-INF/hsqldb/dropTables.txt b/src/main/webapp/META-INF/hsqldb/dropTables.txt new file mode 100644 index 0000000000000000000000000000000000000000..90ae6329f90859d5945753ba28441d489b0384f7 --- /dev/null +++ b/src/main/webapp/META-INF/hsqldb/dropTables.txt @@ -0,0 +1,7 @@ +DROP TABLE visits; +DROP TABLE pets; +DROP TABLE owners; +DROP TABLE types; +DROP TABLE vet_specialties; +DROP TABLE specialties; +DROP TABLE vets; diff --git a/src/main/webapp/META-INF/hsqldb/initDB.txt b/src/main/webapp/META-INF/hsqldb/initDB.txt new file mode 100644 index 0000000000000000000000000000000000000000..a75bfbbd86da3da4e60d6fbd5ed565837453b657 --- /dev/null +++ b/src/main/webapp/META-INF/hsqldb/initDB.txt @@ -0,0 +1,55 @@ +CREATE TABLE vets ( + id INTEGER NOT NULL 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 NOT NULL 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 NOT NULL IDENTITY PRIMARY KEY, + name VARCHAR(80) +); +CREATE INDEX types_name ON types(name); + +CREATE TABLE owners ( + id INTEGER NOT NULL 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 NOT NULL 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 NOT NULL 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/src/main/webapp/META-INF/hsqldb/populateDB.txt b/src/main/webapp/META-INF/hsqldb/populateDB.txt new file mode 100644 index 0000000000000000000000000000000000000000..1bf0c4a6edb05ac0f8bb2382d19367d8b15e85cf --- /dev/null +++ b/src/main/webapp/META-INF/hsqldb/populateDB.txt @@ -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', '2000-09-07', 1, 1); +INSERT INTO pets VALUES (2, 'Basil', '2002-08-06', 6, 2); +INSERT INTO pets VALUES (3, 'Rosy', '2001-04-17', 2, 3); +INSERT INTO pets VALUES (4, 'Jewel', '2000-03-07', 2, 3); +INSERT INTO pets VALUES (5, 'Iggy', '2000-11-30', 3, 4); +INSERT INTO pets VALUES (6, 'George', '2000-01-20', 4, 5); +INSERT INTO pets VALUES (7, 'Samantha', '1995-09-04', 1, 6); +INSERT INTO pets VALUES (8, 'Max', '1995-09-04', 1, 6); +INSERT INTO pets VALUES (9, 'Lucky', '1999-08-06', 5, 7); +INSERT INTO pets VALUES (10, 'Mulligan', '1997-02-24', 2, 8); +INSERT INTO pets VALUES (11, 'Freddy', '2000-03-09', 5, 9); +INSERT INTO pets VALUES (12, 'Lucky', '2000-06-24', 2, 10); +INSERT INTO pets VALUES (13, 'Sly', '2002-06-08', 1, 10); + +INSERT INTO visits VALUES (1, 7, '1996-03-04', 'rabies shot'); +INSERT INTO visits VALUES (2, 8, '1996-03-04', 'rabies shot'); +INSERT INTO visits VALUES (3, 8, '1996-06-04', 'neutered'); +INSERT INTO visits VALUES (4, 7, '1996-09-04', 'spayed'); diff --git a/src/main/webapp/META-INF/orm.xml b/src/main/webapp/META-INF/orm.xml new file mode 100644 index 0000000000000000000000000000000000000000..d7c8f7040a13f121824717b763d879068e24233c --- /dev/null +++ b/src/main/webapp/META-INF/orm.xml @@ -0,0 +1,122 @@ +<?xml version="1.0" encoding="UTF-8"?> +<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_1_0.xsd" + version="1.0"> + + <persistence-unit-metadata> + <xml-mapping-metadata-complete/> + <persistence-unit-defaults> + <access>PROPERTY</access> + </persistence-unit-defaults> + </persistence-unit-metadata> + + <package>org.springframework.samples.petclinic</package> + + <mapped-superclass class="BaseEntity"> + <attributes> + <id name="id"> + <generated-value strategy="IDENTITY"/> + </id> + <transient name="new"/> + </attributes> + </mapped-superclass> + + <mapped-superclass class="NamedEntity"> + <attributes> + <basic name="name"> + <column name="NAME"/> + </basic> + </attributes> + </mapped-superclass> + + <mapped-superclass class="Person"> + <attributes> + <basic name="firstName"> + <column name="FIRST_NAME"/> + </basic> + <basic name="lastName"> + <column name="LAST_NAME"/> + </basic> + </attributes> + </mapped-superclass> + + <entity class="Vet"> + <table name="VETS"/> + <attributes> + <many-to-many name="specialtiesInternal" target-entity="Specialty" fetch="EAGER"> + <join-table name="VET_SPECIALTIES"> + <join-column name="VET_ID"/> + <inverse-join-column name="SPECIALTY_ID"/> + </join-table> + </many-to-many> + <transient name="specialties"/> + <transient name="nrOfSpecialties"/> + </attributes> + </entity> + + <entity class="Specialty"> + <table name="SPECIALTIES"/> + </entity> + + <entity class="Owner"> + <table name="OWNERS"/> + <attributes> + <basic name="address"/> + <basic name="city"/> + <basic name="telephone"/> + <one-to-many name="petsInternal" target-entity="Pet" mapped-by="owner" fetch="EAGER"> + <cascade> + <cascade-all/> + </cascade> + </one-to-many> + <transient name="pets"/> + </attributes> + </entity> + + <entity class="Pet"> + <table name="PETS"/> + <attributes> + <basic name="birthDate"> + <column name="BIRTH_DATE"/> + <temporal>DATE</temporal> + </basic> + <many-to-one name="owner" fetch="EAGER"> + <cascade> + <cascade-all/> + </cascade> + </many-to-one> + <many-to-one name="type" fetch="EAGER"> + <cascade> + <cascade-all/> + </cascade> + </many-to-one> + <one-to-many name="visitsInternal" target-entity="Visit" mapped-by="pet" fetch="EAGER"> + <cascade> + <cascade-all/> + </cascade> + </one-to-many> + <transient name="visits"/> + </attributes> + </entity> + + <entity class="PetType"> + <table name="TYPES"/> + </entity> + + <entity class="Visit"> + <table name="VISITS"/> + <attributes> + <basic name="date"> + <column name="VISIT_DATE"/> + <temporal>DATE</temporal> + </basic> + <many-to-one name="pet" fetch="EAGER"> + <cascade> + <cascade-all/> + </cascade> + </many-to-one> + </attributes> + </entity> + +</entity-mappings> diff --git a/src/main/webapp/META-INF/persistence.xml b/src/main/webapp/META-INF/persistence.xml new file mode 100644 index 0000000000000000000000000000000000000000..86bf7c115d83338fa6cbe49e8cb07be48b3dfb94 --- /dev/null +++ b/src/main/webapp/META-INF/persistence.xml @@ -0,0 +1,16 @@ +<persistence xmlns="http://java.sun.com/xml/ns/persistence" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" + version="1.0"> + + <persistence-unit name="PetClinic" transaction-type="RESOURCE_LOCAL"> + + <!-- Explicitly define mapping file path, else Hibernate won't find the default --> + <mapping-file>META-INF/orm.xml</mapping-file> + + <!-- Prevent annotation scanning. In this app we are purely driven by orm.xml --> + <exclude-unlisted-classes>true</exclude-unlisted-classes> + + </persistence-unit> + +</persistence> diff --git a/src/main/webapp/WEB-INF/applicationContext-hibernate.xml b/src/main/webapp/WEB-INF/applicationContext-hibernate.xml new file mode 100644 index 0000000000000000000000000000000000000000..bfc166db6253d6a10eed9680f488b8adb2b15285 --- /dev/null +++ b/src/main/webapp/WEB-INF/applicationContext-hibernate.xml @@ -0,0 +1,89 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Application context definition for PetClinic on Hibernate. +--> +<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" + xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" + xsi:schemaLocation=" + http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd + http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.5.xsd + http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> + + <!-- ========================= RESOURCE DEFINITIONS ========================= --> + + <!-- Configurer that replaces ${...} placeholders with values from a properties file --> + <!-- (in this case, JDBC-related settings for the dataSource definition below) --> + <context:property-placeholder location="classpath:jdbc.properties"/> + + <!-- + Uses Apache Commons DBCP for connection pooling. See Commons DBCP documentation + for the required JAR files. Alternatively you can use another connection pool + such as C3P0, similarly configured using Spring. + --> + <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" + p:driverClassName="${jdbc.driverClassName}" p:url="${jdbc.url}" p:username="${jdbc.username}" + p:password="${jdbc.password}"/> + + <!-- JNDI DataSource for JEE environments --> + <!-- + <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/petclinic"/> + --> + + <!-- Hibernate SessionFactory --> + <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean" + p:dataSource-ref="dataSource" p:mappingResources="petclinic.hbm.xml"> + <property name="hibernateProperties"> + <props> + <prop key="hibernate.dialect">${hibernate.dialect}</prop> + <prop key="hibernate.show_sql">${hibernate.show_sql}</prop> + <prop key="hibernate.generate_statistics">${hibernate.generate_statistics}</prop> + </props> + </property> + <property name="eventListeners"> + <map> + <entry key="merge"> + <bean class="org.springframework.orm.hibernate3.support.IdTransferringMergeEventListener"/> + </entry> + </map> + </property> + </bean> + + <!-- Transaction manager for a single Hibernate SessionFactory (alternative to JTA) --> + <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager" + p:sessionFactory-ref="sessionFactory"/> + + <!-- Transaction manager that delegates to JTA (for a transactional JNDI DataSource) --> + <!-- + <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"/> + --> + + + <!-- ========================= BUSINESS OBJECT DEFINITIONS ========================= --> + + <!-- + Activates various annotations to be detected in bean classes: + Spring's @Required and @Autowired, as well as JSR 250's @Resource. + --> + <context:annotation-config/> + + <!-- + Instruct Spring to perform declarative transaction management + automatically on annotated classes. + --> + <tx:annotation-driven/> + + <!-- + Exporter that exposes the Hibernate statistics service via JMX. Autodetects the + service MBean, using its bean name as JMX object name. + --> + <context:mbean-export/> + + <!-- PetClinic's central data access object: Hibernate implementation --> + <bean id="clinic" class="org.springframework.samples.petclinic.hibernate.HibernateClinic"/> + + <!-- Hibernate's JMX statistics service --> + <bean name="petclinic:type=HibernateStatistics" class="org.hibernate.jmx.StatisticsService" autowire="byName"/> + +</beans> diff --git a/src/main/webapp/WEB-INF/applicationContext-jdbc.xml b/src/main/webapp/WEB-INF/applicationContext-jdbc.xml new file mode 100644 index 0000000000000000000000000000000000000000..69a2b1f5dcc7f75389f272eb4bc25ede55ba02ff --- /dev/null +++ b/src/main/webapp/WEB-INF/applicationContext-jdbc.xml @@ -0,0 +1,85 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Application context definition for PetClinic on JDBC. +--> +<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" + xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" + xmlns:tx="http://www.springframework.org/schema/tx" + xsi:schemaLocation=" + http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd + http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd + http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.5.xsd + http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> + + <!-- ========================= RESOURCE DEFINITIONS ========================= --> + + <!-- Configurer that replaces ${...} placeholders with values from a properties file --> + <!-- (in this case, JDBC-related settings for the dataSource definition below) --> + <context:property-placeholder location="classpath:jdbc.properties"/> + + <!-- + Spring FactoryBean that creates a DataSource using Apache Commons DBCP for connection + pooling. See Commons DBCP documentation for the required JAR files. This factory bean + can populate the data source with a schema and data scripts if configured to do so. + + An alternate factory bean can be created for different connection pool implementations, + C3P0 for example. + --> + <bean id="dataSource" class="org.springframework.samples.petclinic.config.DbcpDataSourceFactory" + p:driverClassName="${jdbc.driverClassName}" p:url="${jdbc.url}" + p:username="${jdbc.username}" p:password="${jdbc.password}" p:populate="${jdbc.populate}" + p:schemaLocation="${jdbc.schemaLocation}" p:dataLocation="${jdbc.dataLocation}"/> + + <!-- JNDI DataSource for JEE environments --> + <!-- + <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/petclinic"/> + --> + + <!-- Transaction manager for a single JDBC DataSource (alternative to JTA) --> + <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" + p:dataSource-ref="dataSource"/> + + <!-- Transaction manager that delegates to JTA (for a transactional JNDI DataSource) --> + <!-- + <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"/> + --> + + + <!-- ========================= BUSINESS OBJECT DEFINITIONS ========================= --> + + <!-- + Activates various annotations to be detected in bean classes: Spring's + @Required and @Autowired, as well as JSR 250's @PostConstruct, + @PreDestroy and @Resource (if available) and JPA's @PersistenceContext + and @PersistenceUnit (if available). + --> + <context:annotation-config/> + + <!-- + Instruct Spring to retrieve and apply @AspectJ aspects which are defined + as beans in this context (such as the CallMonitoringAspect below). + --> + <aop:aspectj-autoproxy/> + + <!-- + Instruct Spring to perform automatic transaction management on annotated classes. + The SimpleJdbcClinic implementation declares @Transactional annotations. + "proxy-target-class" is set because of SimpleJdbcClinic's @ManagedOperation usage. + --> + <tx:annotation-driven/> + + <!-- + Exporter that exposes the Clinic DAO and the CallMonitoringAspect via JMX, + based on the @ManagedResource, @ManagedAttribute, and @ManagedOperation annotations. + --> + <context:mbean-export/> + + <!-- PetClinic's central data access object using Spring's SimpleJdbcTemplate --> + <bean id="clinic" class="org.springframework.samples.petclinic.jdbc.SimpleJdbcClinic"/> + + <!-- Call monitoring aspect that monitors call count and call invocation time --> + <bean id="callMonitor" class="org.springframework.samples.petclinic.aspects.CallMonitoringAspect"/> + +</beans> diff --git a/src/main/webapp/WEB-INF/applicationContext-jpa.xml b/src/main/webapp/WEB-INF/applicationContext-jpa.xml new file mode 100644 index 0000000000000000000000000000000000000000..25374e9abe2cf717be38eb4dae3a3ec95b852175 --- /dev/null +++ b/src/main/webapp/WEB-INF/applicationContext-jpa.xml @@ -0,0 +1,101 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Application context definition for PetClinic on JPA. +--> +<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" + xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" + xmlns:tx="http://www.springframework.org/schema/tx" + xsi:schemaLocation=" + http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd + http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd + http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.5.xsd + http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> + + <!-- ========================= RESOURCE DEFINITIONS ========================= --> + + <!-- + Activates a load-time weaver for the context. Any bean within the context that + implements LoadTimeWeaverAware (such as LocalContainerEntityManagerFactoryBean) + will receive a reference to the autodetected load-time weaver. + --> + <context:load-time-weaver/> + + <!-- Configurer that replaces ${...} placeholders with values from a properties file --> + <!-- (in this case, JDBC-related settings for the dataSource definition below) --> + <context:property-placeholder location="classpath:jdbc.properties"/> + + <!-- + Uses Apache Commons DBCP for connection pooling. See Commons DBCP documentation + for the required JAR files. Alternatively you can use another connection pool + such as C3P0, similarly configured using Spring. + --> + <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" + p:driverClassName="${jdbc.driverClassName}" p:url="${jdbc.url}" p:username="${jdbc.username}" + p:password="${jdbc.password}"/> + + <!-- JNDI DataSource for JEE environments --> + <!-- + <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/petclinic"/> + --> + + <!-- JPA EntityManagerFactory --> + <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" + p:dataSource-ref="dataSource"> + <property name="jpaVendorAdapter"> + <bean class="org.springframework.orm.jpa.vendor.TopLinkJpaVendorAdapter" + p:databasePlatform="${jpa.databasePlatform}" p:showSql="${jpa.showSql}"/> + <!-- + <bean class="org.springframework.orm.jpa.vendor.OpenJpaVendorAdapter" + p:database="${jpa.database}" p:showSql="${jpa.showSql}"/> + --> + <!-- + <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" + p:database="${jpa.database}" p:showSql="${jpa.showSql}"/> + --> + </property> + </bean> + + <!-- Transaction manager for a single JPA EntityManagerFactory (alternative to JTA) --> + <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager" + p:entityManagerFactory-ref="entityManagerFactory"/> + + + <!-- ========================= BUSINESS OBJECT DEFINITIONS ========================= --> + + <!-- + Activates various annotations to be detected in bean classes: Spring's + @Required and @Autowired, as well as JSR 250's @PostConstruct, + @PreDestroy and @Resource (if available) and JPA's @PersistenceContext + and @PersistenceUnit (if available). + --> + <context:annotation-config/> + + <!-- + Instruct Spring to perform declarative transaction management + automatically on annotated classes. + --> + <tx:annotation-driven mode="aspectj"/> + + <!-- + Simply defining this bean will cause requests to owner names to be saved. + This aspect is defined in petclinic.jar's META-INF/aop.xml file. + Note that we can dependency inject this bean like any other bean. + --> + <bean class="org.springframework.samples.petclinic.aspects.UsageLogAspect" p:historySize="300"/> + + <!-- + Post-processor to perform exception translation on @Repository classes (from native + exceptions such as JPA PersistenceExceptions to Spring's DataAccessException hierarchy). + --> + <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/> + + <!-- + Will automatically be transactional due to @Transactional. + EntityManager will be auto-injected due to @PersistenceContext. + PersistenceExceptions will be auto-translated due to @Repository. + --> + <bean id="clinic" class="org.springframework.samples.petclinic.jpa.EntityManagerClinic"/> + +</beans> diff --git a/src/main/webapp/WEB-INF/classes/log4j.properties b/src/main/webapp/WEB-INF/classes/log4j.properties new file mode 100644 index 0000000000000000000000000000000000000000..ebee551aa88864fb8a3ae65dcd1ebe88b66ac4ba --- /dev/null +++ b/src/main/webapp/WEB-INF/classes/log4j.properties @@ -0,0 +1,18 @@ +# For JBoss: Avoid to setup Log4J outside $JBOSS_HOME/server/default/deploy/log4j.xml! +# For all other servers: Comment out the Log4J listener in web.xml to activate Log4J. +log4j.rootLogger=INFO, stdout, logfile + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n + +log4j.appender.logfile=org.apache.log4j.RollingFileAppender +log4j.appender.logfile.File=${petclinic.root}/WEB-INF/petclinic.log +log4j.appender.logfile.MaxFileSize=512KB +# Keep three backup files. +log4j.appender.logfile.MaxBackupIndex=3 +# Pattern to output: date priority [category] - message +log4j.appender.logfile.layout=org.apache.log4j.PatternLayout +log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n + +log4j.logger.org.springframework.samples.petclinic.aspects=DEBUG diff --git a/src/main/webapp/WEB-INF/classes/messages.properties b/src/main/webapp/WEB-INF/classes/messages.properties new file mode 100644 index 0000000000000000000000000000000000000000..173417a1014824791ee97decfc43084273fd0723 --- /dev/null +++ b/src/main/webapp/WEB-INF/classes/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/src/main/webapp/WEB-INF/classes/messages_de.properties b/src/main/webapp/WEB-INF/classes/messages_de.properties new file mode 100644 index 0000000000000000000000000000000000000000..124bee48b2c863f62b80367890392fb0775efcfd --- /dev/null +++ b/src/main/webapp/WEB-INF/classes/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=ungültiges Datum +typeMismatch.birthDate=ungültiges Datum diff --git a/src/main/webapp/WEB-INF/classes/messages_en.properties b/src/main/webapp/WEB-INF/classes/messages_en.properties new file mode 100644 index 0000000000000000000000000000000000000000..05d519bb86d059e73ce879196658c54c1e8cd7dc --- /dev/null +++ b/src/main/webapp/WEB-INF/classes/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/src/main/webapp/WEB-INF/geronimo-web.xml b/src/main/webapp/WEB-INF/geronimo-web.xml new file mode 100644 index 0000000000000000000000000000000000000000..b5d88e1ecd2fa8ea9da8d02f63c2fed8bfedde14 --- /dev/null +++ b/src/main/webapp/WEB-INF/geronimo-web.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<web-app xmlns="http://geronimo.apache.org/xml/ns/j2ee/web-1.0" + configId="org/springframework/samples/petclinic"> + <context-root>/petclinic</context-root> + <context-priority-classloader>true</context-priority-classloader> +</web-app> \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/dataAccessFailure.jsp b/src/main/webapp/WEB-INF/jsp/dataAccessFailure.jsp new file mode 100644 index 0000000000000000000000000000000000000000..256cca17786ac11c8add7aa544301df6183c52e6 --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/dataAccessFailure.jsp @@ -0,0 +1,19 @@ +<%@ include file="/WEB-INF/jsp/includes.jsp" %> +<%@ include file="/WEB-INF/jsp/header.jsp" %> + +<% +Exception ex = (Exception) request.getAttribute("exception"); +%> + +<h2>Data access failure: <%= ex.getMessage() %></h2> +<p/> + +<% +ex.printStackTrace(new java.io.PrintWriter(out)); +%> + +<p/> +<br/> +<a href="<spring:url value="/" htmlEscape="true" />">Home</a> + +<%@ include file="/WEB-INF/jsp/footer.jsp" %> diff --git a/src/main/webapp/WEB-INF/jsp/footer.jsp b/src/main/webapp/WEB-INF/jsp/footer.jsp new file mode 100644 index 0000000000000000000000000000000000000000..52aaffc47e73c0aabb743f735c8670161140cd1a --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/footer.jsp @@ -0,0 +1,12 @@ + + <table class="footer"> + <tr> + <td><a href="<spring:url value="/" htmlEscape="true" />">Home</a></td> + <td align="right"><img src="<spring:url value="/static/images/springsource-logo.png" htmlEscape="true" />" alt="Sponsored by SpringSource"/></td> + </tr> + </table> + + </div> +</body> + +</html> diff --git a/src/main/webapp/WEB-INF/jsp/header.jsp b/src/main/webapp/WEB-INF/jsp/header.jsp new file mode 100644 index 0000000000000000000000000000000000000000..49393d364c9bb15d9a84810bc78a4e60a8743dcf --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/header.jsp @@ -0,0 +1,14 @@ +<!-- + PetClinic :: a Spring Framework demonstration +--> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> + +<head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="stylesheet" href="<spring:url value="/static/styles/petclinic.css" htmlEscape="true" />" type="text/css"/> + <title>PetClinic :: a Spring Framework demonstration</title> +</head> + +<body> + + <div id="main"> diff --git a/src/main/webapp/WEB-INF/jsp/includes.jsp b/src/main/webapp/WEB-INF/jsp/includes.jsp new file mode 100644 index 0000000000000000000000000000000000000000..96c3e304cdb3e96bd330210972c3701fd92abf73 --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/includes.jsp @@ -0,0 +1,5 @@ +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> +<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> +<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> diff --git a/src/main/webapp/WEB-INF/jsp/owners/form.jsp b/src/main/webapp/WEB-INF/jsp/owners/form.jsp new file mode 100644 index 0000000000000000000000000000000000000000..1670a7c1bf529be6b009e304e11ce2c731ef337d --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/owners/form.jsp @@ -0,0 +1,61 @@ +<%@ include file="/WEB-INF/jsp/includes.jsp" %> +<%@ include file="/WEB-INF/jsp/header.jsp" %> +<c:choose> + <c:when test="${owner.new}"><c:set var="method" value="post"/></c:when> + <c:otherwise><c:set var="method" value="put"/></c:otherwise> +</c:choose> + +<h2><c:if test="${owner.new}">New </c:if>Owner:</h2> +<form:form modelAttribute="owner" method="${method}"> + <table> + <tr> + <th> + First Name: <form:errors path="firstName" cssClass="errors"/> + <br/> + <form:input path="firstName" size="30" maxlength="80"/> + </th> + </tr> + <tr> + <th> + Last Name: <form:errors path="lastName" cssClass="errors"/> + <br/> + <form:input path="lastName" size="30" maxlength="80"/> + </th> + </tr> + <tr> + <th> + Address: <form:errors path="address" cssClass="errors"/> + <br/> + <form:input path="address" size="30" maxlength="80"/> + </th> + </tr> + <tr> + <th> + City: <form:errors path="city" cssClass="errors"/> + <br/> + <form:input path="city" size="30" maxlength="80"/> + </th> + </tr> + <tr> + <th> + Telephone: <form:errors path="telephone" cssClass="errors"/> + <br/> + <form:input path="telephone" size="20" maxlength="20"/> + </th> + </tr> + <tr> + <td> + <c:choose> + <c:when test="${owner.new}"> + <p class="submit"><input type="submit" value="Add Owner"/></p> + </c:when> + <c:otherwise> + <p class="submit"><input type="submit" value="Update Owner"/></p> + </c:otherwise> + </c:choose> + </td> + </tr> + </table> +</form:form> + +<%@ include file="/WEB-INF/jsp/footer.jsp" %> diff --git a/src/main/webapp/WEB-INF/jsp/owners/list.jsp b/src/main/webapp/WEB-INF/jsp/owners/list.jsp new file mode 100644 index 0000000000000000000000000000000000000000..44fc3cac0b8631960ce76cd1a9cc05d9faf87ed3 --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/owners/list.jsp @@ -0,0 +1,34 @@ +<%@ include file="/WEB-INF/jsp/includes.jsp" %> +<%@ include file="/WEB-INF/jsp/header.jsp" %> + +<h2>Owners:</h2> + +<table> + <thead> + <th>Name</th> + <th>Address</th> + <th>City</th> + <th>Telephone</th> + <th>Pets</th> + </thead> + <c:forEach var="owner" items="${selections}"> + <tr> + <td> + <spring:url value="owners/{ownerId}" var="ownerUrl"> + <spring:param name="ownerId" value="${owner.id}"/> + </spring:url> + <a href="${fn:escapeXml(ownerUrl)}">${owner.firstName} ${owner.lastName}</a> + </td> + <td>${owner.address}</td> + <td>${owner.city}</td> + <td>${owner.telephone}</td> + <td> + <c:forEach var="pet" items="${owner.pets}"> + ${pet.name} + </c:forEach> + </td> + </tr> + </c:forEach> +</table> + +<%@ include file="/WEB-INF/jsp/footer.jsp" %> diff --git a/src/main/webapp/WEB-INF/jsp/owners/search.jsp b/src/main/webapp/WEB-INF/jsp/owners/search.jsp new file mode 100644 index 0000000000000000000000000000000000000000..b972390a19fabba33e45845b11733c416de7e38f --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/owners/search.jsp @@ -0,0 +1,26 @@ +<%@ include file="/WEB-INF/jsp/includes.jsp" %> +<%@ include file="/WEB-INF/jsp/header.jsp" %> + + +<h2>Find Owners:</h2> + +<spring:url value="/owners" var="formUrl"/> +<form:form modelAttribute="owner" action="${fn:escapeXml(formUrl)}" method="get"> + <table> + <tr> + <th> + Last Name: <form:errors path="*" cssClass="errors"/> + <br/> + <form:input path="lastName" size="30" maxlength="80" /> + </th> + </tr> + <tr> + <td><p class="submit"><input type="submit" value="Find Owners"/></p></td> + </tr> + </table> +</form:form> + +<br/> +<a href='<spring:url value="/owners/new" htmlEscape="true"/>'>Add Owner</a> + +<%@ include file="/WEB-INF/jsp/footer.jsp" %> diff --git a/src/main/webapp/WEB-INF/jsp/owners/show.jsp b/src/main/webapp/WEB-INF/jsp/owners/show.jsp new file mode 100644 index 0000000000000000000000000000000000000000..9767c18ae17fdef936aea8ac237622c449c0fca8 --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/owners/show.jsp @@ -0,0 +1,108 @@ +<%@ include file="/WEB-INF/jsp/includes.jsp" %> +<%@ include file="/WEB-INF/jsp/header.jsp" %> +<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> + +<h2>Owner Information</h2> + + <table> + <tr> + <th>Name</th> + <td><b>${owner.firstName} ${owner.lastName}</b></td> + </tr> + <tr> + <th>Address</th> + <td>${owner.address}</td> + </tr> + <tr> + <th>City</th> + <td>${owner.city}</td> + </tr> + <tr> + <th>Telephone </th> + <td>${owner.telephone}</td> + </tr> + </table> + <table class="table-buttons"> + <tr> + <td colspan="2" align="center"> + <spring:url value="{ownerId}/edit" var="editUrl"> + <spring:param name="ownerId" value="${owner.id}" /> + </spring:url> + <a href="${fn:escapeXml(editUrl)}">Edit Owner</a> + </td> + <td> + <spring:url value="{ownerId}/pets/new" var="addUrl"> + <spring:param name="ownerId" value="${owner.id}" /> + </spring:url> + <a href="${fn:escapeXml(addUrl)}">Add New Pet</a> + </td> + </tr> + </table> + + <h2>Pets and Visits</h2> + + <c:forEach var="pet" items="${owner.pets}"> + <table width="94%"> + <tr> + <td valign="top"> + <table> + <tr> + <th>Name</th> + <td><b>${pet.name}</b></td> + </tr> + <tr> + <th>Birth Date</th> + <td><fmt:formatDate value="${pet.birthDate}" pattern="yyyy-MM-dd"/></td> + </tr> + <tr> + <th>Type</th> + <td>${pet.type.name}</td> + </tr> + </table> + </td> + <td valign="top"> + <table> + <thead> + <th>Visit Date</th> + <th>Description</th> + </thead> + <c:forEach var="visit" items="${pet.visits}"> + <tr> + <td><fmt:formatDate value="${visit.date}" pattern="yyyy-MM-dd"/></td> + <td>${visit.description}</td> + </tr> + </c:forEach> + </table> + </td> + </tr> + </table> + <table class="table-buttons"> + <tr> + <td> + <spring:url value="{ownerId}/pets/{petId}/edit" var="petUrl"> + <spring:param name="ownerId" value="${owner.id}"/> + <spring:param name="petId" value="${pet.id}"/> + </spring:url> + <a href="${fn:escapeXml(petUrl)}">Edit Pet</a> + </td> + <td></td> + <td> + <spring:url value="{ownerId}/pets/{petId}/visits/new" var="visitUrl"> + <spring:param name="ownerId" value="${owner.id}"/> + <spring:param name="petId" value="${pet.id}"/> + </spring:url> + <a href="${fn:escapeXml(visitUrl)}">Add Visit</a> + </td> + <td></td> + <td> + <spring:url value="{ownerId}/pets/{petId}/visits.atom" var="feedUrl"> + <spring:param name="ownerId" value="${owner.id}"/> + <spring:param name="petId" value="${pet.id}"/> + </spring:url> + <a href="${fn:escapeXml(feedUrl)}" rel="alternate" type="application/atom+xml">Atom Feed</a> + </td> + </tr> + </table> + </c:forEach> + +<%@ include file="/WEB-INF/jsp/footer.jsp" %> diff --git a/src/main/webapp/WEB-INF/jsp/pets/form.jsp b/src/main/webapp/WEB-INF/jsp/pets/form.jsp new file mode 100644 index 0000000000000000000000000000000000000000..459ec63b35d452a3fbd8e138c15465704a40d814 --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/pets/form.jsp @@ -0,0 +1,56 @@ +<%@ include file="/WEB-INF/jsp/includes.jsp" %> +<%@ include file="/WEB-INF/jsp/header.jsp" %> +<c:choose> + <c:when test="${pet.new}"><c:set var="method" value="post"/></c:when> + <c:otherwise><c:set var="method" value="put"/></c:otherwise> +</c:choose> + +<h2><c:if test="${pet.new}">New </c:if>Pet</h2> + +<b>Owner:</b> ${pet.owner.firstName} ${pet.owner.lastName} +<br/> +<form:form modelAttribute="pet" method="${method}"> + <table> + <tr> + <th> + Name: <form:errors path="name" cssClass="errors"/> + <br/> + <form:input path="name" size="30" maxlength="30"/> + </th> + </tr> + <tr> + <th> + Birth Date: <form:errors path="birthDate" cssClass="errors"/> + <br/> + <form:input path="birthDate" size="10" maxlength="10"/> (yyyy-mm-dd) + </th> + </tr> + <tr> + <th> + Type: <form:errors path="type" cssClass="errors"/> + <br/> + <form:select path="type" items="${types}"/> + </th> + </tr> + <tr> + <td> + <c:choose> + <c:when test="${pet.new}"> + <p class="submit"><input type="submit" value="Add Pet"/></p> + </c:when> + <c:otherwise> + <p class="submit"><input type="submit" value="Update Pet"/></p> + </c:otherwise> + </c:choose> + </td> + </tr> + </table> +</form:form> + +<c:if test="${!pet.new}"> + <form:form method="delete"> + <p class="submit"><input type="submit" value="Delete Pet"/></p> + </form:form> +</c:if> + +<%@ include file="/WEB-INF/jsp/footer.jsp" %> diff --git a/src/main/webapp/WEB-INF/jsp/pets/visitForm.jsp b/src/main/webapp/WEB-INF/jsp/pets/visitForm.jsp new file mode 100644 index 0000000000000000000000000000000000000000..b1207a00f3ed867235ad65a9773ad86c66818f05 --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/pets/visitForm.jsp @@ -0,0 +1,68 @@ +<%@ include file="/WEB-INF/jsp/includes.jsp" %> +<%@ include file="/WEB-INF/jsp/header.jsp" %> + +<h2><c:if test="${visit.new}">New </c:if>Visit:</h2> + +<form:form modelAttribute="visit"> + <b>Pet:</b> + <table width="333"> + <thead> + <th>Name</th> + <th>Birth Date</th> + <th>Type</th> + <th>Owner</th> + </thead> + <tr> + <td>${visit.pet.name}</td> + <td><fmt:formatDate value="${visit.pet.birthDate}" pattern="yyyy-MM-dd"/></td> + <td>${visit.pet.type.name}</td> + <td>${visit.pet.owner.firstName} ${visit.pet.owner.lastName}</td> + </tr> + </table> + + <table width="333"> + <tr> + <th> + Date: + <br/><form:errors path="date" cssClass="errors"/> + </th> + <td> + <form:input path="date" size="10" maxlength="10"/> (yyyy-mm-dd) + </td> + <tr/> + <tr> + <th valign="top"> + Description: + <br/><form:errors path="description" cssClass="errors"/> + </th> + <td> + <form:textarea path="description" rows="10" cols="25"/> + </td> + </tr> + <tr> + <td colspan="2"> + <input type="hidden" name="petId" value="${visit.pet.id}"/> + <p class="submit"><input type="submit" value="Add Visit"/></p> + </td> + </tr> + </table> +</form:form> + +<br/> +<b>Previous Visits:</b> +<table width="333"> + <tr> + <th>Date</th> + <th>Description</th> + </tr> + <c:forEach var="visit" items="${visit.pet.visits}"> + <c:if test="${!visit.new}"> + <tr> + <td><fmt:formatDate value="${visit.date}" pattern="yyyy-MM-dd"/></td> + <td>${visit.description}</td> + </tr> + </c:if> + </c:forEach> +</table> + +<%@ include file="/WEB-INF/jsp/footer.jsp" %> diff --git a/src/main/webapp/WEB-INF/jsp/uncaughtException.jsp b/src/main/webapp/WEB-INF/jsp/uncaughtException.jsp new file mode 100644 index 0000000000000000000000000000000000000000..e97fdf378899784ec4b48949e8c6d6a8e47b4566 --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/uncaughtException.jsp @@ -0,0 +1,49 @@ +<%@ include file="/WEB-INF/jsp/includes.jsp" %> +<%@ include file="/WEB-INF/jsp/header.jsp" %> + +<h2/>Internal error</h2> +<p/> + +<% +try { + // The Servlet spec guarantees this attribute will be available + Throwable exception = (Throwable) request.getAttribute("javax.servlet.error.exception"); + + if (exception != null) { + if (exception instanceof ServletException) { + // It's a ServletException: we should extract the root cause + ServletException sex = (ServletException) exception; + Throwable rootCause = sex.getRootCause(); + if (rootCause == null) + rootCause = sex; + out.println("** Root cause is: "+ rootCause.getMessage()); + rootCause.printStackTrace(new java.io.PrintWriter(out)); + } + else { + // It's not a ServletException, so we'll just show it + exception.printStackTrace(new java.io.PrintWriter(out)); + } + } + else { + out.println("No error information available"); + } + + // Display cookies + out.println("\nCookies:\n"); + Cookie[] cookies = request.getCookies(); + if (cookies != null) { + for (int i = 0; i < cookies.length; i++) { + out.println(cookies[i].getName() + "=[" + cookies[i].getValue() + "]"); + } + } + +} catch (Exception ex) { + ex.printStackTrace(new java.io.PrintWriter(out)); +} +%> + +<p/> +<br/> + + +<%@ include file="/WEB-INF/jsp/footer.jsp" %> diff --git a/src/main/webapp/WEB-INF/jsp/vets.jsp b/src/main/webapp/WEB-INF/jsp/vets.jsp new file mode 100644 index 0000000000000000000000000000000000000000..cff2154f2902c343386d3c8421610e90c5c996f8 --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/vets.jsp @@ -0,0 +1,31 @@ +<%@ include file="/WEB-INF/jsp/includes.jsp" %> +<%@ include file="/WEB-INF/jsp/header.jsp" %> + +<h2>Veterinarians:</h2> + +<table> + <thead> + <th>Name</th> + <th>Specialties</th> + </thead> + <c:forEach var="vet" items="${vets.vetList}"> + <tr> + <td>${vet.firstName} ${vet.lastName}</td> + <td> + <c:forEach var="specialty" items="${vet.specialties}"> + ${specialty.name} + </c:forEach> + <c:if test="${vet.nrOfSpecialties == 0}">none</c:if> + </td> + </tr> + </c:forEach> +</table> +<table class="table-buttons"> + <tr> + <td> + <a href="<spring:url value="/vets.xml" htmlEscape="true" />">View as XML</a> + </td> + </tr> +</table> + +<%@ include file="/WEB-INF/jsp/footer.jsp" %> diff --git a/src/main/webapp/WEB-INF/jsp/welcome.jsp b/src/main/webapp/WEB-INF/jsp/welcome.jsp new file mode 100644 index 0000000000000000000000000000000000000000..1b07c65c3c226f9304fe26c287ed4f7788f26dd3 --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/welcome.jsp @@ -0,0 +1,15 @@ +<%@ include file="/WEB-INF/jsp/includes.jsp" %> +<%@ include file="/WEB-INF/jsp/header.jsp" %> + +<img src="<spring:url value="/static/images/pets.png" htmlEscape="true" />" align="right" style="position:relative;right:30px;"> +<h2><fmt:message key="welcome"/></h2> + +<ul> + <li><a href="<spring:url value="/owners/search" htmlEscape="true" />">Find owner</a></li> + <li><a href="<spring:url value="/vets" htmlEscape="true" />">Display all veterinarians</a></li> + <li><a href="<spring:url value="/static/html/tutorial.html" htmlEscape="true" />">Tutorial</a></li> +</ul> + +<p> </p> + +<%@ include file="/WEB-INF/jsp/footer.jsp" %> diff --git a/src/main/webapp/WEB-INF/petclinic-servlet.xml b/src/main/webapp/WEB-INF/petclinic-servlet.xml new file mode 100644 index 0000000000000000000000000000000000000000..3c05fc3000369aebd1021bbe2216ab08de253baa --- /dev/null +++ b/src/main/webapp/WEB-INF/petclinic-servlet.xml @@ -0,0 +1,110 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + - DispatcherServlet application context for PetClinic's web tier. +--> +<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" + xmlns:oxm="http://www.springframework.org/schema/oxm" + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd + http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-3.0.xsd"> + + <!-- + - The controllers are autodetected POJOs labeled with the @Controller annotation. + --> + <context:component-scan base-package="org.springframework.samples.petclinic.web"/> + + <!-- + - The form-based controllers within this application provide @RequestMapping + - annotations at the type level for path mapping URLs and @RequestMapping + - at the method level for request type mappings (e.g., GET and POST). + - In contrast, ClinicController - which is not form-based - provides + - @RequestMapping only at the method level for path mapping URLs. + - + - DefaultAnnotationHandlerMapping is driven by these annotations and is + - enabled by default with Java 5+. + --> + + <!-- + - This bean processes annotated handler methods, applying PetClinic-specific PropertyEditors + - for request parameter binding. It overrides the default AnnotationMethodHandlerAdapter. + --> + <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"> + <property name="webBindingInitializer"> + <bean class="org.springframework.samples.petclinic.web.ClinicBindingInitializer"/> + </property> + </bean> + + <!-- + - This bean resolves specific types of exceptions to corresponding logical + - view names for error views. The default behaviour of DispatcherServlet + - is to propagate all exceptions to the servlet container: this will happen + - here with all other types of exceptions. + --> + <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> + <property name="exceptionMappings"> + <props> + <prop key="org.springframework.web.servlet.PageNotFound">pageNotFound</prop> + <prop key="org.springframework.dao.DataAccessException">dataAccessFailure</prop> + <prop key="org.springframework.transaction.TransactionException">dataAccessFailure</prop> + </props> + </property> + </bean> + + <!-- + - This view resolver delegates to the InternalResourceViewResolver and BeanNameViewResolver, + - and uses the requested media type to pick a matching view. When the media type is 'text/html', + - it will delegate to the InternalResourceViewResolver's JstlView, otherwise to the + - BeanNameViewResolver. Note the use of the expression language to refer to the contentType + - property of the vets view bean, setting it to 'application/vnd.springsource.samples.petclinic+xml'. + --> + <bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver"> + <property name="mediaTypes"> + <map> + <entry key="xml" value="#{vets.contentType}"/> + <entry key="atom" value="#{visits.contentType}"/> + </map> + </property> + <property name="order" value="0"/> + </bean> + + <!-- + - The BeanNameViewResolver is used to pick up the visits view name (below). + - It has the order property set to 2, which means that this will + - be the first view resolver to be used after the delegating content + - negotiating view resolver. + --> + <bean class="org.springframework.web.servlet.view.BeanNameViewResolver" p:order="1"/> + <!-- + + - This bean configures the 'prefix' and 'suffix' properties of + - InternalResourceViewResolver, which resolves logical view names + - returned by Controllers. For example, a logical view name of "vets" + - will be mapped to "/WEB-INF/jsp/vets.jsp". + --> + <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:prefix="/WEB-INF/jsp/" + p:suffix=".jsp" p:order="2"/> + + <!-- + - The AtomView rendering a Atom feed of the visits + --> + <bean id="visits" class="org.springframework.samples.petclinic.web.VisitsAtomView"/> + + <bean id="vets" class="org.springframework.web.servlet.view.xml.MarshallingView"> + <property name="contentType" value="application/vnd.springsource.samples.petclinic+xml"/> + <property name="marshaller" ref="marshaller"/> + </bean> + + <oxm:jaxb2-marshaller id="marshaller"> + <oxm:class-to-be-bound name="org.springframework.samples.petclinic.Vets"/> + </oxm:jaxb2-marshaller> + + <!-- + - Message source for this context, loaded from localized "messages_xx" files. + - Could also reside in the root application context, as it is generic, + - but is currently just used within PetClinic's web tier. + --> + <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource" + p:basename="messages"/> + +</beans> diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000000000000000000000000000000000..06a6a311086c1a67449f30b1f12f5e5f851621f0 --- /dev/null +++ b/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,161 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> + + <display-name>Spring PetClinic</display-name> + + <description>Spring PetClinic sample application</description> + + <!-- + Key of the system property that should specify the root directory of this + web app. Applied by WebAppRootListener or Log4jConfigListener. + --> + <context-param> + <param-name>webAppRootKey</param-name> + <param-value>petclinic.root</param-value> + </context-param> + + + + <!-- + Location of the Log4J config file, for initialization and refresh checks. + Applied by Log4jConfigListener. + --> + <context-param> + <param-name>log4jConfigLocation</param-name> + <param-value>/WEB-INF/classes/log4j.properties</param-value> + </context-param> + + <!-- + - Location of the XML file that defines the root application context. + - Applied by ContextLoaderServlet. + - + - Can be set to: + - "/WEB-INF/applicationContext-hibernate.xml" for the Hibernate implementation, + - "/WEB-INF/applicationContext-jpa.xml" for the JPA one, or + - "/WEB-INF/applicationContext-jdbc.xml" for the JDBC one. + --> + <context-param> + <param-name>contextConfigLocation</param-name> + + <param-value>/WEB-INF/applicationContext-jdbc.xml</param-value> + <!-- + <param-value>/WEB-INF/applicationContext-hibernate.xml</param-value> + <param-value>/WEB-INF/applicationContext-jpa.xml</param-value> + --> + + <!-- + To use the JPA variant above, you will need to enable Spring load-time + weaving in your server environment. See PetClinic's readme and/or + Spring's JPA documentation for information on how to do this. + --> + </context-param> + + <!-- + - Configures Log4J for this web app. + - As this context specifies a context-param "log4jConfigLocation", its file path + - is used to load the Log4J configuration, including periodic refresh checks. + - + - Would fall back to default Log4J initialization (non-refreshing) if no special + - context-params are given. + - + - Exports a "web app root key", i.e. a system property that specifies the root + - directory of this web app, for usage in log file paths. + - This web app specifies "petclinic.root" (see log4j.properties file). + --> + <!-- Leave the listener commented-out if using JBoss --> + <!-- + <listener> + <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class> + </listener> + --> + + <!-- + - Loads the root application context of this web app at startup, + - by default from "/WEB-INF/applicationContext.xml". + - Note that you need to fall back to Spring's ContextLoaderServlet for + - J2EE servers that do not follow the Servlet 2.4 initialization order. + - + - Use WebApplicationContextUtils.getWebApplicationContext(servletContext) + - to access it anywhere in the web application, outside of the framework. + - + - The root context is the parent of all servlet-specific contexts. + - This means that its beans are automatically available in these child contexts, + - both for getBean(name) calls and (external) bean references. + --> + <listener> + <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> + </listener> + + <!-- + - Map static resources to the default servlet + - examples: + - http://localhost:8080/static/images/pets.png + - http://localhost:8080/static/styles/petclinic.css + --> + <servlet-mapping> + <servlet-name>default</servlet-name> + <url-pattern>/static/*</url-pattern> + </servlet-mapping> + + <!-- + - Servlet that dispatches request to registered handlers (Controller implementations). + - Has its own application context, by default defined in "{servlet-name}-servlet.xml", + - i.e. "petclinic-servlet.xml". + - + - A web app can contain any number of such servlets. + - Note that this web app has a shared root application context, serving as parent + - of all DispatcherServlet contexts. + --> + <servlet> + <servlet-name>petclinic</servlet-name> + <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> + <load-on-startup>2</load-on-startup> + </servlet> + + <!-- + - Maps the petclinic dispatcher to "*.do". All handler mappings in + - petclinic-servlet.xml will by default be applied to this subpath. + - If a mapping isn't a /* subpath, the handler mappings are considered + - relative to the web app root. + - + - NOTE: A single dispatcher can be mapped to multiple paths, like any servlet. + --> + <servlet-mapping> + <servlet-name>petclinic</servlet-name> + <url-pattern>/</url-pattern> + </servlet-mapping> + + <filter> + <filter-name>httpMethodFilter</filter-name> + <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> + </filter> + + <filter-mapping> + <filter-name>httpMethodFilter</filter-name> + <servlet-name>petclinic</servlet-name> + </filter-mapping> + + <session-config> + <session-timeout>10</session-timeout> + </session-config> + + <error-page> + <exception-type>java.lang.Exception</exception-type> + <!-- Displays a stack trace --> + <location>/WEB-INF/jsp/uncaughtException.jsp</location> + </error-page> + + <!-- + - Reference to PetClinic database. + - Only needed if not using a local DataSource but a JNDI one instead. + --> + <!-- + <resource-ref> + <res-ref-name>jdbc/petclinic</res-ref-name> + <res-type>javax.sql.DataSource</res-type> + <res-auth>Container</res-auth> + </resource-ref> + --> + +</web-app> diff --git a/src/main/webapp/html/tutorial.html b/src/main/webapp/html/tutorial.html new file mode 100644 index 0000000000000000000000000000000000000000..acc261fb1cf1d2ac8c070fbaa381771b00867453 --- /dev/null +++ b/src/main/webapp/html/tutorial.html @@ -0,0 +1,1153 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html> +<head> + <title>The Spring PetClinic Application</title> + <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> + <link rel="stylesheet" href="../styles/petclinic.css" type="text/css"> +</head> + +<body> + +<div id="main"> + + <h1>The Spring PetClinic Application</h1> + + <!-- + <table class="updated"> + <tr><td>Updated:</td><td>25.NOV.2009</td><td>Costin Leau(<strong>Work In Progress: not yet fully updated regarding Spring 3.0</strong>)</td></tr> + <tr><td></td><td>11.OCT.2009</td><td>Sam Brannen</td></tr> + <tr><td></td><td>21.OCT.2007</td><td>Sam Brannen</td></tr> + <tr><td></td><td>30.JUN.2006</td><td>Costin Leau</td></tr> + <tr><td></td><td>01.DEC.2004</td><td>Ken Krebs</td></tr> + <tr><td></td><td>16.AUG.2003</td><td>Ken Krebs</td></tr> + </table> + --> + +<!-- === INTRODUCTION ====================================================== --> + <h2>Introduction</h2> + + <p> + The Spring Framework is a collection of small, well-focused, loosely coupled Java + frameworks that can be used independently or collectively to build + industrial strength applications of many different types. The PetClinic + sample application is designed to show how the Spring + application frameworks can be used to build simple, but powerful + database-oriented applications. It will demonstrate the use + of Spring's core functionality: + </p> + + <ul> + <li>JavaBeans based application configuration using Inversion-Of-Control</li> + <li>Model-View-Controller web Presentation Layer</li> + <li>Practical database access through JDBC, Hibernate, or Java Persistence API</li> + <li>Application monitoring based on JMX</li> + <li>Declarative Transaction Management using AOP</li> + <li>Data Validation that supports but is not dependent on the Presentation Layer</li> + </ul> + + <p> + The Spring frameworks provide a great deal of useful infrastructure to + simplify the tasks faced by application developers. This infrastructure + helps developers to create applications that are: + </p> + + <ul> + <li> + <span style="font-weight: bold; text-decoration: underline;">concise</span> + by handling a lot of the complex control flow that is needed to use the + Java API's, such as JDBC, JNDI, JTA, RMI, and EJB. + </li> + <li> + <span style="font-weight: bold; text-decoration: underline;">flexible</span> + by simplifying the process of external application configuration + through the use of Reflection and JavaBeans. This allows the developer to + achieve a clean separation of configuration data from application code. + All application and web application objects, including validators, + workflow controllers, and views, are JavaBeans that can be configured + externally.</li> + <li> + <span style="font-weight: bold; text-decoration: underline;">testable</span> + by supplying an interface based design to maximize pluggability. This + facilitates unit testing of Business Logic without requiring the + presence of application or live database servers.</li> + <li> + <span style="font-weight: bold; text-decoration: underline;">maintainable</span> + by facilitating a clean separation of the application layers. It most + importantly helps maintain the independence of the Business Layer + from the Presentation layer. PetClinic demonstrates the use of a + Model-View-Controller + based web presentation framework that can work seamlessly with many + different types of view technologies. The Spring web application + framework helps developers to implement their Presentation as a clean + and thin layer focused on its main missions of translating user actions + into application events and rendering model data.</li> + </ul> + + <p> + It is assumed that users of this tutorial will have a basic knowledge + of object-oriented design, Java, Servlets, JSP, and relational + databases. It also assumes a basic knowledge of the use of a Java EE web + application container. + </p> + + <p> + Since the purpose of the sample application is tutorial in nature, the + implementation presented here will of course provide only a small + subset of the functionality that would be needed by a real world version of a + PetClinic application. + </p> + +<!-- === REQUIREMENTS ====================================================== --> + <h2>PetClinic Sample Application Requirements</h2> + + <p> + The application requirement is for an information system that is + accessible through a web browser. The users of the application are + employees of the clinic who in the course of their work need to view + and manage information regarding the veterinarians, the clients, and their + pets. The sample application supports the following: + </p> + + <h3>Use Cases</h3> + <ul> + <li>View a list of veterinarians and their specialties</li> + <li>View information pertaining to a pet owner</li> + <li>Update the information pertaining to a pet owner</li> + <li>Add a new pet owner to the system</li> + <li>View information pertaining to a pet</li> + <li>Update the information pertaining to a pet</li> + <li>Add a new pet to the system</li> + <li>View information pertaining to a pet's visitation history</li> + <li>Add information pertaining to a visit to the pet's visitation history</li> + </ul> + + <h3>Business Rules</h3> + <ol> + <li>An owner may not have multiple pets with the same case-insensitive name.</li> + </ol> + +<!-- === DESIGN AND IMPLEMENTATION ========================================= --> + <h2>PetClinic Sample Application Design & Implementation</h2> + + <h3>Server Technology</h3> + <p> + The sample application should be usable with any Java EE web application + container that is compatible with the Servlet 2.4 and JSP 2.0 + specifications. Some of the deployment files provided are designed + specifically for Apache Tomcat. These files specify container-supplied + connection-pooled data sources. It is not necessary to use these files. + The application has been configured by default to use a data source + with connection pooling. Configuration details are + provided in the Developer Instructions section. The view technologies + that are to be used for rendering the application are Java Server Pages + (JSP) along with the Java Standard Tag Library (JSTL). + </p> + + <h3>Database Technology</h3> + <p> + The sample application uses a relational database for data storage. + Support has been provided for a choice of 1 of 2 database selections, + MySql or HypersonicSQL. HypersonicSQL version 1.8.0 is the default + choice. It is possible to + easily configure the application to use either database. Configuration + details are provided in the Developer Instructions section. + </p> + + <h3>Development Environment</h3> + <p> + A copy of the Spring runtime library jar file is provided with the + sample application along with some of the other required jar files. The + developer will need to obtain the following tools externally, all of + which are freely available: + </p> + + <ul> + <li>Java SDK 1.5.x</li> + <li>Maven 2.0.10+</li> + <li>Ant 1.7.1 (for executing scripts, if needed, against the in-memory database)</li> + <li>Tomcat 6.x.x, or some other Java Servlet container</li> + <li>(Optional) MySQL 5.x with MySQL Connector/J 5.x</li> + </ul> + + <p> + <strong>NOTE:</strong> The version numbers listed are those that were used + in the development of the PetClinic application. Other versions of the same + tools may or may not work. + </p> + + <p> + Download links for the various tools needed are provided in the Developer + Instructions section. + </p> + + <h3>PetClinic Database</h3> + <p> + The following is an overview of the database schema used in PetClinic. + Detailed field descriptions can be found in the + <span style="font-weight: bold; font-style: italic;">"initDB.txt"</span> + SQL script files in the database-specific "db" sub-directories. All "id" key + fields are of Java type <span style="font-weight: bold; font-style: italic;">int</span>. + </p> + + <p> + TABLE: <span style="font-weight: bold; font-style: italic;">owners</span><br> + PRIMARY KEY <span style="font-weight: bold;">id</span> + </p> + + <p> + TABLE: <span style="font-weight: bold; font-style: italic;">types</span><br> + PRIMARY KEY <span style="font-weight: bold;">id</span> + </p> + + <p> + TABLE: <span style="font-weight: bold; font-style: italic;">pets</span><br> + PRIMARY KEY <span style="font-weight: bold;">id</span><br> + FOREIGN KEY <span style="font-weight: bold;">type_id</span> + references the <span style="font-weight: bold; font-style: italic;">types</span> + table<span style="font-weight: bold;"> id</span> field<br> + FOREIGN KEY <span style="font-weight: bold;">owner_id</span> + references the <span style="font-weight: bold; font-style: italic;">owners</span> + table <span style="font-weight: bold;">id</span> field + </p> + + <p> + TABLE: <span style="font-weight: bold; font-style: italic;">vets</span><br> + PRIMARY KEY <span style="font-weight: bold;">id</span> + </p> + + <p> + TABLE: <span style="font-weight: bold; font-style: italic;">specialties</span><br> + PRIMARY KEY <span style="font-weight: bold;">id</span><br> + </p> + + <p> + TABLE: <span style="font-weight: bold; font-style: italic;">vet_specialties</span> - + a link table for <span style="font-weight: bold; font-style: italic;">vets</span> + and their <span style="font-weight: bold; font-style: italic;">specialties</span><br> + FOREIGN KEY <span style="font-weight: bold;">vet_id</span> + references the <span style="font-weight: bold; font-style: italic;">vets</span> + table<span style="font-weight: bold;"> id</span> field<br> + FOREIGN KEY <span style="font-weight: bold;">specialty_id</span> + references the <span style="font-weight: bold; font-style: italic;">specialties</span> + table <span style="font-weight: bold;">id</span> field + </p> + + <p> + TABLE: <span style="font-weight: bold; font-style: italic;">visits</span><br> + PRIMARY KEY <span style="font-weight: bold;">id</span><br> + FOREIGN KEY <span style="font-weight: bold;">pet_id</span> + references the <span style="font-weight: bold; font-style: italic;">pets</span> + table <span style="font-weight: bold;">id</span> field + </p> + + <h3>Directory Structure</h3> + + <p> + d-- indicates a directory holding source code, configuration files, etc.<br> + D-- indicates a directory that is created by the build script + </p> + + <p> + d-- <span style="font-weight: bold; font-style: italic;">petclinic</span>: + the root directory of the project contains build related files<br> + + d-- <span style="font-weight: bold; font-style: italic;">src</span>: contains + Java source code files and ORM configuration files<br> + + d-- <span style="font-weight: bold; font-style: italic;">war</span>: contains + the web application resource files<br> + + d-- <span style="font-weight: bold; font-style: italic;">html</span>: contains + tutorial files<br> + + D-- <span style="font-weight: bold; font-style: italic;">docs</span>: contains + Javadoc files<br> + + d-- <span style="font-weight: bold; font-style: italic;">web-inf</span>: + contains web application configuration files and application context + files<br> + + + d-- <span style="font-weight: bold; font-style: italic;">jsp</span>: + contains Java Server Page files<br> + + + D--<span style="font-weight: bold;"> <span style="font-style: italic;">lib</span></span>: + contains application dependencies<br> + + d-- <span style="font-weight: bold; font-style: italic;">test</span>: contains + testing related + Java source code files<br> + + d-- <span style="font-weight: bold; font-style: italic;">db</span>: contains + database SQL scripts and other related files/directories<br> + + d-- <span style="font-weight: bold; font-style: italic;">hsqldb</span>: + contains files related to HSQL, contains scripts and a Tomcat + context definition file<br> + + d-- <span style="font-weight: bold; font-style: italic;">mysql</span>: + contains files related to MySQL, contains scripts and a Tomcat context + definition file<br> + + D-- <span style="font-weight: bold; font-style: italic;">.classes</span>: + contains compiled Java class files<br> + + D-- <span style="font-weight: bold; font-style: italic;">.testclasses</span>: + contains compiled testing related Java class files<br> + + D-- <span style="font-weight: bold; font-style: italic;">junit-reports</span>: + contains generated xml-formatted test reports<br> + + D-- <span style="font-weight: bold; font-style: italic;">reports/html</span>: + contains generated html-formatted test reports<br> + + D-- <span style="font-weight: bold; font-style: italic;">dist</span>: contains + packaged archives of files + </p> + +<!-- === DESIGN ============================================================ --> + <h2>PetClinic Application Design</h2> + + <h3>Logging</h3> + <p> + Spring supports the use of the Apache Commons Logging API. This API + provides the ability to use Java 1.4 loggers, the simple Commons loggers, + and Apache log4j loggers. PetClinic uses log4j to provide sophisticated + and configurable logging capabilities. The file, + <span style="font-weight: bold; font-style: italic;">src/main/resources/log4j.properties</span> + configures the definition of <strong>log4j</strong>loggers. + </p> + + <h3>Business Layer</h3> + <p> + The Business Layer consists of a number of basic JavaBean classes + representing the application domain objects and associated validation + objects that are used by the Presentation Layer. The validation objects + used in PetClinic are all implementations of the + <strong>org.springframework.validation.Validator</strong> interface. + </p> + + <ul> + <li><span style="font-weight: bold; font-style: italic;">org.springframework.samples.petclinic.Entity</span> + is a simple JavaBean superclass used for all persistable objects.</li> + <li><span style="font-weight: bold; font-style: italic;">org.springframework.samples.petclinic.NamedEntity</span> + is an extension of <span style="font-weight: bold;">Entity</span> that adds a name property.</li> + <li><span style="font-weight: bold; font-style: italic;">org.springframework.samples.petclinic.Specialty</span> + is an extension of <span style="font-weight: bold;">NamedEntity</span>.</li> + <li><span style="font-weight: bold; font-style: italic;">org.springframework.samples.petclinic.PetType</span> + is an extension of <span style="font-weight: bold;">NamedEntity</span>.</li> + <li><span style="font-weight: bold; font-style: italic;">org.springframework.samples.petclinic.Person</span> is + an extension of <span style="font-weight: bold;">Entity</span> that provides a superclass for all objects that + implement the notion of a person.</li> + <li><span style="font-weight: bold; font-style: italic;">org.springframework.samples.petclinic.Vet</span> is + an extension of <span style="font-weight: bold;">Person</span> that implements a + veterinarian. It holds a <span style="font-weight: bold;">List</span> of + specialties that the <span style="font-weight: bold;">Vet</span> is capable of.</li> + <li><span style="font-weight: bold; font-style: italic;">org.springframework.samples.petclinic.Owner</span> + is an extension of <span style="font-weight: bold;">Person</span> that implements a pet owner. + It holds a <span style="font-weight: bold;">List</span> of pets owned.</li> + <li><span style="font-weight: bold; font-style: italic;">org.springframework.samples.petclinic.Pet</span> + is an extension of <span style="font-weight: bold;">NamedEntity</span> that + implements a pet. It holds a <span style="font-weight: bold;">List</span> of + visits made concerning the pet.</li> + <li><span style="font-weight: bold; font-style: italic;">org.springframework.samples.petclinic.Visit</span> + is a simple JavaBean that implements the notion of a clinic visit + for a pet. </li> + <li><span style="font-weight: bold; font-style: italic;">org.springframework.samples.petclinic.util.EntityUtils</span> + provides utility methods for handling entities.</li> + </ul> + + <ul> + <li><span style="font-weight: bold; font-style: italic;">org.springframework.samples.petclinic.validation.OwnerValidator</span> + is a Spring <span style="font-weight: bold;">Validator</span> that + verifies correct data entry for the Add and Edit Owner forms.</li> + <li><span style="font-weight: bold; font-style: italic;">org.springframework.samples.petclinic.validation.PetValidator</span> + is a Spring <span style="font-weight: bold;">Validator</span> that + verifies correct data entry for the Add and Edit Pet forms.</li> + <li><span style="font-weight: bold; font-style: italic;">org.springframework.samples.petclinic.validation.VisitValidator</span> + is a Spring <span style="font-weight: bold;">Validator</span> that + verifies correct data entry for the AddVisit form.</li> + </ul> + + <h3>Business / Persistence Layer</h3> + + <p> + Since the PetClinic application is all about database access and there is + very little business logic in the application outside of that, there is + no separation of the primary Business and Persistence Layer API's. + While this design technique should not be used for an application with more + complex business logic, it is acceptable here because all of the + non-persistence related business rules have been implemented in + business objects and have not leaked into the Persistence Layer. The most + important facet of the design is that the Business and Persistence + Layers are <strong>COMPLETELY</strong> independent of the Presentation Layer. + </p> + + <p> + The Persistence Layer can be configured to use either HSQL or MySQL + with any one of the following data access technologies aided by + infrastructure provided by Spring: + </p> + + <ul> + <li>JDBC</li> + <li>Hibernate 3</li> + <li>Java Persistence API</li> + </ul> + + <p> + <span style="font-weight: bold;">NOTE:</span> Spring also provides + infrastructure for using other + <strong>O</strong>bject/<strong>R</strong>elational <strong>M</strong>apping + frameworks such as JDO and iBATIS SqlMaps, but these are not demonstrated in PetClinic. + (See the 'jpetstore' sample application that ships with the full Spring Framework + distribution for an example of using iBatis and Spring.) + </p> + + <p> + One of the key elements provided by Spring is the use of a common set + of meaningful data access exceptions that can be used regardless of + which database or access strategy is used. All of these exceptions + derive from <strong>org.springframework.dao.DataAccessException</strong>. + Since most exceptions encountered during database access are indicative + of programming errors, <strong>DataAccessException</strong> is an + abstract <strong>RuntimeException</strong> whose derivatives only need + to be caught by application code to handle recoverable errors when it + makes sense to do so. This greatly simplifies application code compared + to, for example, code using JDBC directly where <strong>SqlExceptions</strong> + must be caught and database specific error codes must be decoded. + Examination of the PetClinic source code will show that the + persistence-oriented code is completely focused on the relevant + transfer of data to/from the referenced objects without extraneous + error handling. + </p> + + <p> + The high-level business/persistence API for PetClinic is the + <span style="font-weight: bold;">org.springframework.samples.petclinic.Clinic</span> + interface. Each persistence strategy in PetClinic is a different implementation of the + <span style="font-weight: bold;">Clinic</span> interface. In each case, the + <span style="font-weight: bold;">Clinic</span> implementation is fronted by + a transactional proxy, configured via the + <span style="font-weight: bold;">@Transactional</span> annotation, that also + implements <span style="font-weight: bold;">Clinic</span>. These objects + are standard Java dynamic proxies which are created by an instance of + <span style="font-weight: bold;">org.springframework.transaction.interceptor.TransactionProxyFactoryBean</span>. + These proxies are configured in the respective application context file + via <strong><tx:annotation-driven /></strong> and specify that all + <span style="font-weight: bold;">Clinic</span> methods are run in a + transactional context. The transaction managers used in PetClinic are all + implementations of the + <span style="font-weight: bold;">org.springframework.transaction.PlatformTransactionManager</span> + interface. All of the implementations are by default configured + to use a local <span style="font-weight: bold;">DataSource</span> that + will work in any environment through the use of an instance of + <span style="font-weight: bold;">org.springframework.jdbc.datasource.DriverManagerDataSource</span>. + While this is appropriate for use in a demo or single user + program, a connection pooling <span style="font-weight: bold;">DataSource</span>, + such as an instance of <span style="font-weight: bold;">org.apache.commons.dbcp.BasicDataSource</span>, + is more appropriate for use in a multi-user application. Another + alternative is to obtain one through the Java EE environment + using an instance of + <span style="font-weight: bold;">org.springframework.jndi.JndiObjectFactoryBean</span>. + </p> + + <h3>JDBC Clinic Implementation</h3> + <p> + Spring provides a number of high-level database + access convenience classes in the package + <span style="font-weight: bold;">org.springframework.jdbc.object</span>. + These classes and the lower-level Spring classes that they use in the + <span style="font-weight: bold;">org.springframework.jdbc.core</span> package + provide a higher level of abstraction for using JDBC that keeps the developer + from having to correctly implement the handling of the checked + <span style="font-weight: bold;">SqlExceptions</span> with ugly error-prone + nested try-catch-finally blocks. Using the classes in this package + allows the developer to focus efforts on the functionality being + implemented rather than the mechanics of error handling. When using + these classes, it is the responsibility of the developer to provide the + SQL needed and to map the parameters to/from the respective domain + object. This typically is done by extending one of the + <span style="font-weight: bold;">org.springframework.jdbc.object</span> classes, + initializing its SQL, and overriding a method that takes care of + the mapping. In this way, the developer gets to focus on implementing + functionality rather than application plumbing. These classes also + take care of closing connections to prevent hard to find resource + leakage problems. It should be noted that instances of these classes + are lightweight, reusable, and threadsafe. + </p> + + <p> + The JDBC implementation of the Clinic interface is + <span style="font-weight: bold; font-style: italic;">org.springframework.samples.petclinic.jdbc.SimpleJdbcClinic</span>, + which uses Java 5 language features, + <strong>org.springframework.jdbc.core.simple.SimpleJdbcTemplate</strong>, and + <strong>org.springframework.jdbc.core.simple.SimpleJdbcInsert</strong>. + It also takes advantage of classes like + <strong>org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource</strong> and + <strong>org.springframework.jdbc.core.simple.ParameterizedBeanPropertyRowMapper</strong> + which provide automatic mapping between JavaBean properties and JDBC + parameters or query results. SimpleJdbcClinic is a rewrite of the + AbstractJdbcClinic which was the base class for JDBC implementations of + the Clinic interface for Spring 2.0. + </p> + + <p> + The transaction manager used in the JDBC Clinic Implementation is an + instance of <span style="font-weight: bold;">org.springframework.jdbc.datasource.DataSourceTransactionManager</span> + that can be used for local transactions. + </p> + + <h3>Hibernate 3 Clinic Implementation</h3> + <p> + The Hibernate 3 implementation of the <span style="font-weight: bold;">Clinic</span> + interface is + <span style="font-weight: bold; font-style: italic;">org.springframework.samples.petclinic.hibernate.HibernateClinic</span>. + To simplify using Hibernate, Spring provides the + <span style="font-weight: bold;">org.springframework.orm.hibernate3.LocalSessionFactoryBean</span>. + The Hibernate configuration is provided by the file <span style="font-style: italic;">src/main/resources/petclinic.hbm.xml</span>. + </p> + + <h3>Java Persistence API (JPA) Clinic Implementation</h3> + <p> + The JPA implementation of the <span style="font-weight: bold;">Clinic</span> + interface is + <span style="font-weight: bold; font-style: italic;">org.springframework.samples.petclinic.jpa.EntityManagerClinic</span>, + which is based on native JPA usage combined with Spring's + <span style="font-weight: bold;">@Repository</span> and + <span style="font-weight: bold;">@Transactional</span> annotations but + otherwise has no dependencies on any Spring API's. + To simplify JPA usage, Spring provides (among other classes) the + <span style="font-weight: bold;">org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean</span>. + The JPA configuration is provided by + <span style="font-style: italic;">src/main/resources/META-INF/orm.xml</span> and + <span style="font-style: italic;"> src/main/resources/META-INF/persistence.xml.</span> + </p> + + <h3>ApplicationContext</h3> + <p> + A Spring <span style="font-weight: bold;">org.springframework.context.ApplicationContext</span> object + provides a map of user-defined JavaBeans that specify either a singleton + object or the initial construction of prototype instances. These beans + constitute the <span style="font-weight: bold;">Business/Persistence + Layer</span> of PetClinic. The following beans are defined in all 3 + versions (1 per access strategy) of the PetClinic + <span style="font-style: italic;">src/main/webapp/WEB-INF/applicationContext-*.xml</span> + file: + </p> + + <ol> + <li>A <span style="font-weight: bold; font-style: italic;">PropertyPlaceholderConfigurer</span>, + which is configured via + <strong><context:property-placeholder ... /></strong> and + is a singleton bean that replaces ${...} placeholders with values from a + properties file, in this case, JDBC-related settings for the + <span style="font-weight: bold; font-style: italic;">dataSource</span> bean + described below + (see <span style="font-weight: bold; font-style: italic;">src/main/resources/jdbc.properties</span>). + </li> + <li><span style="font-weight: bold; font-style: italic;">dataSource</span>, + which is a singleton bean that defines the implementation of the source + of database connections used by the application. + </li> + <li><span style="font-weight: bold; font-style: italic;">transactionManager</span>, + which is a singleton bean that defines the implementation of the transaction + management strategy for the application. + </li> + <li><span style="font-weight: bold; font-style: italic;">clinic</span>, + which is a singleton bean that defines the implementation of the + <span style="font-weight: bold;">Clinic</span> interface that provides the + primary Business Layer API of the application. + </li> + </ol> + + <h3>Presentation Layer</h3> + <p> + The Presentation Layer is implemented as a Java EE Web Application and + provides a very thin and concise Model-View-Controller type user + interface to the Business and Persistence Layers. + </p> + + <p> + The PetClinic web application is configured via the following files: + </p> + + <ul> + <li><span style="font-weight: bold; font-style: italic;">src/main/webapp/WEB-INF/web.xml</span>: + the web application configuration file.</li> + <li><span style="font-weight: bold; font-style: italic;">src/main/webapp/WEB-INF/petclinic-servlet.xml</span>: + configures the petclinic dispatcher servlet and the other controllers + and forms that it uses. The beans defined in this file reference the + Business/Persistence Layer beans defined in + <span style="font-style: italic;">applicationContext-*.xml.</span></li> + <li><span style="font-weight: bold; font-style: italic;">src/main/resources/messages*.properties</span>: + configures the definition of internationalizable message resources.</li> + </ul> + + <p> + Examine the comments provided in each of these files for a more + in-depth understanding of the details of how the application is + configured. + </p> + + <h3>General</h3> + + <ul> + <li>In <span style="font-weight: bold; font-style: italic;">web.xml</span>, + a context-param, "<span style="font-weight: bold;">webAppRootkey</span>", + provides the key for a system property that specifies the root directory + for the web application. This parameter, + <span style="font-weight: bold;">"petclinic.root"</span>, can be used to aid + in configuring the application.</li> + <li>In <span style="font-weight: bold; font-style: italic;">web.xml</span>, + a <span style="font-weight: bold;">org.springframework.web.context.ContextLoaderListener</span> + is defined that loads the root <span style="font-weight: bold;">ApplicationContext</span> + object of this webapp at startup, by default from the designated + <span style="font-weight: bold; font-style: italic;">/WEB-INF/applicationContext-*.xml</span>. + The root <span style="font-weight: bold;">org.springframework.web.context.WebApplicationContext</span> + of PetClinic is an instance of + <span style="font-weight: bold;">org.springframework.web.context.support.XmlWebApplicationContext</span> + and is the parent of all servlet-specific <span style="font-weight: bold;">ApplicationContexts</span>. + The Spring root <span style="font-weight: bold;">ApplicationContext</span> object + provides a map of user-defined JavaBeans that can be used in any and + all layers of the application. Beans defined in the root + <span style="font-weight: bold;">ApplicationContext</span> are automatically + available in all child <span style="font-weight: bold;">ApplicationContext</span> + objects as (external) bean references. Beans defined in an + <span style="font-weight: bold;">ApplicationContext</span> can also be + accessed directly by Java code through + <span style="font-style: italic;">getBean(name)</span> method calls. + </li> + + <li>In <span style="font-weight: bold; font-style: italic;">web.xml</span>, + a <span style="font-weight: bold;">Servlet</span> named + <span style="font-weight: bold;">"petclinic"</span> is specified to act as + a dispatcher for the entire application. This + <span style="font-weight: bold;">org.springframework.web.servlet.DispatcherServlet</span> + is used to handle all URL's matching the pattern <span style="font-weight: bold;">"*.do"</span>. + As with any <span style="font-weight: bold;">Servlet</span>, multiple URL mappings may + be defined. It is also possible to define multiple instances of + <span style="font-weight: bold;">DispatcherServlet</span>. Each + <span style="font-weight: bold;">DispatcherServlet</span> dispatches + requests to registered handlers (<span style="font-weight: bold;">Controller</span> + interface implementations or POJOs annotated with <strong>@Controller</strong>) + indirectly through a + <span style="font-weight: bold;">org.springframework.web.servlet.handler.HandlerMapping</span> + implementation. Each <span style="font-weight: bold;">DispatcherServlet</span> + has its own <span style="font-weight: bold;">ApplicationContext</span>, + by default defined in <span style="font-weight: bold;">"{servlet-name}-servlet.xml"</span>. + In this case, it is in the file <span style="font-weight: bold;">"petclinic-servlet.xml"</span>. + This <span style="font-weight: bold;">ApplicationContext</span> is + used to specify the various web application user interface beans and + the URL mappings that are used by the <span style="font-weight: bold;">DispatcherServlet</span> + to control the handling of requests. + </li> + </ul> + + <p> + The files <span style="font-weight: bold; font-style: italic;">web.xml</span> and + <span style="font-weight: bold; font-style: italic;">log4j.properties</span> specify + the configuration of logging in the system: + </p> + + <ul> + <li>In <span style="font-weight: bold; font-style: italic;">web.xml</span>, + a <span style="font-weight: bold;">"log4jConfigLocation"</span> context-param + is specified that sets the location of the + <span style="font-weight: bold;">log4j</span> configuration file. The + default location for this file is + <span style="font-weight: bold; font-style: italic;">/WEB-INF/classes/log4j.properties</span>. + Specifying this parameter explicitly allows the location to be changed + from the default and is also used to cause periodic + <span style="font-weight: bold;">log4j</span> configuration refresh checks.</li> + <li>In <span style="font-weight: bold; font-style: italic;">web.xml</span>, + a <span style="font-weight: bold;">Log4jConfigListener</span> is + specified that will initialize <span style="font-weight: bold;">log4j</span> using + the specified configuration file when the web app starts. The + <span style="font-weight: bold;">Log4jConfigListener</span> is commented out + in the file because of a conflict when using JBoss. It should also be + noted that if the container initializes Servlets before Listeners as + some pre-Servlet 2.4 containers do, a <b>Log4jConfigServlet</b> should + be used instead.</li> + <li>In <span style="font-weight: bold; font-style: italic;">log4j.properties</span>, + the following loggers are specified: + <ul> + <li><span style="font-weight: bold; font-style: italic;">"stdout"</span> provides + logging messages to the container's log file.</li> + <li><span style="font-weight: bold; font-style: italic;">"logfile"</span> provides + logging messages to a rolling file that is specified using the + previously defined "petclinic.root".</li> + </ul> + </li> + </ul> + + <p> + Examination and study of these logging files will provide insight into + the operation of the Spring framework and the application as well as + valuable troubleshooting information should something not work + correctly. + </p> + + <h3>DispatcherServlet</h3> + <p> + The following beans are accessible to the + <span style="font-weight: bold;">DispatcherServlet</span> and are defined + in the PetClinic + <span style="font-weight: bold; font-style: italic;">petclinic-servlet.xml</span> + file. This dispatcher uses these definitions to delegate actual display + and form processing tasks to implementations of the Spring + <span style="font-weight: bold;">org.springframework.web.servlet.mvc.Controller</span> + interface. The <span style="font-weight: bold;">DispatcherServlet</span> + acts as the main application <span style="font-weight: bold;">Front Controller</span> + and is responsible for dispatching all requests to the appropriate + <span style="font-weight: bold;">Controller</span> indirectly through a URL + mapping handler. These <span style="font-weight: bold;">Controllers</span> are + responsible for the mechanics of interaction with the user and ultimately + delegate action to the Business/Persistence Layers. + </p> + + <ul> + <li> + <span style="font-weight: bold; font-style: italic;">messageSource</span> + is a singleton bean that defines a message source for this + <span style="font-weight: bold;">ApplicationContext</span>. Messages are + loaded from localized <span style="font-weight: bold;">"messages_xx"</span> + files in the classpath, such as + <span style="font-weight: bold; font-style: italic;">"/WEB-INF/classes/messages.properties"</span> + or <span style="font-weight: bold; font-style: italic;">"/WEB-INF/classes/messages_de.properties"</span>. + <span style="font-style: italic;">getMessage()</span> calls to this context + will use this source. Child contexts can have their own message sources, + which will inherit all messages from this source and are able to define new + messages and override ones defined in the primary message source. + </li> + <li> + <span style="font-weight: bold; font-style: italic;">InternalResourceViewResolver</span> + is a singleton bean that defines the view mappings used by the dispatcher. + Specifically, logical view names returned by Controllers will be mapped to + physical paths using the configured 'prefix' and 'suffix' properties. For + example, a logical view name of "vets" will be mapped to "/WEB-INF/jsp/vets.jsp". + </li> + <li> + <span style="font-weight: bold; font-style: italic;">SimpleMappingExceptionResolver</span> + is a singleton bean that defines how exceptions are propagated. Exceptions + encountered that are not specified are propagated to the servlet container. + </li> + <li> + <span style="font-weight: bold; font-style: italic;"><context:component-scan ... /></span> + is used to autodetect the controllers in the + <strong>org.springframework.samples.petclinic.web</strong> package, which + are POJOs labeled with the <strong>@Controller</strong> annotation. + <ul> + <li> + <strong>ClinicController</strong> is a singleton, annotation-driven + <em>MultiActionController</em> that is used by the dispatcher to handle + non-form based display tasks. A method is provided to handle each + type of request that is supported. + </li> + <li> + In addition, there are 6 singleton, annotation-driven <em>Form</em> controllers, + which are used to handle the various Search, Add and Edit form processing + tasks for the dispatcher. + </li> + </ul> + </li> + <li> + The form-based controllers within the PetClinic application provide + <strong>@RequestMapping</strong> annotations at the type level for path + mapping URLs and @RequestMapping at the method level for request type + mappings (e.g., GET and POST). In contrast, <strong>ClinicController</strong> + – which is not form-based – provides @RequestMapping only at + the method level for path mapping URLs. + <strong>DefaultAnnotationHandlerMapping</strong> is driven by these + annotations and is enabled by default with Java 5+. + </li> + </ul> + + <h3>Views</h3> + <p> + The <span style="font-weight: bold;">Controllers</span> used by the + dispatcher handle the work flow of the application. The actual display + tasks are delegated by the <span style="font-weight: bold;">Controllers</span> + to implementations of the Spring <span style="font-weight: bold;">View + </span>interface. These <span style="font-weight: bold;">View</span> objects are + themselves beans that can render a particular type of view. The + handling <span style="font-weight: bold;">Controller</span> supplies the + <span style="font-weight: bold;">View</span> with a data model to render. + The data model is provided to the <span style="font-weight: bold;">View</span> + as a <span style="font-weight: bold;">Map</span> of objects. + <span style="font-weight: bold;">Views</span> are only responsible for + rendering a display of the data model and performing any display logic + that is particular to the type of <span style="font-weight: bold;">View</span> + being rendered. Spring provides support for rendering many different types of + views: JSP, XSLT, PDF, Velocity templates, Excel files, and others. By + using a <span style="font-weight: bold;">View</span> mapping strategy, + Spring supplies the developer with a great deal of flexibility in supporting + easily configurable view substitution. + </p> + <p> + <strong>ClinicController</strong> relies on + <strong>RequestToViewNameTranslator</strong> to automatically infer the + logical view name from the incoming request URL; whereas, all <em>Form</em> + controllers in the PetClinic application return logical view names as Strings. + These logical view names are then mapped to physical paths via the + configured <strong>InternalResourceViewResolver</strong> (see the + DispatcherServlet section above for further details). Logical view names that + are prepended with <em>"redirect:"</em> will be resolved to instances + of <strong>RedirectView</strong>, which simply redirects to another URL. + The other resolved <strong>View</strong>s will be instances of + <strong>JstlView</strong>, which provides some handy support for + internationalization & localization in JSP pages that use JSTL. + </p> + + <h3>Messages</h3> + <p> + The <span style="font-weight: bold; font-style: italic;">messages*.properties</span> + files are loaded from the classpath to provide localized messages for + the supported languages. PetClinic supplies a localized + <strong>"welcome"</strong> message as well as localized form entry error + messages in the default (i.e., English) and German properties files. + See the "countries" sample application for a more detailed example of Spring's + support for internationalization. + </p> + + <h3>Presentation Layer classes</h3> + <ul> + <li> + <span style="font-weight: bold; font-style: italic;">org.springframework.samples.petclinic.web.ClinicController</span> + is an annotation-driven, POJO <em>MultiActionController</em> + that is used to handle simple display-oriented URLs. + </li> + <li> + <span style="font-weight: bold; font-style: italic;">org.springframework.samples.petclinic.web.FindOwnersForm</span> + is an annotation-driven, POJO <em>Form</em> controller that is used to search for + <strong>Owner</strong>s by last name. + </li> + <li> + <span style="font-weight: bold; font-style: italic;">org.springframework.samples.petclinic.web.AddOwnerForm</span> + is an annotation-driven, POJO <em>Form</em> controller that is used to add a new <strong>Owner</strong> + to the system. + </li> + <li> + <span style="font-weight: bold; font-style: italic;">org.springframework.samples.petclinic.web.EditOwnerForm</span> + is an annotation-driven, POJO <em>Form</em> controller that is used to edit an existing <strong>Owner</strong>. + A copy of the existing <strong>Owner</strong> is used for editing. + </li> + <li> + <span style="font-weight: bold; font-style: italic;">org.springframework.samples.petclinic.web.AddPetForm</span> + is an annotation-driven, POJO <em>Form</em> controller that is used to add a new <strong>Pet</strong> + to an existing <strong>Owner</strong>. + </li> + <li> + <span style="font-weight: bold; font-style: italic;">org.springframework.samples.petclinic.web.EditPetForm</span> + is an annotation-driven, POJO <em>Form</em> controller that is used to edit an existing <strong>Pet</strong>. + A copy of the existing <strong>Pet</strong> is used for editing. + </li> + <li> + <span style="font-weight: bold; font-style: italic;">org.springframework.samples.petclinic.web.AddVisitForm</span> + is an annotation-driven, POJO <em>Form</em> controller that is used to add a new <strong>Visit</strong> + to an existing <strong>Pet</strong>. + </li> + </ul> + + <h3>Logical Views & Implemented Use Cases</h3> + + <ul> + <li><span style="font-weight: bold; font-style: italic;">welcome</span> is + the "home" screen. It provides links to display a list of all vets, + find an owner, or view documentation.</li> + <li><span style="font-weight: bold; font-style: italic;">vets</span> displays + all vets and their specialties.</li> + <li><span style="font-weight: bold; font-style: italic;">findOwners</span> + is used to find owners by last name.</li> + <li><span style="font-weight: bold; font-style: italic;">findOwnersRedirect</span> + redirects to findOwner.</li> + <li><span style="font-weight: bold; font-style: italic;">selectOwner</span> + allows user to select from a list of multiple owners with the same last + name.</li> + <li><span style="font-weight: bold; font-style: italic;">owner</span> displays + a owner's data and a list of the owner's pets and their data.</li> + <li><span style="font-weight: bold; font-style: italic;">ownerRedirect</span> + redirects to owner.</li> + <li><span style="font-weight: bold; font-style: italic;">owner</span> supports + <span style="font-weight: bold; font-style: italic;">AddOwnerForm</span> + and <span style="font-weight: bold; font-style: italic;">EditOwnerForm</span></li> + <li><span style="font-weight: bold; font-style: italic;">pet</span> supports + <span style="font-weight: bold; font-style: italic;">AddPetForm</span> + and <span style="font-weight: bold; font-style: italic;">web.EditPetForm</span></li> + <li><span style="font-weight: bold; font-style: italic;">visit</span> supports + <span style="font-weight: bold; font-style: italic;">AddVisitForm</span></li> + <li><span style="font-weight: bold; font-style: italic;">dataAccessFailure</span> + displays a stacktrace</li> + </ul> + + <h3>Java Server Pages</h3> + + <ul> + <li><span style="font-weight: bold; font-style: italic;">index.jsp</span> + redirects to the "welcome" page.</li> + <li><span style="font-weight: bold; font-style: italic;">includes.jsp</span> is + statically included in <span style="text-decoration: underline;">all</span> + JSP's used in the application. It specifies the taglibs that are in use.</li> + <li><span style="font-weight: bold; font-style: italic;">header.jsp</span> and + <span style="font-style: italic; font-weight: bold;">footer.jsp</span> + display info common to virtually all pages. Spring also supplies + support for the integration of <a href="http://www.lifl.fr/%7Edumoulin/tiles">Tiles</a> + (included in Struts) but this is not used in PetClinic.</li> + <li><span style="font-weight: bold; font-style: italic;">dataAccessFailure.jsp</span> + is the error page configured via + <span style="font-weight: bold; font-style: italic;">SimpleMappingExceptionResolver</span>, + which displays a stack trace and normally wouldn't be used in a production + version of an application. It can be seen in action by entering a URL of + "editOwner.do" or "editPet.do" with an invalid request parameter, for example: + <a href="http://localhost:8080/org.springframework.samples.petclinic/owner.do?ownerId=-1">/petclinic/owner.do?ownerId=-1</a>. + The handlers for these URLs normally expect to see a respective "ownerId" or "petId" + request parameter corresponding to an Owner or Pet in the database. Thus, + these handlers will throw a <strong>DataAccessException</strong> when such + a request parameter is provided which references a non-existing entity.</li> + <li><span style="font-weight: bold; font-style: italic;">uncaughtException.jsp</span> + is the <span style="font-weight: bold; font-style: italic;">web.xml</span> configured + "error-page". It displays a stack trace and normally wouldn't be used + in a production version of an application. It can be seen in action by + entering a URL of "editOwner.do" or "editPet.do" without a valid request + parameter, for example: + <a href="http://localhost:8080/org.springframework.samples.petclinic/owner.do">/petclinic/owner.do</a>. + The handlers for these URLs normally expect to see a respective "ownerId" or "petId" + request parameter and throw a <strong>ServletException</strong> when such + a request parameter is not found.</li> + <li><span style="font-weight: bold; font-style: italic;">welcome.jsp</span> implements + <span style="font-weight: bold; font-style: italic;">welcome</span>.</li> + <li><span style="font-weight: bold; font-style: italic;">vets.jsp</span> implements + <span style="font-weight: bold; font-style: italic;">vets</span>.</li> + <li><span style="font-weight: bold; font-style: italic;">findOwners.jsp</span> implements + <span style="font-weight: bold; font-style: italic;">findOwners</span>.</li> + <li><span style="font-weight: bold; font-style: italic;">owners.jsp</span> implements + <span style="font-weight: bold; font-style: italic;">selectOwner</span>.</li> + <li><span style="font-weight: bold; font-style: italic;">owner.jsp</span> implements + <span style="font-weight: bold; font-style: italic;">owner</span>.</li> + <li><span style="font-weight: bold; font-style: italic;">ownerForm.jsp</span> implements + <span style="font-weight: bold; font-style: italic;">ownerForm</span>.</li> + <li><span style="font-weight: bold; font-style: italic;">petForm.jsp</span> implements + <span style="font-weight: bold; font-style: italic;">petForm</span>.</li> + <li><span style="font-weight: bold; font-style: italic;">visitForm.jsp</span> implements + <span style="font-weight: bold; font-style: italic;">visitForm</span>.</li> + </ul> + + <p> + The following items should be noted regarding the web application + implementation design: + </p> + + <ol> + <li>all JSP's are stored under <span style="font-weight: bold; font-style: italic;">/WEB-INF/jsp</span> except + for <span style="font-weight: bold; font-style: italic;">index.jsp</span> which + is the configured "welcome-file"</li> + <li>The use of JSP technology in the application is not exposed to + the user, i.e., the end user never sees a URL ending in + <span style="font-weight: bold; font-style: italic;">".jsp".</span></li> + <li>By convention, all URL's in the application ending in + <span style="font-weight: bold; font-style: italic;">".do"</span> are + handled by web application controllers. Static html pages ending in + <span style="font-weight: bold; font-style: italic;">".html"</span>, such as + Javadoc, will be directly served to the end user.</li> + <li>The results of all form entries are handled using browser round + trip redirection to minimize possible end user confusion.</li> + <li>All pages are extremely simple JSP implementations that focus + only on providing the necessary functionality.</li> + <li>References to <span style="font-weight: bold;">Entity</span> objects + are passed around in the application by supplying the object's <code>ID</code> + as a request parameter.</li> + </ol> + + <h3>Testing</h3> + <ul> + <li><span style="font-weight: bold; font-style: italic;">org.springframework.samples.petclinic.OwnerTests</span> + is a simple JUnit 4 based TestCase that supports Business Rule #1.</li> + <li><span style="font-weight: bold; font-style: italic;">org.springframework.samples.petclinic.AbstractClinicTests</span> + is a JUnit 4 based TestCase requiring a live database connection that is + used to confirm correct operation of the database access objects in the + various implementations of the <strong>Clinic</strong> interface. + "AbstractClinicTests-context.xml" declares a common + <strong>javax.sql.DataSource</strong>. Subclasses specify additional + context locations which declare a + <strong>org.springframework.transaction.PlatformTransactionManager</strong> + and a concrete implementation of <strong>Clinic</strong>. + <p> + AbstractClinicTests extends + <strong>AbstractTransactionalJUnit4SpringContextTests</strong>, + one of the valuable testing support classes provided by the + <em>Spring TestContext Framework</em> found in the + <code>org.springframework.test.context</code> package. The + annotation-driven configuration used here represents best practice for + integration tests with Spring. Note, however, that + AbstractTransactionalJUnit4SpringContextTests serves only as a convenience + for extension. For example, if you do not wish for your test classes to be + tied to a Spring-specific class hierarchy, you may configure your tests with + annotations such as <strong>@ContextConfiguration</strong>, + <strong>@TestExecutionListeners</strong>, <strong>@Transactional</strong>, etc. + </p> + <p> + AbstractClinicTests and its subclasses benefit from the following services + provided by the Spring TestContext Framework: + </p> + <ul> + <li><strong>Spring IoC container caching</strong> which spares us + unnecessary set up time between test execution.</li> + <li><strong>Dependency Injection</strong> of test fixture instances, + meaning that we don't need to perform application context lookups. See the + use of <strong>@Autowired</strong> on the <code>clinic</code> instance + variable, which uses autowiring <em>by type</em>. As an alternative, we + could annotate <code>clinic</code> with JSR 250's + <strong>@Resource</strong> to achieve dependency injection <em>by name</em>. + <em>(see: <strong>@ContextConfiguration</strong>, + <strong>DependencyInjectionTestExecutionListener</strong>)</em></li> + <li><strong>Transaction management</strong>, meaning each test method is + executed in its own transaction, which is automatically rolled back by + default. Thus, even if tests insert or otherwise change database state, there + is no need for a teardown or cleanup script. + <em>(see: <strong>@TransactionConfiguration</strong>, + <strong>@Transactional</strong>, <strong>TransactionalTestExecutionListener</strong>)</em></li> + <li><strong>Useful inherited protected fields</strong>, such as a + <strong>SimpleJdbcTemplate</strong> that can be used to verify + database state after test operations or to verify the results of + queries performed by application code. An + <strong>ApplicationContext</strong> is also inherited and can be + used for explicit bean lookup if necessary. + <em>(see: <strong>AbstractJUnit4SpringContextTests</strong>, + <strong>AbstractTransactionalJUnit4SpringContextTests</strong>)</em></li> + </ul> + <p> + The <em>Spring TestContext Framework</em> and related unit and + integration testing support classes are shipped in + <code>spring-test.jar</code>. + </p> + + </li> + </ul> + + <h3>Downloads</h3> + <ul> + <li>Download and install the + <a href="http://www.springsource.com/download/community?project=Spring%20Framework" target="_blank">Spring Framework</a> + (the PetClinic sample application is included)</li> + <li>Download and install a <a href="http://java.sun.com/" target="_blank">Java</a> + Software Developer Kit, version 1.5 or later</li> + <li>Download and install <a href="http://maven.apache.org" target="_blank">Apache Maven</a>, + preferably version 2.0.10 or later</li> + <li>Download and install <a href="http://ant.apache.org" target="_blank">Apache Ant</a>, + preferably version 1.7.1 or later</li> + <li>Download and install <a href="http://jakarta.apache.org/tomcat/index.html" target="_blank">Apache Tomcat</a>, + preferably version 6.0.20 or later</li> + <li>Download and install <a href="http://dev.mysql.com/downloads/" target="_blank">MySQL</a>, + preferably version 5.1.x or later (optional)</li> + <li><a href="http://hsqldb.sourceforge.net/" target="_blank">Hypersonic SQL</a>, and + <a href="http://hibernate.org/" target="_blank">Hibernate</a> are provided with the + application.</li> + <li>PetClinic and Spring use the <a href="http://www.apache.org/" target="_blank">Apache</a> + <a href="http://commons.apache.org/logging/" target="_blank">Commons Logging</a> + and <a href="http://logging.apache.org/log4j/" target="_blank">log4j</a> + packages.</li> + </ul> + + <h3>Maven Setup</h3> + <p> + Make sure that the Maven executable is in your command shell path. + </p> + + <h3>MYSQL Setup (optional)</h3> + <p> + Add the PetClinic database to a running MySQL server by following the + explicit instructions found in + <span style="font-weight: bold; font-style: italic;">db/mysql/petclinic_db_setup_mysql.txt</span>. + PetClinic expects by default to be able to access the server via the + standard port <code>3306</code>. To use a different port, it will be + necessary to change the PetClinic Database Setup. + </p> + + <h3>PetClinic Database Setup</h3> + <p> + To use a Java EE server supplied connection-pooled data source with + Tomcat, it will be necessary to use and possibly edit the appropriate + context definition file for the petclinic webapp. To use it, deploy a + copy of the appropriate context definition file in Tomcat's webapps + directory and restart the server. Consult the Tomcat log files if + something goes wrong when starting either Tomcat or the PetClinic + application. The context files are named + <span style="font-weight: bold; font-style: italic;">petclinic_tomcat_*.xml</span>, + where <strong>*</strong> is either "hsqldb" or "mysql". + There is a context file supplied for each + database in its respective directory. There is also a context file + <span style="font-weight: bold; font-style: italic;">db/petclinic_tomcat_all.xml</span> + that will provide a JNDI connection-pooled DataSource for all supported + databases. Should you use this file, you must of course make sure that all the + database servers are running when you restart Tomcat. + </p> + + <p> + <a name="dbNotes"></a> + <span style="font-weight: bold;">NOTES:</span> + </p> + + <ol> + <li>Should you deploy one of the context files or define a context in + Tomcat's <span style="font-weight: bold; font-style: italic;">server.xml</span>, + Tomcat will not automatically deploy the webapp from the + <span style="font-weight: bold; font-style: italic;">petclinic.war</span> file. + The webapp will then need to be manually extracted to the target + directory.</li> + <li>The context files will also configure logging to supply a + separate log file for the petclinic context. This will separate the + container logging for petclinic from that of the other webapps. This + should not be confused with the application log file provided through + <span style="font-weight: bold;">log4j.</span></li> + <li>An Ant script (<span style="font-weight: bold; font-style: italic;">db/build.xml</span>) + has been provided that can be used to re-initialize either database. To + select or configure the data source and database used for the webapp and + for testing, you will need to edit the following files: + <ul> + <li><span style="font-weight: bold; font-style: italic;">src/main/webapp/WEB-INF/applicationContext-*.xml</span>: + for configuring the DataSource in the webapp</li> + <li><span style="font-weight: bold; font-style: italic;">src/main/resources/jdbc.properties</span>: + for configuring JDBC connection settings for both the webapp and testing</li> + <li><span style="font-weight: bold; font-style: italic;">build.properties</span>: + for running the "tests" target in Ant</li> + </ul> + </li> + </ol> + + <h3>Building the PetClinic Application</h3> + <p> + Open a command line shell and navigate to the directory containing + PetClinic. Make sure the database is running and execute + "mvn clean package". This will run clean and compile everything + and execute the tests, including integration tests against an in-memory + database. + </p> + + <h3>Deploying the PetClinic Application</h3> + <p> + Deploy the web application to the server in the usual way (see + <a href="#dbNotes">notes</a> regarding database setup). If you need + instructions for web application deployment, see the Tomcat + documentation for details. The Web Application aRchive file is + <span style="font-weight: bold; font-style: italic;">org.springframework.samples.petclinic.war</span> + and can be found in the + <span style="font-weight: bold; font-style: italic;">target/artifacts</span> directory. + </p> + + <h3>Using the PetClinic Application</h3> + <p> + Make sure the PetClinic web application is running and browse to + <a href="http://localhost:8080/org.springframework.samples.petclinic/">http://localhost:8080/org.springframework.samples.petclinic/</a>. + </p> + + <table class="footer"> + <tr> + <td><a href="../welcome.do">Home</a></td> + <td align="right"><img src="../images/springsource-logo.png"/></td> + </tr> + </table> + </div> +</body> + +</html> \ No newline at end of file diff --git a/src/main/webapp/images/banner-graphic.png b/src/main/webapp/images/banner-graphic.png new file mode 100644 index 0000000000000000000000000000000000000000..e6d01d5885266efbf4dc99431576a13dee5725e4 Binary files /dev/null and b/src/main/webapp/images/banner-graphic.png differ diff --git a/src/main/webapp/images/bullet-arrow.png b/src/main/webapp/images/bullet-arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..5909c25b397bb899de0dc010cb1b564b90175893 Binary files /dev/null and b/src/main/webapp/images/bullet-arrow.png differ diff --git a/src/main/webapp/images/pets.png b/src/main/webapp/images/pets.png new file mode 100644 index 0000000000000000000000000000000000000000..0fe63c282b189268e53aa34cf523f32ad7772401 Binary files /dev/null and b/src/main/webapp/images/pets.png differ diff --git a/src/main/webapp/images/springsource-logo.png b/src/main/webapp/images/springsource-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..e170f8abf778b24ed1de11dad6f1444e849269ba Binary files /dev/null and b/src/main/webapp/images/springsource-logo.png differ diff --git a/src/main/webapp/images/submit-bg.png b/src/main/webapp/images/submit-bg.png new file mode 100644 index 0000000000000000000000000000000000000000..10a94371b642251703b0c2439d5e62578f8e6e5a Binary files /dev/null and b/src/main/webapp/images/submit-bg.png differ diff --git a/src/main/webapp/styles/petclinic.css b/src/main/webapp/styles/petclinic.css new file mode 100644 index 0000000000000000000000000000000000000000..e12f96af7807f9e1baadbffcaa75a7e1ead6abba --- /dev/null +++ b/src/main/webapp/styles/petclinic.css @@ -0,0 +1,234 @@ +/* main elements */ + +body,div,td { + font-family: Arial, Helvetica, sans-serif; + font-size: 12px; + color: #666; +} + +body { + background-color: #fff; + background-image: url(../images/banner-graphic.png); + background-position: top center; + background-repeat: no-repeat; + text-align: center; + min-width: 600px; + margin-top: 60px; + margin-left: auto; + margin-right: auto; +} + +div { + margin: 5px 25px 5px 25px; + text-align: left; +} + +/* header and footer elements */ + +#main { + margin:0 auto; + position:relative; + top: 35px; + left:0px; + width:560px; + text-align:left; +} + +.footer { + background:#fff; + border:none; + margin-top:20px; + border-top:1px solid #999999; + width:100%; +} + +.footer td {color:#999999;} + +.footer a:link {color: #7db223;} + + +/* text styles */ + +h1,h2,h3 { + font-family: Helvetica, sans-serif; + color: #7db223; +} + +h1 { + font-size: 20px; + line-height: 26px; +} + +h2 { + font-size: 18px; + line-height: 24px; +} + +h3 { + font-size: 15px; + line-height: 21px; + color:#555; +} + +h4 { + font-size: 14px; + line-height: 20px; +} + +.errors { + color: red; + font-weight: bold; +} + +a { + text-decoration: underline; + font-size: 13px; +} + +a:link { + color: #7db223; +} + +a:hover { + color: #456314; +} + +a:active { + color: #7db223; +} + +a:visited { + color: #7db223; +} + +ul { + list-style: disc url(../images/bullet-arrow.png); +} + +li { + padding-top: 5px; + text-align: left; +} + +li ul { + list-style: square url(../images/bullet-arrow.png); +} + +li ul li ul { + list-style: circle none; +} + +/* table elements */ + +table { + background: #d6e2c3; + margin: 3px 0 0 0; + border: 4px solid #d6e2c3; + border-collapse: collapse; +} + +table table { + margin: -5px 0; + border: 0px solid #e0e7d3; + width: 100%; +} + +table td,table th { + padding: 8px; +} + +table th { + font-size: 12px; + text-align: left; + font-weight: bold; +} + +table thead { + font-weight: bold; + font-style: italic; + background-color: #c2ceaf; +} + +table a:link {color: #303030;} + +caption { + caption-side: top; + width: auto; + text-align: left; + font-size: 12px; + color: #848f73; + padding-bottom: 4px; +} + +fieldset { + background: #e0e7d3; + padding: 8px; + padding-bottom: 22px; + border: none; + width: 560px; +} + +fieldset label { + width: 70px; + float: left; + margin-top: 1.7em; + margin-left: 20px; +} + +fieldset textfield { + margin: 3px; + height: 20px; + background: #e0e7d3; +} + +fieldset textarea { + margin: 3px; + height: 165px; + background: #e0e7d3; +} + +fieldset input { + margin: 3px; + height: 20px; + background: #e0e7d3; +} + +fieldset table { + width: 100%; +} + +fieldset th { + padding-left: 25px; +} + +.table-buttons { + background-color:#fff; + border:none; +} + +.table-buttons td { + border:none; +} + +.submit input { + background:url(../images/submit-bg.png) repeat-x; + border: 2px outset #d7b9c9; + color:#383838; + padding:2px 10px; + font-size:11px; + text-transform:uppercase; + font-weight:bold; +} + +.updated { + background:#ecf1e5; + font-size:11px; + margin-left:2px; + border:4px solid #ecf1e5; +} + +.updated td { + padding:2px 8px; + font-size:11px; + color:#888888; +} diff --git a/src/test/java/org/springframework/samples/petclinic/AbstractClinicTests.java b/src/test/java/org/springframework/samples/petclinic/AbstractClinicTests.java new file mode 100644 index 0000000000000000000000000000000000000000..a698691867a90b98be6dd03edca171e71f1790f2 --- /dev/null +++ b/src/test/java/org/springframework/samples/petclinic/AbstractClinicTests.java @@ -0,0 +1,224 @@ +package org.springframework.samples.petclinic; + +import java.util.Collection; +import java.util.Date; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import org.junit.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.samples.petclinic.util.EntityUtils; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests; + +/** + * <p> + * Base class for {@link Clinic} integration tests. + * </p> + * <p> + * "AbstractClinicTests-context.xml" declares a common + * {@link javax.sql.DataSource DataSource}. Subclasses should specify + * additional context locations which declare a + * {@link org.springframework.transaction.PlatformTransactionManager PlatformTransactionManager} + * and a concrete implementation of {@link Clinic}. + * </p> + * <p> + * This class extends {@link AbstractTransactionalJUnit4SpringContextTests}, + * one of the valuable testing support classes provided by the + * <em>Spring TestContext Framework</em> found in the + * <code>org.springframework.test.context</code> package. The + * annotation-driven configuration used here represents best practice for + * integration tests with Spring. Note, however, that + * AbstractTransactionalJUnit4SpringContextTests serves only as a convenience + * for extension. For example, if you do not wish for your test classes to be + * tied to a Spring-specific class hierarchy, you may configure your tests with + * annotations such as {@link ContextConfiguration @ContextConfiguration}, + * {@link org.springframework.test.context.TestExecutionListeners @TestExecutionListeners}, + * {@link org.springframework.transaction.annotation.Transactional @Transactional}, + * etc. + * </p> + * <p> + * AbstractClinicTests and its subclasses benefit from the following services + * provided by the Spring TestContext Framework: + * </p> + * <ul> + * <li><strong>Spring IoC container caching</strong> which spares us + * unnecessary set up time between test execution.</li> + * <li><strong>Dependency Injection</strong> of test fixture instances, + * meaning that we don't need to perform application context lookups. See the + * use of {@link Autowired @Autowired} on the <code>clinic</code> instance + * variable, which uses autowiring <em>by type</em>. As an alternative, we + * could annotate <code>clinic</code> with + * {@link javax.annotation.Resource @Resource} to achieve dependency injection + * <em>by name</em>. + * <em>(see: {@link ContextConfiguration @ContextConfiguration}, + * {@link org.springframework.test.context.support.DependencyInjectionTestExecutionListener DependencyInjectionTestExecutionListener})</em></li> + * <li><strong>Transaction management</strong>, meaning each test method is + * executed in its own transaction, which is automatically rolled back by + * default. Thus, even if tests insert or otherwise change database state, there + * is no need for a teardown or cleanup script. + * <em>(see: {@link org.springframework.test.context.transaction.TransactionConfiguration @TransactionConfiguration}, + * {@link org.springframework.transaction.annotation.Transactional @Transactional}, + * {@link org.springframework.test.context.transaction.TransactionalTestExecutionListener TransactionalTestExecutionListener})</em></li> + * <li><strong>Useful inherited protected fields</strong>, such as a + * {@link org.springframework.jdbc.core.simple.SimpleJdbcTemplate SimpleJdbcTemplate} + * that can be used to verify database state after test operations or to verify + * the results of queries performed by application code. An + * {@link org.springframework.context.ApplicationContext ApplicationContext} is + * also inherited and can be used for explicit bean lookup if necessary. + * <em>(see: {@link org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests AbstractJUnit4SpringContextTests}, + * {@link AbstractTransactionalJUnit4SpringContextTests})</em></li> + * </ul> + * <p> + * The Spring TestContext Framework and related unit and integration testing + * support classes are shipped in <code>spring-test.jar</code>. + * </p> + * + * @author Ken Krebs + * @author Rod Johnson + * @author Juergen Hoeller + * @author Sam Brannen + */ +@ContextConfiguration +public abstract class AbstractClinicTests extends AbstractTransactionalJUnit4SpringContextTests { + + @Autowired + protected Clinic clinic; + + + @Test + public void getVets() { + Collection<Vet> vets = this.clinic.getVets(); + // Use the inherited countRowsInTable() convenience method (from + // AbstractTransactionalJUnit4SpringContextTests) to verify the results. + assertEquals("JDBC query must show the same number of vets", super.countRowsInTable("VETS"), vets.size()); + Vet v1 = EntityUtils.getById(vets, Vet.class, 2); + assertEquals("Leary", v1.getLastName()); + assertEquals(1, v1.getNrOfSpecialties()); + assertEquals("radiology", (v1.getSpecialties().get(0)).getName()); + Vet v2 = EntityUtils.getById(vets, Vet.class, 3); + assertEquals("Douglas", v2.getLastName()); + assertEquals(2, v2.getNrOfSpecialties()); + assertEquals("dentistry", (v2.getSpecialties().get(0)).getName()); + assertEquals("surgery", (v2.getSpecialties().get(1)).getName()); + } + + @Test + public void getPetTypes() { + Collection<PetType> petTypes = this.clinic.getPetTypes(); + assertEquals("JDBC query must show the same number of pet types", super.countRowsInTable("TYPES"), + petTypes.size()); + PetType t1 = EntityUtils.getById(petTypes, PetType.class, 1); + assertEquals("cat", t1.getName()); + PetType t4 = EntityUtils.getById(petTypes, PetType.class, 4); + assertEquals("snake", t4.getName()); + } + + @Test + public void findOwners() { + Collection<Owner> owners = this.clinic.findOwners("Davis"); + assertEquals(2, owners.size()); + owners = this.clinic.findOwners("Daviss"); + assertEquals(0, owners.size()); + } + + @Test + public void loadOwner() { + Owner o1 = this.clinic.loadOwner(1); + assertTrue(o1.getLastName().startsWith("Franklin")); + Owner o10 = this.clinic.loadOwner(10); + assertEquals("Carlos", o10.getFirstName()); + + // XXX: Add programmatic support for ending transactions with the + // TestContext Framework. + + // Check lazy loading, by ending the transaction: + // endTransaction(); + + // Now Owners are "disconnected" from the data store. + // We might need to touch this collection if we switched to lazy loading + // in mapping files, but this test would pick this up. + o1.getPets(); + } + + @Test + public void insertOwner() { + Collection<Owner> owners = this.clinic.findOwners("Schultz"); + int found = owners.size(); + Owner owner = new Owner(); + owner.setLastName("Schultz"); + this.clinic.storeOwner(owner); + // assertTrue(!owner.isNew()); -- NOT TRUE FOR TOPLINK (before commit) + owners = this.clinic.findOwners("Schultz"); + assertEquals("Verifying number of owners after inserting a new one.", found + 1, owners.size()); + } + + @Test + public void updateOwner() throws Exception { + Owner o1 = this.clinic.loadOwner(1); + String old = o1.getLastName(); + o1.setLastName(old + "X"); + this.clinic.storeOwner(o1); + o1 = this.clinic.loadOwner(1); + assertEquals(old + "X", o1.getLastName()); + } + + @Test + public void loadPet() { + Collection<PetType> types = this.clinic.getPetTypes(); + Pet p7 = this.clinic.loadPet(7); + assertTrue(p7.getName().startsWith("Samantha")); + assertEquals(EntityUtils.getById(types, PetType.class, 1).getId(), p7.getType().getId()); + assertEquals("Jean", p7.getOwner().getFirstName()); + Pet p6 = this.clinic.loadPet(6); + assertEquals("George", p6.getName()); + assertEquals(EntityUtils.getById(types, PetType.class, 4).getId(), p6.getType().getId()); + assertEquals("Peter", p6.getOwner().getFirstName()); + } + + @Test + public void insertPet() { + Owner o6 = this.clinic.loadOwner(6); + int found = o6.getPets().size(); + Pet pet = new Pet(); + pet.setName("bowser"); + Collection<PetType> types = this.clinic.getPetTypes(); + pet.setType(EntityUtils.getById(types, PetType.class, 2)); + pet.setBirthDate(new Date()); + o6.addPet(pet); + assertEquals(found + 1, o6.getPets().size()); + // both storePet and storeOwner are necessary to cover all ORM tools + this.clinic.storePet(pet); + this.clinic.storeOwner(o6); + // assertTrue(!pet.isNew()); -- NOT TRUE FOR TOPLINK (before commit) + o6 = this.clinic.loadOwner(6); + assertEquals(found + 1, o6.getPets().size()); + } + + @Test + public void updatePet() throws Exception { + Pet p7 = this.clinic.loadPet(7); + String old = p7.getName(); + p7.setName(old + "X"); + this.clinic.storePet(p7); + p7 = this.clinic.loadPet(7); + assertEquals(old + "X", p7.getName()); + } + + @Test + public void insertVisit() { + Pet p7 = this.clinic.loadPet(7); + int found = p7.getVisits().size(); + Visit visit = new Visit(); + p7.addVisit(visit); + visit.setDescription("test"); + // both storeVisit and storePet are necessary to cover all ORM tools + this.clinic.storeVisit(visit); + this.clinic.storePet(p7); + // assertTrue(!visit.isNew()); -- NOT TRUE FOR TOPLINK (before commit) + p7 = this.clinic.loadPet(7); + assertEquals(found + 1, p7.getVisits().size()); + } + +} diff --git a/src/test/java/org/springframework/samples/petclinic/OwnerTests.java b/src/test/java/org/springframework/samples/petclinic/OwnerTests.java new file mode 100644 index 0000000000000000000000000000000000000000..84b5f62c6c25acc504340a7da38ee50dafee5050 --- /dev/null +++ b/src/test/java/org/springframework/samples/petclinic/OwnerTests.java @@ -0,0 +1,27 @@ +package org.springframework.samples.petclinic; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import org.junit.Test; + +/** + * JUnit test for the {@link Owner} class. + * + * @author Ken Krebs + */ +public class OwnerTests { + + @Test + public void testHasPet() { + Owner owner = new Owner(); + Pet fido = new Pet(); + fido.setName("Fido"); + assertNull(owner.getPet("Fido")); + assertNull(owner.getPet("fido")); + owner.addPet(fido); + assertEquals(fido, owner.getPet("Fido")); + assertEquals(fido, owner.getPet("fido")); + } + +} diff --git a/src/test/java/org/springframework/samples/petclinic/PetClinicTestSuite.java b/src/test/java/org/springframework/samples/petclinic/PetClinicTestSuite.java new file mode 100644 index 0000000000000000000000000000000000000000..d561993993793a59cc2c130be1ee7c81a46698ca --- /dev/null +++ b/src/test/java/org/springframework/samples/petclinic/PetClinicTestSuite.java @@ -0,0 +1,29 @@ +package org.springframework.samples.petclinic; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; +import org.springframework.samples.petclinic.hibernate.HibernateClinicTests; +import org.springframework.samples.petclinic.jdbc.SimpleJdbcClinicTests; +import org.springframework.samples.petclinic.jpa.EntityManagerClinicTests; +import org.springframework.samples.petclinic.jpa.HibernateEntityManagerClinicTests; +import org.springframework.samples.petclinic.jpa.OpenJpaEntityManagerClinicTests; +import org.springframework.samples.petclinic.web.VisitsAtomViewTest; + +/** + * JUnit 4 based test suite for all PetClinic tests. + * + * @author Sam Brannen + */ +@RunWith(Suite.class) +@SuiteClasses({ + OwnerTests.class, + SimpleJdbcClinicTests.class, + HibernateClinicTests.class, + EntityManagerClinicTests.class, + HibernateEntityManagerClinicTests.class, + OpenJpaEntityManagerClinicTests.class, + VisitsAtomViewTest.class +}) +public class PetClinicTestSuite { +} diff --git a/src/test/java/org/springframework/samples/petclinic/hibernate/HibernateClinicTests.java b/src/test/java/org/springframework/samples/petclinic/hibernate/HibernateClinicTests.java new file mode 100644 index 0000000000000000000000000000000000000000..3c275c820b22c9d168063d1fc6aa8b28ffea8b00 --- /dev/null +++ b/src/test/java/org/springframework/samples/petclinic/hibernate/HibernateClinicTests.java @@ -0,0 +1,20 @@ +package org.springframework.samples.petclinic.hibernate; + +import org.springframework.samples.petclinic.AbstractClinicTests; +import org.springframework.test.context.ContextConfiguration; + +/** + * <p> + * Integration tests for the {@link HibernateClinic} implementation. + * </p> + * <p> + * "HibernateClinicTests-context.xml" determines the actual beans to test. + * </p> + * + * @author Juergen Hoeller + * @author Sam Brannen + */ +@ContextConfiguration +public class HibernateClinicTests extends AbstractClinicTests { + +} diff --git a/src/test/java/org/springframework/samples/petclinic/jdbc/SimpleJdbcClinicTests.java b/src/test/java/org/springframework/samples/petclinic/jdbc/SimpleJdbcClinicTests.java new file mode 100644 index 0000000000000000000000000000000000000000..709100d1a12db583b5a3c9b5cb4afbbd8b3949a5 --- /dev/null +++ b/src/test/java/org/springframework/samples/petclinic/jdbc/SimpleJdbcClinicTests.java @@ -0,0 +1,19 @@ +package org.springframework.samples.petclinic.jdbc; + +import org.springframework.samples.petclinic.AbstractClinicTests; +import org.springframework.test.context.ContextConfiguration; + +/** + * <p> + * Integration tests for the {@link SimpleJdbcClinic} implementation. + * </p> + * <p> + * "SimpleJdbcClinicTests-context.xml" determines the actual beans to test. + * </p> + * + * @author Thomas Risberg + */ +@ContextConfiguration +public class SimpleJdbcClinicTests extends AbstractClinicTests { + +} diff --git a/src/test/java/org/springframework/samples/petclinic/jpa/AbstractJpaClinicTests.java b/src/test/java/org/springframework/samples/petclinic/jpa/AbstractJpaClinicTests.java new file mode 100644 index 0000000000000000000000000000000000000000..251af819d16e09ff4707034db918881d356d9ee0 --- /dev/null +++ b/src/test/java/org/springframework/samples/petclinic/jpa/AbstractJpaClinicTests.java @@ -0,0 +1,199 @@ + +package org.springframework.samples.petclinic.jpa; + +import java.util.Collection; +import java.util.Date; + +import javax.persistence.EntityManager; + +import org.springframework.jdbc.core.simple.SimpleJdbcTemplate; +import org.springframework.samples.petclinic.Clinic; +import org.springframework.samples.petclinic.Owner; +import org.springframework.samples.petclinic.Pet; +import org.springframework.samples.petclinic.PetType; +import org.springframework.samples.petclinic.Vet; +import org.springframework.samples.petclinic.Visit; +import org.springframework.samples.petclinic.util.EntityUtils; +import org.springframework.test.annotation.ExpectedException; +import org.springframework.test.jpa.AbstractJpaTests; + +/** + * <p> + * This class extends {@link AbstractJpaTests}, one of the valuable test + * superclasses provided in the <code>org.springframework.test</code> package. + * This represents best practice for integration tests with Spring for JPA based + * tests which require <em>shadow class loading</em>. For all other types of + * integration testing, the <em>Spring TestContext Framework</em> is + * preferred. + * </p> + * <p> + * AbstractJpaTests and its superclasses provide the following services: + * <ul> + * <li>Injects test dependencies, meaning that we don't need to perform + * application context lookups. See the setClinic() method. Injection uses + * autowiring by type.</li> + * <li>Executes each test method in its own transaction, which is automatically + * rolled back by default. This means that even if tests insert or otherwise + * change database state, there is no need for a teardown or cleanup script.</li> + * <li>Provides useful inherited protected fields, such as a + * {@link SimpleJdbcTemplate} that can be used to verify database state after + * test operations, or verify the results of queries performed by application + * code. Alternatively, you can use protected convenience methods such as + * {@link #countRowsInTable(String)}, {@link #deleteFromTables(String[])}, + * etc. An ApplicationContext is also inherited, and can be used for explicit + * lookup if necessary.</li> + * </ul> + * <p> + * {@link AbstractJpaTests} and related classes are shipped in + * <code>spring-test.jar</code>. + * </p> + * + * @author Rod Johnson + * @author Sam Brannen + * @see AbstractJpaTests + */ +public abstract class AbstractJpaClinicTests extends AbstractJpaTests { + + protected Clinic clinic; + + + /** + * This method is provided to set the Clinic instance being tested by the + * Dependency Injection injection behaviour of the superclass from the + * <code>org.springframework.test</code> package. + * + * @param clinic clinic to test + */ + public void setClinic(Clinic clinic) { + this.clinic = clinic; + } + + @ExpectedException(IllegalArgumentException.class) + public void testBogusJpql() { + this.sharedEntityManager.createQuery("SELECT RUBBISH FROM RUBBISH HEAP").executeUpdate(); + } + + public void testApplicationManaged() { + EntityManager appManaged = this.entityManagerFactory.createEntityManager(); + appManaged.joinTransaction(); + } + + public void testGetVets() { + Collection<Vet> vets = this.clinic.getVets(); + // Use the inherited countRowsInTable() convenience method (from + // AbstractTransactionalDataSourceSpringContextTests) to verify the + // results. + assertEquals("JDBC query must show the same number of vets", super.countRowsInTable("VETS"), vets.size()); + Vet v1 = EntityUtils.getById(vets, Vet.class, 2); + assertEquals("Leary", v1.getLastName()); + assertEquals(1, v1.getNrOfSpecialties()); + assertEquals("radiology", (v1.getSpecialties().get(0)).getName()); + Vet v2 = EntityUtils.getById(vets, Vet.class, 3); + assertEquals("Douglas", v2.getLastName()); + assertEquals(2, v2.getNrOfSpecialties()); + assertEquals("dentistry", (v2.getSpecialties().get(0)).getName()); + assertEquals("surgery", (v2.getSpecialties().get(1)).getName()); + } + + public void testGetPetTypes() { + Collection<PetType> petTypes = this.clinic.getPetTypes(); + assertEquals("JDBC query must show the same number of pet types", super.countRowsInTable("TYPES"), + petTypes.size()); + PetType t1 = EntityUtils.getById(petTypes, PetType.class, 1); + assertEquals("cat", t1.getName()); + PetType t4 = EntityUtils.getById(petTypes, PetType.class, 4); + assertEquals("snake", t4.getName()); + } + + public void testFindOwners() { + Collection<Owner> owners = this.clinic.findOwners("Davis"); + assertEquals(2, owners.size()); + owners = this.clinic.findOwners("Daviss"); + assertEquals(0, owners.size()); + } + + public void testLoadOwner() { + Owner o1 = this.clinic.loadOwner(1); + assertTrue(o1.getLastName().startsWith("Franklin")); + Owner o10 = this.clinic.loadOwner(10); + assertEquals("Carlos", o10.getFirstName()); + + // Check lazy loading, by ending the transaction + endTransaction(); + + // Now Owners are "disconnected" from the data store. + // We might need to touch this collection if we switched to lazy loading + // in mapping files, but this test would pick this up. + o1.getPets(); + } + + public void testInsertOwner() { + Collection<Owner> owners = this.clinic.findOwners("Schultz"); + int found = owners.size(); + Owner owner = new Owner(); + owner.setLastName("Schultz"); + this.clinic.storeOwner(owner); + // assertTrue(!owner.isNew()); -- NOT TRUE FOR TOPLINK (before commit) + owners = this.clinic.findOwners("Schultz"); + assertEquals(found + 1, owners.size()); + } + + public void testUpdateOwner() throws Exception { + Owner o1 = this.clinic.loadOwner(1); + String old = o1.getLastName(); + o1.setLastName(old + "X"); + this.clinic.storeOwner(o1); + o1 = this.clinic.loadOwner(1); + assertEquals(old + "X", o1.getLastName()); + } + + public void testLoadPet() { + Collection<PetType> types = this.clinic.getPetTypes(); + Pet p7 = this.clinic.loadPet(7); + assertTrue(p7.getName().startsWith("Samantha")); + assertEquals(EntityUtils.getById(types, PetType.class, 1).getId(), p7.getType().getId()); + assertEquals("Jean", p7.getOwner().getFirstName()); + Pet p6 = this.clinic.loadPet(6); + assertEquals("George", p6.getName()); + assertEquals(EntityUtils.getById(types, PetType.class, 4).getId(), p6.getType().getId()); + assertEquals("Peter", p6.getOwner().getFirstName()); + } + + public void testInsertPet() { + Owner o6 = this.clinic.loadOwner(6); + int found = o6.getPets().size(); + Pet pet = new Pet(); + pet.setName("bowser"); + Collection<PetType> types = this.clinic.getPetTypes(); + pet.setType(EntityUtils.getById(types, PetType.class, 2)); + pet.setBirthDate(new Date()); + o6.addPet(pet); + assertEquals(found + 1, o6.getPets().size()); + this.clinic.storeOwner(o6); + // assertTrue(!pet.isNew()); -- NOT TRUE FOR TOPLINK (before commit) + o6 = this.clinic.loadOwner(6); + assertEquals(found + 1, o6.getPets().size()); + } + + public void testUpdatePet() throws Exception { + Pet p7 = this.clinic.loadPet(7); + String old = p7.getName(); + p7.setName(old + "X"); + this.clinic.storePet(p7); + p7 = this.clinic.loadPet(7); + assertEquals(old + "X", p7.getName()); + } + + public void testInsertVisit() { + Pet p7 = this.clinic.loadPet(7); + int found = p7.getVisits().size(); + Visit visit = new Visit(); + p7.addVisit(visit); + visit.setDescription("test"); + this.clinic.storePet(p7); + // assertTrue(!visit.isNew()); -- NOT TRUE FOR TOPLINK (before commit) + p7 = this.clinic.loadPet(7); + assertEquals(found + 1, p7.getVisits().size()); + } + +} diff --git a/src/test/java/org/springframework/samples/petclinic/jpa/EntityManagerClinicTests.java b/src/test/java/org/springframework/samples/petclinic/jpa/EntityManagerClinicTests.java new file mode 100644 index 0000000000000000000000000000000000000000..67c472fdeeacbe91eebd7da70fed0e23951b1504 --- /dev/null +++ b/src/test/java/org/springframework/samples/petclinic/jpa/EntityManagerClinicTests.java @@ -0,0 +1,51 @@ +package org.springframework.samples.petclinic.jpa; + +import java.util.List; + +import org.springframework.samples.petclinic.aspects.UsageLogAspect; + +/** + * <p> + * Tests for the DAO variant based on the shared EntityManager approach. Uses + * TopLink Essentials (the reference implementation) for testing. + * </p> + * <p> + * Specifically tests usage of an <code>orm.xml</code> file, loaded by the + * persistence provider through the Spring-provided persistence unit root URL. + * </p> + * + * @author Rod Johnson + * @author Juergen Hoeller + */ +public class EntityManagerClinicTests extends AbstractJpaClinicTests { + + private UsageLogAspect usageLogAspect; + + public void setUsageLogAspect(UsageLogAspect usageLogAspect) { + this.usageLogAspect = usageLogAspect; + } + + @Override + protected String[] getConfigPaths() { + return new String[] { + "applicationContext-jpaCommon.xml", + "applicationContext-toplinkAdapter.xml", + "applicationContext-entityManager.xml" + }; + } + + public void testUsageLogAspectIsInvoked() { + String name1 = "Schuurman"; + String name2 = "Greenwood"; + String name3 = "Leau"; + + assertTrue(this.clinic.findOwners(name1).isEmpty()); + assertTrue(this.clinic.findOwners(name2).isEmpty()); + + List<String> namesRequested = this.usageLogAspect.getNamesRequested(); + assertTrue(namesRequested.contains(name1)); + assertTrue(namesRequested.contains(name2)); + assertFalse(namesRequested.contains(name3)); + } + +} diff --git a/src/test/java/org/springframework/samples/petclinic/jpa/HibernateEntityManagerClinicTests.java b/src/test/java/org/springframework/samples/petclinic/jpa/HibernateEntityManagerClinicTests.java new file mode 100644 index 0000000000000000000000000000000000000000..d95b452e17cf798a84d8dc0aaa0e1a3e0a4b1d02 --- /dev/null +++ b/src/test/java/org/springframework/samples/petclinic/jpa/HibernateEntityManagerClinicTests.java @@ -0,0 +1,26 @@ +package org.springframework.samples.petclinic.jpa; + +/** + * <p> + * Tests for the DAO variant based on the shared EntityManager approach, using + * Hibernate EntityManager for testing instead of the reference implementation. + * </p> + * <p> + * Specifically tests usage of an <code>orm.xml</code> file, loaded by the + * persistence provider through the Spring-provided persistence unit root URL. + * </p> + * + * @author Juergen Hoeller + */ +public class HibernateEntityManagerClinicTests extends EntityManagerClinicTests { + + @Override + protected String[] getConfigPaths() { + return new String[] { + "applicationContext-jpaCommon.xml", + "applicationContext-hibernateAdapter.xml", + "applicationContext-entityManager.xml" + }; + } + +} diff --git a/src/test/java/org/springframework/samples/petclinic/jpa/OpenJpaEntityManagerClinicTests.java b/src/test/java/org/springframework/samples/petclinic/jpa/OpenJpaEntityManagerClinicTests.java new file mode 100644 index 0000000000000000000000000000000000000000..98e38ed6134cd0836f91ab3a0680f37e1cbf9d53 --- /dev/null +++ b/src/test/java/org/springframework/samples/petclinic/jpa/OpenJpaEntityManagerClinicTests.java @@ -0,0 +1,27 @@ + +package org.springframework.samples.petclinic.jpa; + +/** + * <p> + * Tests for the DAO variant based on the shared EntityManager approach, using + * Apache OpenJPA for testing instead of the reference implementation. + * </p> + * <p> + * Specifically tests usage of an <code>orm.xml</code> file, loaded by the + * persistence provider through the Spring-provided persistence unit root URL. + * </p> + * + * @author Juergen Hoeller + */ +public class OpenJpaEntityManagerClinicTests extends EntityManagerClinicTests { + + @Override + protected String[] getConfigPaths() { + return new String[] { + "applicationContext-jpaCommon.xml", + "applicationContext-openJpaAdapter.xml", + "applicationContext-entityManager.xml" + }; + } + +} diff --git a/src/test/java/org/springframework/samples/petclinic/web/VisitsAtomViewTest.java b/src/test/java/org/springframework/samples/petclinic/web/VisitsAtomViewTest.java new file mode 100644 index 0000000000000000000000000000000000000000..1e82e1d5bb142078dea20c35d749dadc28f44c95 --- /dev/null +++ b/src/test/java/org/springframework/samples/petclinic/web/VisitsAtomViewTest.java @@ -0,0 +1,90 @@ +/* + * Copyright 2002-2009 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.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.sun.syndication.feed.atom.Entry; +import com.sun.syndication.feed.atom.Feed; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import org.junit.Before; +import org.junit.Test; + +import org.springframework.samples.petclinic.Pet; +import org.springframework.samples.petclinic.PetType; +import org.springframework.samples.petclinic.Visit; + +/** + * @author Arjen Poutsma + */ +public class VisitsAtomViewTest { + + private VisitsAtomView visitView; + + private Map<String, Object> model; + + private Feed feed; + + @Before + public void setUp() { + visitView = new VisitsAtomView(); + PetType dog = new PetType(); + dog.setName("dog"); + Pet bello = new Pet(); + bello.setName("Bello"); + bello.setType(dog); + Visit belloVisit = new Visit(); + belloVisit.setPet(bello); + belloVisit.setDate(new Date(2009, 0, 1)); + belloVisit.setDescription("Bello visit"); + Pet wodan = new Pet(); + wodan.setName("Wodan"); + wodan.setType(dog); + Visit wodanVisit = new Visit(); + wodanVisit.setPet(wodan); + wodanVisit.setDate(new Date(2009, 0, 2)); + wodanVisit.setDescription("Wodan visit"); + List<Visit> visits = new ArrayList<Visit>(); + visits.add(belloVisit); + visits.add(wodanVisit); + + model = new HashMap<String, Object>(); + model.put("visits", visits); + feed = new Feed(); + + } + + @Test + public void buildFeedMetadata() { + visitView.buildFeedMetadata(model, feed, null); + + assertNotNull("No id set", feed.getId()); + assertNotNull("No title set", feed.getTitle()); + assertEquals("Invalid update set", new Date(2009, 0, 2), feed.getUpdated()); + } + + @Test + public void buildFeedEntries() throws Exception { + List<Entry> entries = visitView.buildFeedEntries(model, null, null); + assertEquals("Invalid amount of entries", 2, entries.size()); + } +} diff --git a/src/test/resources/log4j.xml b/src/test/resources/log4j.xml new file mode 100644 index 0000000000000000000000000000000000000000..767b96d6206d5fc8eb42f14f9e10be7f5c3251fc --- /dev/null +++ b/src/test/resources/log4j.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> + +<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"> + + <!-- Appenders --> + <appender name="console" class="org.apache.log4j.ConsoleAppender"> + <param name="Target" value="System.out" /> + <layout class="org.apache.log4j.PatternLayout"> + <param name="ConversionPattern" value="%-5p: %c - %m%n" /> + </layout> + </appender> + + <logger name="org.springframework.beans"> + <level value="warn" /> + </logger> + + <logger name="org.springframework.binding"> + <level value="debug" /> + </logger> + + <!-- Root Logger --> + <root> + <priority value="warn" /> + <appender-ref ref="console" /> + </root> + +</log4j:configuration> \ No newline at end of file diff --git a/src/test/resources/org/springframework/samples/petclinic/AbstractClinicTests-context.xml b/src/test/resources/org/springframework/samples/petclinic/AbstractClinicTests-context.xml new file mode 100644 index 0000000000000000000000000000000000000000..3f79cbc093998d1bd6b9c63876f759306da35f08 --- /dev/null +++ b/src/test/resources/org/springframework/samples/petclinic/AbstractClinicTests-context.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" + xmlns:tx="http://www.springframework.org/schema/tx" + xsi:schemaLocation=" + http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd + http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> + + <context:property-placeholder location="classpath:/jdbc.properties"/> + + <context:annotation-config/> + + <tx:annotation-driven/> + + <bean id="dataSource" class="org.springframework.samples.petclinic.config.DbcpDataSourceFactory" + p:driverClassName="${jdbc.driverClassName}" p:url="${jdbc.url}" + p:username="${jdbc.username}" p:password="${jdbc.password}" p:populate="${jdbc.populate}" + p:schemaLocation="${jdbc.schemaLocation}" p:dataLocation="${jdbc.dataLocation}" + p:dropLocation="${jdbc.dropLocation}"/> + +</beans> diff --git a/src/test/resources/org/springframework/samples/petclinic/hibernate/HibernateClinicTests-context.xml b/src/test/resources/org/springframework/samples/petclinic/hibernate/HibernateClinicTests-context.xml new file mode 100644 index 0000000000000000000000000000000000000000..7320035ceceb6a274cec06980a039bca9da3af21 --- /dev/null +++ b/src/test/resources/org/springframework/samples/petclinic/hibernate/HibernateClinicTests-context.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> +<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:p="http://www.springframework.org/schema/p" + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> + + <bean class="org.springframework.samples.petclinic.hibernate.HibernateClinic"> + <constructor-arg ref="sessionFactory"/> + </bean> + + <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean" + p:dataSource-ref="dataSource" p:mappingResources="petclinic.hbm.xml"> + <property name="hibernateProperties"> + <props> + <prop key="hibernate.dialect">${hibernate.dialect}</prop> + <prop key="hibernate.show_sql">${hibernate.show_sql}</prop> + </props> + </property> + <property name="eventListeners"> + <map> + <entry key="merge"> + <bean class="org.springframework.orm.hibernate3.support.IdTransferringMergeEventListener" /> + </entry> + </map> + </property> + </bean> + + <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager" + p:sessionFactory-ref="sessionFactory" /> + + <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" /> + +</beans> diff --git a/src/test/resources/org/springframework/samples/petclinic/jdbc/SimpleJdbcClinicTests-context.xml b/src/test/resources/org/springframework/samples/petclinic/jdbc/SimpleJdbcClinicTests-context.xml new file mode 100644 index 0000000000000000000000000000000000000000..81a536988bcda95bd6e06638a0c4f8141c5a8ac0 --- /dev/null +++ b/src/test/resources/org/springframework/samples/petclinic/jdbc/SimpleJdbcClinicTests-context.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8"?> +<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:p="http://www.springframework.org/schema/p" + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> + + <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" + p:dataSource-ref="dataSource" /> + + <bean class="org.springframework.samples.petclinic.jdbc.SimpleJdbcClinic" /> + +</beans> diff --git a/src/test/resources/org/springframework/samples/petclinic/jpa/applicationContext-entityManager.xml b/src/test/resources/org/springframework/samples/petclinic/jpa/applicationContext-entityManager.xml new file mode 100644 index 0000000000000000000000000000000000000000..a9d25738584611f2cfef1f74ecf576c2da4c0f51 --- /dev/null +++ b/src/test/resources/org/springframework/samples/petclinic/jpa/applicationContext-entityManager.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" + xsi:schemaLocation=" + http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd + http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> + + <aop:aspectj-autoproxy /> + + <bean class="org.springframework.samples.petclinic.aspects.UsageLogAspect" p:historySize="300" /> + + <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" /> + + <bean id="clinic" class="org.springframework.samples.petclinic.jpa.EntityManagerClinic" /> + +</beans> diff --git a/src/test/resources/org/springframework/samples/petclinic/jpa/applicationContext-hibernateAdapter.xml b/src/test/resources/org/springframework/samples/petclinic/jpa/applicationContext-hibernateAdapter.xml new file mode 100644 index 0000000000000000000000000000000000000000..447d1bce08f496cf80b6b1e767b5f057e906d994 --- /dev/null +++ b/src/test/resources/org/springframework/samples/petclinic/jpa/applicationContext-hibernateAdapter.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:p="http://www.springframework.org/schema/p" + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> + + <bean id="jpaAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" + p:database="${jpa.database}" p:showSql="${jpa.showSql}" /> + +</beans> diff --git a/src/test/resources/org/springframework/samples/petclinic/jpa/applicationContext-jpaCommon.xml b/src/test/resources/org/springframework/samples/petclinic/jpa/applicationContext-jpaCommon.xml new file mode 100644 index 0000000000000000000000000000000000000000..b873dfc47ed0ef3540e55146cbba4ea707eee333 --- /dev/null +++ b/src/test/resources/org/springframework/samples/petclinic/jpa/applicationContext-jpaCommon.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" + xmlns:tx="http://www.springframework.org/schema/tx" + xsi:schemaLocation=" + http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd + http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> + + <context:property-placeholder location="classpath:/jdbc.properties" /> + + <tx:annotation-driven /> + + <bean id="dataSource" class="org.springframework.samples.petclinic.config.DbcpDataSourceFactory" + p:driverClassName="${jdbc.driverClassName}" p:url="${jdbc.url}" + p:username="${jdbc.username}" p:password="${jdbc.password}" p:populate="${jdbc.populate}" + p:schemaLocation="${jdbc.schemaLocation}" p:dataLocation="${jdbc.dataLocation}" + p:dropLocation="${jdbc.dropLocation}"/> + + <!-- Note: the specific "jpaAdapter" bean sits in adapter context file --> + <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" + p:dataSource-ref="dataSource" p:jpaVendorAdapter-ref="jpaAdapter"> + <property name="loadTimeWeaver"> + <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" /> + </property> + </bean> + + <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager" + p:entityManagerFactory-ref="entityManagerFactory" /> + +</beans> diff --git a/src/test/resources/org/springframework/samples/petclinic/jpa/applicationContext-openJpaAdapter.xml b/src/test/resources/org/springframework/samples/petclinic/jpa/applicationContext-openJpaAdapter.xml new file mode 100644 index 0000000000000000000000000000000000000000..8f6f7c4427c4779fbe013b0d5e0e0d4bf455c7cf --- /dev/null +++ b/src/test/resources/org/springframework/samples/petclinic/jpa/applicationContext-openJpaAdapter.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:p="http://www.springframework.org/schema/p" + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> + + <bean id="jpaAdapter" class="org.springframework.orm.jpa.vendor.OpenJpaVendorAdapter" p:database="${jpa.database}" + p:showSql="${jpa.showSql}" /> + +</beans> diff --git a/src/test/resources/org/springframework/samples/petclinic/jpa/applicationContext-toplinkAdapter.xml b/src/test/resources/org/springframework/samples/petclinic/jpa/applicationContext-toplinkAdapter.xml new file mode 100644 index 0000000000000000000000000000000000000000..ac031de8e148870caaa07acbecb3e38e744c49dc --- /dev/null +++ b/src/test/resources/org/springframework/samples/petclinic/jpa/applicationContext-toplinkAdapter.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:p="http://www.springframework.org/schema/p" + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> + + <bean id="jpaAdapter" class="org.springframework.orm.jpa.vendor.TopLinkJpaVendorAdapter" + p:databasePlatform="${jpa.databasePlatform}" p:showSql="${jpa.showSql}" /> + +</beans>