diff --git a/pom.xml b/pom.xml index cf47f8aa6..c8bee86bd 100644 --- a/pom.xml +++ b/pom.xml @@ -69,6 +69,13 @@ test + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.15.3 + + com.h2database @@ -220,7 +227,7 @@ spring-boot-maven-plugin - build-info @@ -378,7 +385,7 @@ - org.eclipse.m2e @@ -436,4 +443,4 @@ - \ No newline at end of file + diff --git a/src/main/java/org/springframework/samples/petclinic/api/OwnerApiController.java b/src/main/java/org/springframework/samples/petclinic/api/OwnerApiController.java new file mode 100644 index 000000000..ca987292d --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/api/OwnerApiController.java @@ -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 searchOwnersBySurname(@RequestParam String lastname) { + return ownerRepository.findAllByLastName(lastname); + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/owner/Owner.java b/src/main/java/org/springframework/samples/petclinic/owner/Owner.java index c7394689a..4261d60cb 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/Owner.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/Owner.java @@ -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); diff --git a/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java b/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java index 848d4f08e..976bf82ff 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java @@ -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 ownersResults = findPaginatedForOwnersLastName(page, ""); + addPaginationModel(page, model, ownersResults); return "owners/findOwners"; } diff --git a/src/main/java/org/springframework/samples/petclinic/owner/OwnerRepository.java b/src/main/java/org/springframework/samples/petclinic/owner/OwnerRepository.java index f44449439..ae537a64c 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/OwnerRepository.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/OwnerRepository.java @@ -57,6 +57,10 @@ public interface OwnerRepository extends Repository { @Transactional(readOnly = true) Page 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 findAllByLastName(@Param("lastName") String lastName); + /** * Retrieve an {@link Owner} from the data store by id. * @param id the id to search for diff --git a/src/main/java/org/springframework/samples/petclinic/owner/PetController.java b/src/main/java/org/springframework/samples/petclinic/owner/PetController.java index 781fb5805..f2accbf05 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/PetController.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/PetController.java @@ -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 request = new HttpEntity<>(petAsJson, headers); + ResponseEntity 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}"; } diff --git a/src/main/resources/db/h2/data.sql b/src/main/resources/db/h2/data.sql index f232b1361..d1fc79d1f 100644 --- a/src/main/resources/db/h2/data.sql +++ b/src/main/resources/db/h2/data.sql @@ -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 + diff --git a/src/main/resources/db/h2/schema.sql b/src/main/resources/db/h2/schema.sql index 4a6c322cb..05866edcf 100644 --- a/src/main/resources/db/h2/schema.sql +++ b/src/main/resources/db/h2/schema.sql @@ -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); diff --git a/src/main/resources/templates/owners/createOrUpdateOwnerForm.html b/src/main/resources/templates/owners/createOrUpdateOwnerForm.html index 72c40fbc6..49c038d93 100644 --- a/src/main/resources/templates/owners/createOrUpdateOwnerForm.html +++ b/src/main/resources/templates/owners/createOrUpdateOwnerForm.html @@ -16,6 +16,8 @@ th:replace="~{fragments/inputField :: input ('City', 'city', 'text')}" /> +
diff --git a/src/main/resources/templates/owners/findOwners.html b/src/main/resources/templates/owners/findOwners.html index 0a818fc79..5dfccb8db 100644 --- a/src/main/resources/templates/owners/findOwners.html +++ b/src/main/resources/templates/owners/findOwners.html @@ -28,6 +28,67 @@ Add Owner +
+   +   +
+ +

Owners

+ + + + + + + + + + + + + + + + + + +
NameAddressCityTelephoneEmailPets
+ + + + + +
+
+ Pages: + [ + + [[${i}]] + [[${i}]] + + + + + + + + + + + + + + + + + + +
+ diff --git a/src/main/resources/templates/owners/ownerDetails.html b/src/main/resources/templates/owners/ownerDetails.html index 15bca4ad2..c4707a0f6 100644 --- a/src/main/resources/templates/owners/ownerDetails.html +++ b/src/main/resources/templates/owners/ownerDetails.html @@ -36,6 +36,10 @@ Telephone + + Email + + Edit diff --git a/src/main/resources/templates/owners/ownersList.html b/src/main/resources/templates/owners/ownersList.html index 9af325289..4e9ac5e30 100644 --- a/src/main/resources/templates/owners/ownersList.html +++ b/src/main/resources/templates/owners/ownersList.html @@ -13,6 +13,7 @@ Address City Telephone + Email Pets @@ -24,6 +25,7 @@ + diff --git a/src/test/java/org/springframework/samples/petclinic/owner/OwnerControllerTests.java b/src/test/java/org/springframework/samples/petclinic/owner/OwnerControllerTests.java index 78b812371..12e71853c 100644 --- a/src/test/java/org/springframework/samples/petclinic/owner/OwnerControllerTests.java +++ b/src/test/java/org/springframework/samples/petclinic/owner/OwnerControllerTests.java @@ -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 paginatedOwners = new PageImpl(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>() { diff --git a/src/test/java/org/springframework/samples/petclinic/service/ClinicServiceTests.java b/src/test/java/org/springframework/samples/petclinic/service/ClinicServiceTests.java index d7240f351..7b415a745 100644 --- a/src/test/java/org/springframework/samples/petclinic/service/ClinicServiceTests.java +++ b/src/test/java/org/springframework/samples/petclinic/service/ClinicServiceTests.java @@ -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);