Polishing the new RESTful interface to the petclinic webapp (SPR-5487):

* Eliminated redundant 'clinic' servlet mapping (was: http://localhost:8080/petclinic/clinic/owners; now: http://localhost:8080/petclinic/owners)
* A parameterless GET for /owners now returns the list of all owners, rather than an error.
* /owners/form is now /owners/search (distinguishes the 'search form' resource from the 'edit owner form' resource)
* Eliminated any need for redirects, <welcome-file-list/>, and index.jsp. Deleted all of them.
* Updated /owners/{oid}/edit to submit using PUT instead of POST
* Updated URI for edit pet form from /owners/{oid}/pets/{pid} to /owners/{oid}/pets/{pid}/edit (the edit form is a distinct resource)
* Updated /owners/{oid}/pets/{pid}/edit to submit using PUT instead of POST

Changes unrelated to the web interface:

* Partitioned up JSPs into new owners, pets, and vets folders.
* Renamed those JSPs, e.g. ownerForm.jsp -> owners/form.jsp; findOwners.jsp -> owners/search.jsp; owners.jsp -> owners/list.jsp
* Updated various controllers to respect the changes to the URI templates, etc.
* Updated .classpath to include all necessary projects and libs to run the webapp successfully in WTP
* Updated JSP error checking rules to relax validation of fragments like header.jsp and footer.jsp
This commit is contained in:
Chris Beams 2009-02-14 09:11:14 +00:00
parent da2175c0b7
commit 3ddd08942c
23 changed files with 89 additions and 57 deletions

View File

@ -4,7 +4,6 @@
<classpathentry kind="src" path="src/main/resources"/>
<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
<classpathentry kind="src" output="target/test-classes" path="src/test/resources"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.jst.j2ee.internal.web.container"/>
<classpathentry combineaccessrules="false" kind="src" path="/org.springframework.test"/>
<classpathentry kind="var" path="IVY_CACHE/org.junit/com.springsource.org.junit/4.5.0/com.springsource.org.junit-4.5.0.jar" sourcepath="/IVY_CACHE/org.junit/com.springsource.org.junit/4.5.0/com.springsource.org.junit-sources-4.5.0.jar"/>
@ -36,5 +35,7 @@
<classpathentry combineaccessrules="false" kind="src" path="/org.springframework.web.servlet"/>
<classpathentry combineaccessrules="false" kind="src" path="/org.springframework.aop"/>
<classpathentry combineaccessrules="false" kind="src" path="/org.springframework.orm"/>
<classpathentry kind="con" path="org.eclipse.jst.server.core.container/com.springsource.server.ide.server.core.runtime.classpath/SpringSource dm Server (Runtime) v1.0"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.launching.macosx.MacOSXType/JVM 1.6.0"/>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@ -1,3 +1,4 @@
#Fri Nov 21 08:19:32 EST 2008
#Fri Feb 13 19:16:19 PST 2009
classpath.helper/org.eclipse.jdt.launching.JRE_CONTAINER\:\:org.eclipse.jdt.internal.launching.macosx.MacOSXType\:\:JVM\ 1.6.0/owners=jst.java\:5.0
classpath.helper/org.eclipse.jst.server.core.container\:\:com.springsource.server.ide.server.core.runtime.classpath\:\:SpringSource\ dm\ Server\ (Runtime)\ v1.0/owners=jst.web\:2.4
eclipse.preferences.version=1

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<faceted-project>
<runtime name="SpringSource dm Server (Runtime) v1.0"/>
<fixed facet="jst.java"/>
<fixed facet="jst.web"/>
<installed facet="jst.java" version="5.0"/>

View File

@ -44,19 +44,19 @@ public class AddOwnerForm {
public String setupForm(Model model) {
Owner owner = new Owner();
model.addAttribute(owner);
return "ownerForm";
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 "ownerForm";
return "owners/form";
}
else {
this.clinic.storeOwner(owner);
status.setComplete();
return "redirect:/clinic/owners/" + owner.getId();
return "redirect:/owners/" + owner.getId();
}
}

View File

@ -56,19 +56,19 @@ public class AddPetForm {
Pet pet = new Pet();
owner.addPet(pet);
model.addAttribute("pet", pet);
return "petForm";
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 "petForm";
return "pets/form";
}
else {
this.clinic.storePet(pet);
status.setComplete();
return "redirect:/clinic/owners/" + pet.getOwner().getId();
return "redirect:/owners/" + pet.getOwner().getId();
}
}

View File

@ -48,19 +48,19 @@ public class AddVisitForm {
Visit visit = new Visit();
pet.addVisit(visit);
model.addAttribute("visit", visit);
return "visitForm";
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 "visitForm";
return "pets/visitForm";
}
else {
this.clinic.storeVisit(visit);
status.setComplete();
return "redirect:/clinic/owners/" + visit.getPet().getOwner().getId();
return "redirect:/owners/" + visit.getPet().getOwner().getId();
}
}

View File

@ -5,6 +5,7 @@ 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.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@ -38,8 +39,9 @@ public class ClinicController {
* determine the logical view name based on the request URL: "/welcome.do"
* -&gt; "welcome".
*/
@RequestMapping("/welcome")
public void welcomeHandler() {
@RequestMapping("/")
public String welcomeHandler() {
return "welcome";
}
/**
@ -53,10 +55,11 @@ public class ClinicController {
* @return a ModelMap with the model attributes for the view
*/
@RequestMapping("/vets")
public ModelMap vetsHandler() {
public String vetsHandler(Model model) {
Vets vets = new Vets();
vets.getVetList().addAll(this.clinic.getVets());
return new ModelMap(vets);
model.addAttribute(vets);
return "vets/list";
}
/**
@ -67,7 +70,7 @@ public class ClinicController {
*/
@RequestMapping("/owners/{ownerId}")
public ModelAndView ownerHandler(@PathVariable("ownerId") int ownerId) {
ModelAndView mav = new ModelAndView("owner");
ModelAndView mav = new ModelAndView("owners/show");
mav.addObject(this.clinic.loadOwner(ownerId));
return mav;
}

View File

@ -44,19 +44,19 @@ public class EditOwnerForm {
public String setupForm(@PathVariable("ownerId") int ownerId, Model model) {
Owner owner = this.clinic.loadOwner(ownerId);
model.addAttribute(owner);
return "ownerForm";
return "owners/form";
}
@RequestMapping(method = RequestMethod.POST)
@RequestMapping(method = RequestMethod.PUT)
public String processSubmit(@ModelAttribute Owner owner, BindingResult result, SessionStatus status) {
new OwnerValidator().validate(owner, result);
if (result.hasErrors()) {
return "ownerForm";
return "owners/form";
}
else {
this.clinic.storeOwner(owner);
status.setComplete();
return "redirect:/clinic/owners/" + owner.getId();
return "redirect:/owners/" + owner.getId();
}
}

View File

@ -27,7 +27,7 @@ import org.springframework.web.bind.WebDataBinder;
* @author Arjen Poutsma
*/
@Controller
@RequestMapping("/owners/*/pets/{petId}")
@RequestMapping("/owners/*/pets/{petId}/edit")
@SessionAttributes("pet")
public class EditPetForm {
@ -52,19 +52,19 @@ public class EditPetForm {
public String setupForm(@PathVariable("petId") int petId, Model model) {
Pet pet = this.clinic.loadPet(petId);
model.addAttribute("pet", pet);
return "petForm";
return "pets/form";
}
@RequestMapping(method = RequestMethod.POST)
@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 "petForm";
return "pets/form";
}
else {
this.clinic.storePet(pet);
status.setComplete();
return "redirect:/clinic/owners/" + pet.getOwner().getId();
return "redirect:/owners/" + pet.getOwner().getId();
}
}
@ -72,7 +72,7 @@ public class EditPetForm {
public String deletePet(@PathVariable int petId) {
Pet pet = this.clinic.loadPet(petId);
this.clinic.deletePet(petId);
return "redirect:/clinic/owners/" + pet.getOwner().getId();
return "redirect:/owners/" + pet.getOwner().getId();
}
}

View File

@ -36,30 +36,36 @@ public class FindOwnersForm {
dataBinder.setDisallowedFields(new String[] {"id"});
}
@RequestMapping(value = "/owners/form", method = RequestMethod.GET)
@RequestMapping(value = "/owners/search", method = RequestMethod.GET)
public String setupForm(Model model) {
model.addAttribute("owner", new Owner());
return "findOwners";
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 "findOwners";
return "owners/search";
}
if (results.size() > 1) {
// multiple owners found
model.addAttribute("selections", results);
return "owners";
return "owners/list";
}
else {
// 1 owner found
owner = results.iterator().next();
return "redirect:/clinic/owners/" + owner.getId();
return "redirect:/owners/" + owner.getId();
}
}

View File

@ -14,6 +14,6 @@ ex.printStackTrace(new java.io.PrintWriter(out));
<p/>
<br/>
<a href="<spring:url value="/welcome" escapeXml="true" />">Home</a>
<a href="<spring:url value="/" escapeXml="true" />">Home</a>
<%@ include file="/WEB-INF/jsp/footer.jsp" %>

View File

@ -1,7 +1,7 @@
<table class="footer">
<tr>
<td><a href="<spring:url value="/clinic/welcome" escapeXml="true" />">Home</a></td>
<td><a href="<spring:url value="/" escapeXml="true" />">Home</a></td>
<td align="right"><img src="<spring:url value="/images/springsource-logo.png" escapeXml="true" />" alt="Sponsored by SpringSource"/></td>
</tr>
</table>

View File

@ -1,9 +1,12 @@
<%@ 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">
<form:form modelAttribute="owner" method="${method}">
<table>
<tr>
<th>

View File

@ -4,7 +4,6 @@
<h2>Owners:</h2>
<table>
<tr>
<thead>
<th>Name</th>
<th>Address</th>
@ -12,7 +11,6 @@
<th>Telephone</th>
<th>Pets</th>
</thead>
</tr>
<c:forEach var="owner" items="${selections}">
<tr>
<td>

View File

@ -3,7 +3,7 @@
<h2>Find Owners:</h2>
<spring:url value="/clinic/owners" var="formUrl"/>
<spring:url value="/owners" var="formUrl"/>
<form:form modelAttribute="owner" action="${fn:escapeXml(formUrl)}" method="get">
<table>
<tr>
@ -20,6 +20,6 @@
</form:form>
<br/>
<a href='<spring:url value="/clinic/owners/new" escapeXml="true"/>'>Add Owner</a>
<a href='<spring:url value="/owners/new" escapeXml="true"/>'>Add Owner</a>
<%@ include file="/WEB-INF/jsp/footer.jsp" %>

View File

@ -78,7 +78,7 @@
<table class="table-buttons">
<tr>
<td>
<spring:url value="{ownerId}/pets/{petId}" var="petUrl">
<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>

View File

@ -1,12 +1,15 @@
<%@ 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">
<form:form modelAttribute="pet" method="${method}">
<table>
<tr>
<th>

View File

@ -23,7 +23,7 @@
<table class="table-buttons">
<tr>
<td>
<a href="<spring:url value="/clinic/vets.xml" escapeXml="true" />">View as XML</a>
<a href="<spring:url value="/vets.xml" escapeXml="true" />">View as XML</a>
</td>
</tr>
</table>

View File

@ -5,8 +5,8 @@
<h2><fmt:message key="welcome"/></h2>
<ul>
<li><a href="<spring:url value="/clinic/owners/form" escapeXml="true" />">Find owner</a></li>
<li><a href="<spring:url value="/clinic/vets" escapeXml="true" />">Display all veterinarians</a></li>
<li><a href="<spring:url value="/owners/search" escapeXml="true" />">Find owner</a></li>
<li><a href="<spring:url value="/vets" escapeXml="true" />">Display all veterinarians</a></li>
<li><a href="<spring:url value="/html/petclinic.html" escapeXml="true" />">Tutorial</a></li>
</ul>

View File

@ -24,7 +24,7 @@
- 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.
@ -44,6 +44,7 @@
<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>

View File

@ -84,6 +84,29 @@
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--
- Map static resources to the default servlet
- examples:
- http://localhost:8080/images/pets.png
- http://localhost:8080/styles/petclinic.css
-->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.png</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.gif</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.css</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
<!--
- Servlet that dispatches request to registered handlers (Controller implementations).
@ -110,7 +133,7 @@
-->
<servlet-mapping>
<servlet-name>petclinic</servlet-name>
<url-pattern>/clinic/*</url-pattern>
<url-pattern>/</url-pattern>
</servlet-mapping>
<filter>
@ -127,11 +150,6 @@
<session-timeout>10</session-timeout>
</session-config>
<welcome-file-list>
<!-- Redirects to "welcome.htm" for dispatcher handling -->
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<error-page>
<exception-type>java.lang.Exception</exception-type>
<!-- Displays a stack trace -->
@ -149,5 +167,7 @@
<res-auth>Container</res-auth>
</resource-ref>
-->
</web-app>

View File

@ -1,5 +0,0 @@
<%@ include file="/WEB-INF/jsp/includes.jsp" %>
<%-- Redirected because we can't set the welcome page to a virtual URL. --%>
<c:redirect url="/clinic/welcome"/>