Previous: Tutorial Up: Tutorial Next: Authentication

When you click around in the application and find everything working, you will recognize that most of our requirements are already met by the application:

  1. You are able to create owners of pets
  2. You are able to add and edit their pets
  3. You are able to add visits for the owners

The only thing that is really missing from the requirements is the ability to assign visits to vets. In this chapter we are going to realize this.

Modifying the Database

The first thing to we have to do in order to assign visits to vets, is to change the database to reflect this. As we use an in-memory-database (HSQLDB) that is initialized from a script file, we only have to change the script file. We have to stop the server first:

mvn jetty:stop
    

We now have to edit the file petclinic.script from the root-folder (which is named petclinic/). We have to add a database column to the table VISITS referencing the table VETS. We change the line

CREATE MEMORY TABLE VISITS(ID INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 0) NOT NULL PRIMARY KEY,PET_ID INTEGER NOT NULL,VISIT_DATE DATE,DESCRIPTION VARCHAR(255),CONSTRAINT FK_VISITS_PETS FOREIGN KEY(PET_ID) REFERENCES PETS(ID))
    

into

CREATE MEMORY TABLE VISITS(ID INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 0) NOT NULL PRIMARY KEY,PET_ID INTEGER NOT NULL,VET_ID INTEGER NOT NULL,VISIT_DATE DATE,DESCRIPTION VARCHAR(255),CONSTRAINT FK_VISITS_PETS FOREIGN KEY(PET_ID) REFERENCES PETS(ID),CONSTRAINT FK_VISITS_VETS FOREIGN KEY(VET_ID) REFERENCES VETS(ID))
    

Additionally we have to extend all INSERT statements into the table VISITS to fill the additional column. Change

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')
    

into

INSERT INTO VISITS VALUES(1,7,1,'2008-03-04','rabies shot')
INSERT INTO VISITS VALUES(2,8,1,'2008-03-04','rabies shot')
INSERT INTO VISITS VALUES(3,8,2,'2008-06-04','neutered')
INSERT INTO VISITS VALUES(4,7,3,'1996-09-04','spayed')
    

Modifying the Domain Model

The base-class of all our entities is BaseEntity (in src/main/java/org/springframework/samples/petclinic/BaseEntity.java). For proper object-handling we add two methods here: equals and hashCode.

Add

      
    public int hashCode() {
        if (isNew()) {
            return System.identityHashCode(this);
        } else {
            return id;
        }
    }

    public boolean equals(Object object) {
        if (!(object instanceof BaseEntity)) {
            return false;
        }
        if (isNew()) {
            return this == object;
        }
        return getId().equals(((BaseEntity)object).getId());
    }
      
    

Now we can modify our model to reflect the relation between visits and vets. We add the following code to the file Visit.java (in src/main/java/org/springframework/samples/petclinic/)

      
    /** Holds value of property pet. */
    private Vet vet;
    
    public Visit(Pet pet) {
        this();
        setPet(pet);
    }

    /** Getter for property vet.
     * @return Value of property vet.
     */
    public Vet getVet() {
        return this.vet;
    }

    /** Setter for property vet.
     * @param pet New value of property vet.
     */
    public void setVet(Vet vet) {
        this.vet = vet;
    }
      
    

Additionally we have to edit the file orm.xml (in src/main/resources/META-INF/) to configure JPA to use the extra relation.

Change

      
    <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>    
      
    

into

      
    <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>
            <many-to-one name="vet" fetch="EAGER">
                <cascade>
                    <cascade-all/>
                </cascade>
            </many-to-one>
        </attributes>
    </entity>    
      
    

Since we don't know how many visits a vet may have (that may be many), we don't want to model the relation between vets and visits bidirectional. Instead we add a method to the Clinic interface to retrieve all visits of a specified vet. So it is easy to implement filtering or lazy loading of visits later.

Add the following code to the Clinic interface (in Clinic.java, which is in src/main/java/org/springframework/samples/petclinic/).

      
    /**
     * Retrieve <code>Visit</code>s from the data store,
     * returning all visits at a given vet.
     * @param vet the visited vet
     * @return a <code>Collection</code> of matching <code>Visit</code>s
     * (or an empty <code>Collection</code> if none found)
     */
    Collection<Visit> findVisits(Vet vet) throws DataAccessException;
      
    

We also add the implementation of this method in EntityManagerClinic.java (in src/main/java/org/springframework/samples/petclinic/jpa/).

      
    @Transactional(readOnly = true)
    public Collection<Visit> findVisits(Vet vet) {
        Query query = this.em.createQuery("SELECT visit FROM Visit visit "
                                        + "WHERE visit.vet = :vet");
        query.setParameter("vet", vet);
        return query.getResultList();
    }
      
    

Modifying the View

The last thing we have to do is to modify the JSPs to see the relation, navigate it and add it when adding a visit.

First we add a link to the vet in the view of the owner (in the file owner.jsp in src/main/webapp/WEB-INF/jsp).

Change

      
          <table>
            <tr>
            <thead>
              <th>Visit Date</th>
              <th>Description</th>
            </thead>
            </tr>
            <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>
      
    

into

      
          <table>
            <tr>
            <thead>
              <th>Vet</th>
              <th>Visit Date</th>
              <th>Description</th>
            </thead>
            </tr>
            <c:forEach var="visit" items="${pet.visits}">
              <tr>
                <td><a href="vet.do?vetId=${visit.vet.id}">${visit.vet.firstName} ${visit.vet.lastName}</a></td>
                <td><fmt:formatDate value="${visit.date}" pattern="yyyy-MM-dd"/></td>
                <td>${visit.description}</td>
              </tr>
            </c:forEach>
          </table>
      
    

Second we add a page to display a single vet with the list of visits. We name this file vet.jsp and put it into src/main/webapp/WEB-INF/jsp. This is the content of the file:

      
<%@ include file="/WEB-INF/jsp/includes.jsp" %>
<%@ include file="/WEB-INF/jsp/header.jsp" %>

<h2>Vet Information</h2>

  <table>
    <tr>
      <th>Name</th>
      <td><b>${vet.firstName} ${vet.lastName}</b></td>
    </tr>
    <tr>
      <th>Specialities</th>
      <td>
	    <c:forEach var="specialty" items="${vet.specialties}">
          ${specialty.name}
        </c:forEach>
        <c:if test="${vet.nrOfSpecialties == 0}">none</c:if>
      </td>
    </tr>
  </table>

<h2>Visits</h2>

  <c:forEach var="visit" items="${visits}">
    <table width="94%">
      <tr>
        <th>Date</th>
        <td><fmt:formatDate value="${visit.date}" pattern="yyyy-MM-dd"/></td>
      </tr>
      <tr>
        <th>Pet</th>
        <td>${visit.pet.name}</td>
      </tr>
      <tr>
        <th>Type</th>
        <td>${visit.pet.type.name}</td>
      </tr>
      <tr>
        <th>Owner</th>
        <td><a href="owner.do?ownerId=${visit.pet.owner.id}">${visit.pet.owner.firstName} ${visit.pet.owner.lastName}</a></td>
      </tr>
      <tr>
        <th>Description</th>
        <td>${visit.description}</td>
      </tr>
    </table>
    <table class="table-buttons">
      <tr>
        <td>
          <form method="GET" action="<c:url value="/editVisit.do"/>" name="formEditVisit${visit.id}">
            <input type="hidden" name="visitId" value="${visit.id}"/>
            <p class="submit"><input type="submit" value="Edit Visit"/></p>
          </form>
        </td>
      </tr>
    </table>
  </c:forEach>
  
<%@ include file="/WEB-INF/jsp/footer.jsp" %>
      
    

To make the model available for the new vet-view, we add a method to load a vet to the Clinic interface.

Add the following code to the Clinic interface (in Clinic.java, which is in src/main/java/org/springframework/samples/petclinic/).

      
    /**
     * Retrieve a <code>Vet</code> from the data store by id.
     * @param id the id to search for
     * @return the <code>Vet</code> if found
     * @throws org.springframework.dao.DataRetrievalFailureException if not found
     */
    Vet loadVet(int id) throws DataAccessException;
      
    

We also add the implementation of this method in EntityManagerClinic.java (in src/main/java/org/springframework/samples/petclinic/jpa/).

      
    @Transactional(readOnly = true)
    public Vet loadVet(int id) {
    	return this.em.find(Vet.class, id);
    }
      
    

Last we have to add the following code to ClinicController.java (in src/main/java/org/springframework/samples/petclinic/web/). And don't forget to add the import-statement for the Vet.

      
    /**
     * Custom handler for displaying a vet.
     * <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: "/vet.do" -&gt; "vet".
     *
     * @param vetId the ID of the vet to display
     * @return a ModelMap with the model attributes for the view
     */
    @RequestMapping("/vet.do")
    public ModelMap vetHandler(@RequestParam("vetId") int vetId) {
        Vet vet = this.clinic.loadVet(vetId);
        return new ModelMap(vet).addAttribute("visits", this.clinic.findVisits(vet));
    }
      
    

We also may modify vets.jsp in src/main/webapp/WEB-INF/jsp to add links to our newly created vet-page.

Change

      
      <td>${vet.firstName} ${vet.lastName}</td>
      
    

into

      
      <td><a href="vet.do?vetId=${vet.id}">${vet.firstName} ${vet.lastName}</a></td>
      
    

There is one thing more to do in order to establish the relation between visits and vets: An owner must be able to select a vet when making an appointment.

We achieve this by adding the following lines of code to AddVisitForm.java (in src/main/java/org/springframework/samples/petclinic/web/). Again don't forget the import-statements.

      
    @ModelAttribute("vets")
    public Collection<Vet> populateVets() {
        return this.clinic.getVets();
    }
      
    

Now we can put a vet-selector into visitForm.jsp (in src/main/webapp/WEB-INF/jsp/

Change

      
    <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>
      
    

into

      
    <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>
        Vet:
        <br/><form:errors path="vet" cssClass="errors"/>
      </th>
      <td>
        <form:select path="vet" items="${vets}"/>
      </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>
      
    

Adding the vet to the list of previous visits remains as an exercise to you.

In order to display a meaningful text in our vet-selector, we have to override the toString()-method in the class Vet.

Add the following code to Person.java since it is the superclass of Vet. (in src/main/java/org/springframework/samples/petclinic/).

      
    public String toString() {
        return this.getLastName() + ", " + this.getFirstName();
    }
      
    

Additionally add the following code to Vet.java (in src/main/java/org/springframework/samples/petclinic/).

      
    public String toString() {
        StringBuilder specialties = new StringBuilder();
        for (Specialty specialty: getSpecialties()) {
            specialties.append(' ').append(specialty.getName()).append(',');
        }
        if (getNrOfSpecialties() == 0) {
            specialties.append("(none)");
        } else {
            specialties.setCharAt(0, '(');
            specialties.setCharAt(specialties.length() - 1, ')');
        }
        return super.toString() + " " + specialties.toString();
    }
      
    

The vet is now displayed human-readable in the drop-down-box, but we have to ensure that the displayed text properly can be reverted to a vet. We do this by adding a vet-editor.

Create the file VetEditor.java in src/main/java/org/springframework/samples/petclinic/web/ and fill it with the following content.

      
package org.springframework.samples.petclinic.web;

import java.beans.PropertyEditorSupport;

import org.springframework.samples.petclinic.Clinic;
import org.springframework.samples.petclinic.Vet;

public class VetEditor extends PropertyEditorSupport {

	private final Clinic clinic;

	public VetEditor(Clinic clinic) {
		this.clinic = clinic;
	}

	@Override
	public void setAsText(String text) throws IllegalArgumentException {
		for (Vet vet: this.clinic.getVets()) {
			if (vet.toString().equals(text)) {
				setValue(vet);
			}
		}
	}
}
      
    

We register the new editor in ClinicBindingInitializer.java (in src/main/java/org/springframework/samples/petclinic/web/) by adding the following line below of the registration of the other custom editors.

      
        binder.registerCustomEditor(Vet.class, new VetEditor(this.clinic));
      
    

Now we are able to add visits for pets and select the vet to visit. Another requirement of our application was that the vet is able to modify an existing visit. First we must be able to load a visit.

Add the following code to the Clinic interface (in Clinic.java, which is in src/main/java/org/springframework/samples/petclinic/).

      
    /**
     * Retrieve a <code>Visit</code> from the data store by id.
     * @param id the id to search for
     * @return the <code>Visit</code> if found
     * @throws org.springframework.dao.DataRetrievalFailureException if not found
     */
    Visit loadVisit(int id) throws DataAccessException;
      
    

We also add the implementation of this method in EntityManagerClinic.java (in src/main/java/org/springframework/samples/petclinic/jpa/).

      
    @Transactional(readOnly = true)
    public Visit loadVisit(int id) {
    	return this.em.find(Visit.class, id);
    }
      
    

Now we can create a form to edit a visit. Create src/main/java/org/springframework/samples/petclinic/web/EditVisitForm.java with the following content.

      
package org.springframework.samples.petclinic.web;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.samples.petclinic.Clinic;
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.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;

@Controller
@RequestMapping("/editVisit.do")
@SessionAttributes("visit")
public class EditVisitForm {

    private final Clinic clinic;

    @Autowired
    public EditVisitForm(Clinic clinic) {
        this.clinic = clinic;
    }

    @InitBinder
    public void setAllowedFields(WebDataBinder dataBinder) {
        dataBinder.setDisallowedFields(new String[] {"id"});
    }

    @RequestMapping(method = RequestMethod.GET)
    public String setupForm(@RequestParam("visitId") int visitId, Model model) {
        Visit visit = this.clinic.loadVisit(visitId);
        model.addAttribute("visit", visit);
        return "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";
        }
        else {
            this.clinic.storeVisit(visit);
            status.setComplete();
            return "redirect:vet.do?vetId=" + visit.getVet().getId();
        }
    }
}
      
    

As view we can reuse the visitForm.jsp from src/main/webapp/WEB-INF/jsp/. We just have to make two modifications:

Change

      
      <th>
        Vet:
        <br/><form:errors path="vet" cssClass="errors"/>
      </th>
      <td>
        <form:select path="vet" items="${vets}"/>
      </td>
      
    

into

      
      <th>
        Vet:
        <br/><form:errors path="vet" cssClass="errors"/>
      </th>
      <td>
        <c:choose>
          <c:when test="${visit.new}">
            <form:select path="vet" items="${vets}"/>
          </c:when>
          <c:otherwise>
            ${visit.vet.firstName} ${visit.vet.lastName}
          </c:otherwise>
        </c:choose>
      </td>
      
    

Second we change the label of the button. Change

      
      <td colspan="2">
        <input type="hidden" name="petId" value="${visit.pet.id}"/>
        <p class="submit"><input type="submit" value="Add Visit"/></p>
      </td>
      
    

into

      
      <td colspan="2">
        <c:choose>
          <c:when test="${visit.new}">
            <input type="hidden" name="petId" value="${visit.pet.id}"/>
            <p class="submit"><input type="submit" value="Add Visit"/></p>
          </c:when>
          <c:otherwise>
            <p class="submit"><input type="submit" value="Update Visit"/></p>
          </c:otherwise>
        </c:choose>
      </td>
      
    

As we already added a button to edit a visit to vet.jsp, we do not need any more to do to integrate the visit-editor. We can start our server again and see, if our new relation between visits and vets works. (Navigate to Jean Coleman as there are already added some visits.)


Previous: Tutorial Up: Tutorial Next: Authentication