mirror of
https://github.com/spring-projects/spring-petclinic.git
synced 2025-07-21 15:25:49 +00:00
Modified project to support the tasks listed in the case sheet
This commit is contained in:
parent
516722647a
commit
4428c28d19
14 changed files with 192 additions and 40 deletions
7
pom.xml
7
pom.xml
|
@ -69,6 +69,13 @@
|
|||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Java 8 LocalDate Jackson support -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
<version>2.15.3</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Databases - Uses H2 by default -->
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
package org.springframework.samples.petclinic.api;
|
||||
|
||||
import org.springframework.samples.petclinic.owner.Owner;
|
||||
import org.springframework.samples.petclinic.owner.OwnerRepository;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
public class OwnerApiController {
|
||||
|
||||
private final OwnerRepository ownerRepository;
|
||||
|
||||
public OwnerApiController(OwnerRepository ownerRepository) {
|
||||
this.ownerRepository = ownerRepository;
|
||||
}
|
||||
|
||||
@GetMapping("/api/owners")
|
||||
List<Owner> searchOwnersBySurname(@RequestParam String lastname) {
|
||||
return ownerRepository.findAllByLastName(lastname);
|
||||
}
|
||||
|
||||
}
|
|
@ -59,6 +59,10 @@ public class Owner extends Person {
|
|||
@Digits(fraction = 0, integer = 10)
|
||||
private String telephone;
|
||||
|
||||
@Column(name = "email")
|
||||
@NotBlank
|
||||
private String email;
|
||||
|
||||
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
|
||||
@JoinColumn(name = "owner_id")
|
||||
@OrderBy("name")
|
||||
|
@ -92,6 +96,14 @@ public class Owner extends Person {
|
|||
return this.pets;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public void addPet(Pet pet) {
|
||||
if (pet.isNew()) {
|
||||
getPets().add(pet);
|
||||
|
|
|
@ -15,9 +15,7 @@
|
|||
*/
|
||||
package org.springframework.samples.petclinic.owner;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
|
@ -25,17 +23,13 @@ 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.GetMapping;
|
||||
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.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Juergen Hoeller
|
||||
* @author Ken Krebs
|
||||
|
@ -83,7 +77,9 @@ class OwnerController {
|
|||
}
|
||||
|
||||
@GetMapping("/owners/find")
|
||||
public String initFindForm() {
|
||||
public String initFindForm(@RequestParam(defaultValue = "1") int page, Model model) {
|
||||
Page<Owner> ownersResults = findPaginatedForOwnersLastName(page, "");
|
||||
addPaginationModel(page, model, ownersResults);
|
||||
return "owners/findOwners";
|
||||
}
|
||||
|
||||
|
|
|
@ -57,6 +57,10 @@ public interface OwnerRepository extends Repository<Owner, Integer> {
|
|||
@Transactional(readOnly = true)
|
||||
Page<Owner> findByLastName(@Param("lastName") String lastName, Pageable pageable);
|
||||
|
||||
@Query("SELECT DISTINCT owner FROM Owner owner left join owner.pets WHERE owner.lastName LIKE :lastName% ")
|
||||
@Transactional(readOnly = true)
|
||||
List<Owner> findAllByLastName(@Param("lastName") String lastName);
|
||||
|
||||
/**
|
||||
* Retrieve an {@link Owner} from the data store by id.
|
||||
* @param id the id to search for
|
||||
|
|
|
@ -15,24 +15,25 @@
|
|||
*/
|
||||
package org.springframework.samples.petclinic.owner;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.Collection;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.ModelMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.InitBinder;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* @author Juergen Hoeller
|
||||
* @author Ken Krebs
|
||||
|
@ -44,6 +45,8 @@ class PetController {
|
|||
|
||||
private static final String VIEWS_PETS_CREATE_OR_UPDATE_FORM = "pets/createOrUpdatePetForm";
|
||||
|
||||
private static final String POSTMAN_URL = "https://postman-echo.com/post";
|
||||
|
||||
private final OwnerRepository owners;
|
||||
|
||||
public PetController(OwnerRepository owners) {
|
||||
|
@ -117,6 +120,24 @@ class PetController {
|
|||
}
|
||||
|
||||
this.owners.save(owner);
|
||||
|
||||
try {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
objectMapper.findAndRegisterModules();
|
||||
String petAsJson = objectMapper.writeValueAsString(pet);
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
HttpEntity<String> request = new HttpEntity<>(petAsJson, headers);
|
||||
ResponseEntity<String> response = restTemplate.postForEntity(POSTMAN_URL, request, String.class);
|
||||
if (!response.getStatusCode().is2xxSuccessful()) {
|
||||
System.out.printf("Unsuccessful POST of new pet to %s", POSTMAN_URL);
|
||||
}
|
||||
}
|
||||
catch (JsonProcessingException e) {
|
||||
System.out.printf("Unable to map pet to JSON, skipping the POST request to %s", POSTMAN_URL);
|
||||
}
|
||||
|
||||
redirectAttributes.addFlashAttribute("message", "New Pet has been Added");
|
||||
return "redirect:/owners/{ownerId}";
|
||||
}
|
||||
|
|
|
@ -22,16 +22,16 @@ INSERT INTO types VALUES (default, 'snake');
|
|||
INSERT INTO types VALUES (default, 'bird');
|
||||
INSERT INTO types VALUES (default, 'hamster');
|
||||
|
||||
INSERT INTO owners VALUES (default, 'George', 'Franklin', '110 W. Liberty St.', 'Madison', '6085551023');
|
||||
INSERT INTO owners VALUES (default, 'Betty', 'Davis', '638 Cardinal Ave.', 'Sun Prairie', '6085551749');
|
||||
INSERT INTO owners VALUES (default, 'Eduardo', 'Rodriquez', '2693 Commerce St.', 'McFarland', '6085558763');
|
||||
INSERT INTO owners VALUES (default, 'Harold', 'Davis', '563 Friendly St.', 'Windsor', '6085553198');
|
||||
INSERT INTO owners VALUES (default, 'Peter', 'McTavish', '2387 S. Fair Way', 'Madison', '6085552765');
|
||||
INSERT INTO owners VALUES (default, 'Jean', 'Coleman', '105 N. Lake St.', 'Monona', '6085552654');
|
||||
INSERT INTO owners VALUES (default, 'Jeff', 'Black', '1450 Oak Blvd.', 'Monona', '6085555387');
|
||||
INSERT INTO owners VALUES (default, 'Maria', 'Escobito', '345 Maple St.', 'Madison', '6085557683');
|
||||
INSERT INTO owners VALUES (default, 'David', 'Schroeder', '2749 Blackhawk Trail', 'Madison', '6085559435');
|
||||
INSERT INTO owners VALUES (default, 'Carlos', 'Estaban', '2335 Independence La.', 'Waunakee', '6085555487');
|
||||
INSERT INTO owners VALUES (default, 'George', 'Franklin', '110 W. Liberty St.', 'Madison', '6085551023', 'george.franklin@gmail.com');
|
||||
INSERT INTO owners VALUES (default, 'Betty', 'Davis', '638 Cardinal Ave.', 'Sun Prairie', '6085551749', 'betty.davis@gmail.com');
|
||||
INSERT INTO owners VALUES (default, 'Eduardo', 'Rodriquez', '2693 Commerce St.', 'McFarland', '6085558763', 'eddy.rodriquez@hotmail.com');
|
||||
INSERT INTO owners VALUES (default, 'Harold', 'Davis', '563 Friendly St.', 'Windsor', '6085553198', 'hide.the.pain.harold@aon.com');
|
||||
INSERT INTO owners VALUES (default, 'Peter', 'McTavish', '2387 S. Fair Way', 'Madison', '6085552765', 'pistol.pete@gmail.com');
|
||||
INSERT INTO owners VALUES (default, 'Jean', 'Coleman', '105 N. Lake St.', 'Monona', '6085552654', 'jean.coleman@hotmail.com');
|
||||
INSERT INTO owners VALUES (default, 'Jeff', 'Black', '1450 Oak Blvd.', 'Monona', '6085555387', 'jeff.black@outlook.uk');
|
||||
INSERT INTO owners VALUES (default, 'Maria', 'Escobito', '345 Maple St.', 'Madison', '6085557683', 'maria.escobito@outlook.com');
|
||||
INSERT INTO owners VALUES (default, 'David', 'Schroeder', '2749 Blackhawk Trail', 'Madison', '6085559435', 'david.schroeder@gmail.com');
|
||||
INSERT INTO owners VALUES (default, 'Carlos', 'Estaban', '2335 Independence La.', 'Waunakee', '6085555487', 'carlos.estaban@aon.com');
|
||||
|
||||
INSERT INTO pets VALUES (default, 'Leo', '2010-09-07', 1, 1);
|
||||
INSERT INTO pets VALUES (default, 'Basil', '2012-08-06', 6, 2);
|
||||
|
@ -51,3 +51,9 @@ INSERT INTO visits VALUES (default, 7, '2013-01-01', 'rabies shot');
|
|||
INSERT INTO visits VALUES (default, 8, '2013-01-02', 'rabies shot');
|
||||
INSERT INTO visits VALUES (default, 8, '2013-01-03', 'neutered');
|
||||
INSERT INTO visits VALUES (default, 7, '2013-01-04', 'spayed');
|
||||
|
||||
-- This is the command needed to migrate an existing database to add the email address on every owner
|
||||
-- I assumed it to being not nullable as it might be needed for communication with the owner,
|
||||
-- but that would require us to insert either a placeholder or an empty string while we wait for all owners to insert the right email address
|
||||
-- ALTER TABLE owners ADD email CHAR(50) DEFAULT 'Insert valid email address' NOT NULL
|
||||
|
||||
|
|
|
@ -39,7 +39,8 @@ CREATE TABLE owners (
|
|||
last_name VARCHAR_IGNORECASE(30),
|
||||
address VARCHAR(255),
|
||||
city VARCHAR(80),
|
||||
telephone VARCHAR(20)
|
||||
telephone VARCHAR(20),
|
||||
email VARCHAR(50)
|
||||
);
|
||||
CREATE INDEX owners_last_name ON owners (last_name);
|
||||
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
th:replace="~{fragments/inputField :: input ('City', 'city', 'text')}" />
|
||||
<input
|
||||
th:replace="~{fragments/inputField :: input ('Telephone', 'telephone', 'text')}" />
|
||||
<input
|
||||
th:replace="~{fragments/inputField :: input ('Email (max 50 char)', 'email', 'text')}" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
|
|
|
@ -28,6 +28,67 @@
|
|||
|
||||
<a class="btn btn-primary" th:href="@{/owners/new}">Add Owner</a>
|
||||
|
||||
<div>
|
||||
<span> </span>
|
||||
<span> </span>
|
||||
</div>
|
||||
|
||||
<h2>Owners</h2>
|
||||
|
||||
<table id="owners" class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 150px;">Name</th>
|
||||
<th style="width: 200px;">Address</th>
|
||||
<th>City</th>
|
||||
<th style="width: 120px">Telephone</th>
|
||||
<th style="width: 200px">Email</th>
|
||||
<th>Pets</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="owner : ${listOwners}">
|
||||
<td>
|
||||
<a th:href="@{/owners/__${owner.id}__}" th:text="${owner.firstName + ' ' + owner.lastName}"/></a>
|
||||
</td>
|
||||
<td th:text="${owner.address}"/>
|
||||
<td th:text="${owner.city}"/>
|
||||
<td th:text="${owner.telephone}"/>
|
||||
<td th:text="${owner.email}"/>
|
||||
<td><span th:text="${#strings.listJoin(owner.pets, ', ')}"/></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div th:if="${totalPages > 1}">
|
||||
<span>Pages:</span>
|
||||
<span>[</span>
|
||||
<span th:each="i: ${#numbers.sequence(1, totalPages)}">
|
||||
<a th:if="${currentPage != i}" th:href="@{'/owners/find?page=' + ${i}}">[[${i}]]</a>
|
||||
<span th:unless="${currentPage != i}">[[${i}]]</span>
|
||||
</span>
|
||||
<span>] </span>
|
||||
<span>
|
||||
<a th:if="${currentPage > 1}" th:href="@{'/owners/find?page=1'}" title="First"
|
||||
class="fa fa-fast-backward"></a>
|
||||
<span th:unless="${currentPage > 1}" title="First" class="fa fa-fast-backward"></span>
|
||||
</span>
|
||||
<span>
|
||||
<a th:if="${currentPage > 1}" th:href="@{'/owners/find?page=__${currentPage - 1}__'}" title="Previous"
|
||||
class="fa fa-step-backward"></a>
|
||||
<span th:unless="${currentPage > 1}" title="Previous" class="fa fa-step-backward"></span>
|
||||
</span>
|
||||
<span>
|
||||
<a th:if="${currentPage < totalPages}" th:href="@{'/owners/find?page=__${currentPage + 1}__'}" title="Next"
|
||||
class="fa fa-step-forward"></a>
|
||||
<span th:unless="${currentPage < totalPages}" title="Next" class="fa fa-step-forward"></span>
|
||||
</span>
|
||||
<span>
|
||||
<a th:if="${currentPage < totalPages}" th:href="@{'/owners/find?page=__${totalPages}__'}" title="Last"
|
||||
class="fa fa-fast-forward"></a>
|
||||
<span th:unless="${currentPage < totalPages}" title="Last" class="fa fa-step-forward"></span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
</body>
|
||||
|
|
|
@ -36,6 +36,10 @@
|
|||
<th>Telephone</th>
|
||||
<td th:text="*{telephone}"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Email</th>
|
||||
<td th:text="*{email}"></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<a th:href="@{__${owner.id}__/edit}" class="btn btn-primary">Edit
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
<th style="width: 200px;">Address</th>
|
||||
<th>City</th>
|
||||
<th style="width: 120px">Telephone</th>
|
||||
<th style="width: 200px">Email</th>
|
||||
<th>Pets</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -24,6 +25,7 @@
|
|||
<td th:text="${owner.address}"/>
|
||||
<td th:text="${owner.city}"/>
|
||||
<td th:text="${owner.telephone}"/>
|
||||
<td th:text="${owner.email}"/>
|
||||
<td><span th:text="${#strings.listJoin(owner.pets, ', ')}"/></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
|
|
@ -75,6 +75,7 @@ class OwnerControllerTests {
|
|||
george.setAddress("110 W. Liberty St.");
|
||||
george.setCity("Madison");
|
||||
george.setTelephone("6085551023");
|
||||
george.setEmail("franklin.george@gmail.com");
|
||||
Pet max = new Pet();
|
||||
PetType dog = new PetType();
|
||||
dog.setName("dog");
|
||||
|
@ -117,7 +118,8 @@ class OwnerControllerTests {
|
|||
.param("lastName", "Bloggs")
|
||||
.param("address", "123 Caramel Street")
|
||||
.param("city", "London")
|
||||
.param("telephone", "01316761638"))
|
||||
.param("telephone", "01316761638")
|
||||
.param("email", "joe.bloggs@gmail.com"))
|
||||
.andExpect(status().is3xxRedirection());
|
||||
}
|
||||
|
||||
|
@ -129,11 +131,14 @@ class OwnerControllerTests {
|
|||
.andExpect(model().attributeHasErrors("owner"))
|
||||
.andExpect(model().attributeHasFieldErrors("owner", "address"))
|
||||
.andExpect(model().attributeHasFieldErrors("owner", "telephone"))
|
||||
.andExpect(model().attributeHasFieldErrors("owner", "email"))
|
||||
.andExpect(view().name("owners/createOrUpdateOwnerForm"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInitFindForm() throws Exception {
|
||||
Page<Owner> paginatedOwners = new PageImpl<Owner>(Lists.newArrayList(george(), new Owner()));
|
||||
Mockito.when(this.owners.findByLastName(anyString(), any(Pageable.class))).thenReturn(paginatedOwners);
|
||||
mockMvc.perform(get("/owners/find"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(model().attributeExists("owner"))
|
||||
|
@ -178,6 +183,7 @@ class OwnerControllerTests {
|
|||
.andExpect(model().attribute("owner", hasProperty("address", is("110 W. Liberty St."))))
|
||||
.andExpect(model().attribute("owner", hasProperty("city", is("Madison"))))
|
||||
.andExpect(model().attribute("owner", hasProperty("telephone", is("6085551023"))))
|
||||
.andExpect(model().attribute("owner", hasProperty("email", is("franklin.george@gmail.com"))))
|
||||
.andExpect(view().name("owners/createOrUpdateOwnerForm"));
|
||||
}
|
||||
|
||||
|
@ -188,7 +194,8 @@ class OwnerControllerTests {
|
|||
.param("lastName", "Bloggs")
|
||||
.param("address", "123 Caramel Street")
|
||||
.param("city", "London")
|
||||
.param("telephone", "01616291589"))
|
||||
.param("telephone", "01616291589")
|
||||
.param("email", "joe.big.bloggs@gmail.com"))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andExpect(view().name("redirect:/owners/{ownerId}"));
|
||||
}
|
||||
|
@ -206,11 +213,13 @@ class OwnerControllerTests {
|
|||
.perform(post("/owners/{ownerId}/edit", TEST_OWNER_ID).param("firstName", "Joe")
|
||||
.param("lastName", "Bloggs")
|
||||
.param("address", "")
|
||||
.param("telephone", ""))
|
||||
.param("telephone", "")
|
||||
.param("email", ""))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(model().attributeHasErrors("owner"))
|
||||
.andExpect(model().attributeHasFieldErrors("owner", "address"))
|
||||
.andExpect(model().attributeHasFieldErrors("owner", "telephone"))
|
||||
.andExpect(model().attributeHasFieldErrors("owner", "email"))
|
||||
.andExpect(view().name("owners/createOrUpdateOwnerForm"));
|
||||
}
|
||||
|
||||
|
@ -223,6 +232,7 @@ class OwnerControllerTests {
|
|||
.andExpect(model().attribute("owner", hasProperty("address", is("110 W. Liberty St."))))
|
||||
.andExpect(model().attribute("owner", hasProperty("city", is("Madison"))))
|
||||
.andExpect(model().attribute("owner", hasProperty("telephone", is("6085551023"))))
|
||||
.andExpect(model().attribute("owner", hasProperty("email", is("franklin.george@gmail.com"))))
|
||||
.andExpect(model().attribute("owner", hasProperty("pets", not(empty()))))
|
||||
.andExpect(model().attribute("owner", hasProperty("pets", new BaseMatcher<List<Pet>>() {
|
||||
|
||||
|
|
|
@ -111,6 +111,7 @@ class ClinicServiceTests {
|
|||
owner.setAddress("4, Evans Street");
|
||||
owner.setCity("Wollongong");
|
||||
owner.setTelephone("4444444444");
|
||||
owner.setEmail("sam.shultz@outlook.de");
|
||||
this.owners.save(owner);
|
||||
assertThat(owner.getId().longValue()).isNotEqualTo(0);
|
||||
|
||||
|
|
Loading…
Reference in a new issue