diff --git a/src/main/java/org/springframework/samples/petclinic/owner/PetController.java b/src/main/java/org/springframework/samples/petclinic/owner/OwnerPetController.java similarity index 95% rename from src/main/java/org/springframework/samples/petclinic/owner/PetController.java rename to src/main/java/org/springframework/samples/petclinic/owner/OwnerPetController.java index 781fb5805..683e43816 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/PetController.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/OwnerPetController.java @@ -18,6 +18,7 @@ package org.springframework.samples.petclinic.owner; import java.time.LocalDate; import java.util.Collection; +import java.util.Objects; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.util.StringUtils; @@ -40,13 +41,13 @@ import org.springframework.web.servlet.mvc.support.RedirectAttributes; */ @Controller @RequestMapping("/owners/{ownerId}") -class PetController { +class OwnerPetController { private static final String VIEWS_PETS_CREATE_OR_UPDATE_FORM = "pets/createOrUpdatePetForm"; private final OwnerRepository owners; - public PetController(OwnerRepository owners) { + public OwnerPetController(OwnerRepository owners) { this.owners = owners; } @@ -135,10 +136,10 @@ class PetController { String petName = pet.getName(); - // checking if the pet name already exist for the owner + // checking if the pet name already exists for the owner if (StringUtils.hasText(petName)) { Pet existingPet = owner.getPet(petName.toLowerCase(), false); - if (existingPet != null && existingPet.getId() != pet.getId()) { + if (existingPet != null && !Objects.equals(existingPet.getId(), pet.getId())) { result.rejectValue("name", "duplicate", "already exists"); } } 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..c3d9f326e 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/OwnerRepository.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/OwnerRepository.java @@ -79,4 +79,11 @@ public interface OwnerRepository extends Repository { @Transactional(readOnly = true) Page findAll(Pageable pageable); + /** + * Returns the {@link Owner} of a pet + * @param id the pet id to search for + * @return the {@link Owner} if found + */ + Owner findByPets_Id(Integer id); + } diff --git a/src/main/java/org/springframework/samples/petclinic/owner/PetRepository.java b/src/main/java/org/springframework/samples/petclinic/owner/PetRepository.java new file mode 100644 index 000000000..72b33322e --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/owner/PetRepository.java @@ -0,0 +1,20 @@ +package org.springframework.samples.petclinic.owner; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.Repository; +import org.springframework.transaction.annotation.Transactional; + +/** + * @author Thibault Helsmoortel + */ +public interface PetRepository extends Repository { + + @Query("SELECT DISTINCT pet FROM Pet pet WHERE lower(pet.name) LIKE :name% ") + @Transactional(readOnly = true) + Page findByName(String name, Pageable pageable); + + Pet findById(Integer id); + +} diff --git a/src/main/java/org/springframework/samples/petclinic/pet/PetController.java b/src/main/java/org/springframework/samples/petclinic/pet/PetController.java new file mode 100644 index 000000000..9c1cf18fe --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/pet/PetController.java @@ -0,0 +1,124 @@ +package org.springframework.samples.petclinic.pet; + +import java.util.List; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.samples.petclinic.owner.Owner; +import org.springframework.samples.petclinic.owner.OwnerRepository; +import org.springframework.samples.petclinic.owner.Pet; +import org.springframework.samples.petclinic.owner.PetRepository; +import org.springframework.samples.petclinic.owner.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.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.RequestParam; +import org.springframework.web.servlet.ModelAndView; + +/** + * @author Thibault Helsmoortel + */ +@Controller +public class PetController { + + private final PetRepository pets; + + private final OwnerRepository owners; + + public PetController(PetRepository pets, OwnerRepository owners) { + this.pets = pets; + this.owners = owners; + } + + @InitBinder("pet") + public void initPetBinder(WebDataBinder dataBinder) { + dataBinder.setValidator(new PetValidator()); + } + + @ModelAttribute("pet") + public Pet findPet(@PathVariable(name = "petId", required = false) Integer petId) { + if (petId == null) { + return new Pet(); + } + + Pet pet = pets.findById(petId); + if (pet == null) { + throw new IllegalArgumentException("Pet ID not found: " + petId); + } + + return pet; + } + + @GetMapping("/pets/find") + public String initFindForm() { + return "pets/findPets"; + } + + @GetMapping("/pets") + public String processFindForm(@RequestParam(defaultValue = "1") int page, Pet pet, BindingResult result, + Model model) { + // allow parameterless GET request for /pets to return all records + String petName = ""; + if (pet.getName() != null) { + petName = pet.getName().toLowerCase(); + } + + // find pets by name + Page petsResults = findPaginatedForPetName(page, petName); + if (petsResults.isEmpty()) { + // no pets found + result.rejectValue("name", "notFound", "not found"); + + return "pets/findPets"; + } + + if (petsResults.getTotalElements() == 1) { + // 1 pet found + pet = petsResults.iterator().next(); + + return "redirect:/pets/" + pet.getId(); + } + + // multiple pets found + return addPaginationModel(page, model, petsResults); + } + + private Page findPaginatedForPetName(int page, String name) { + int pageSize = 5; + Pageable pageable = PageRequest.of(page - 1, pageSize); + + return pets.findByName(name, pageable); + } + + private String addPaginationModel(int page, Model model, Page paginated) { + List listPets = paginated.getContent(); + model.addAttribute("currentPage", page); + model.addAttribute("totalPages", paginated.getTotalPages()); + model.addAttribute("totalItems", paginated.getTotalElements()); + model.addAttribute("listPets", listPets); + model.addAttribute("listPetAndOwners", + listPets.stream().map(pet -> new PetAndOwner(pet, owners.findByPets_Id(pet.getId()))).toList()); + + return "pets/petsList"; + } + + record PetAndOwner(Pet pet, Owner owner) { + } + + @GetMapping("/pets/{petId}") + public ModelAndView showPet(@PathVariable("petId") int petId) { + ModelAndView mav = new ModelAndView("pets/petDetails"); + Pet pet = this.pets.findById(petId); + Owner owner = owners.findByPets_Id(petId); + mav.addObject("pet", pet); + mav.addObject("owner", owner); + + return mav; + } + +} diff --git a/src/main/resources/templates/fragments/layout.html b/src/main/resources/templates/fragments/layout.html index d3250cce1..8021415ab 100755 --- a/src/main/resources/templates/fragments/layout.html +++ b/src/main/resources/templates/fragments/layout.html @@ -55,6 +55,11 @@ Find owners +
  • + + Find pets +
  • +
  • Veterinarians diff --git a/src/main/resources/templates/pets/findPets.html b/src/main/resources/templates/pets/findPets.html new file mode 100644 index 000000000..0815627fc --- /dev/null +++ b/src/main/resources/templates/pets/findPets.html @@ -0,0 +1,34 @@ + + + + +

    Find pets

    + +
    +
    +
    + +
    +
    +

    Error

    +
    +
    +
    +
    +
    +
    + +
    +
    + + Add pet + +
    + + + diff --git a/src/main/resources/templates/pets/petDetails.html b/src/main/resources/templates/pets/petDetails.html new file mode 100644 index 000000000..39de6ab7f --- /dev/null +++ b/src/main/resources/templates/pets/petDetails.html @@ -0,0 +1,77 @@ + + + + + + + +

    Pet Information

    + +
    + +
    + +
    + +
    + + + + + +
    +
    +
    Name
    +
    +
    Birth Date
    +
    +
    Type
    +
    +
    Owner
    +
    +
    +
    + + + + + + + + + + + + + + + + +
    Visit DateDescription
    Add Visit
    + + +
    +
    +
    + + + + + + diff --git a/src/main/resources/templates/pets/petsList.html b/src/main/resources/templates/pets/petsList.html new file mode 100644 index 000000000..c7f3b95b9 --- /dev/null +++ b/src/main/resources/templates/pets/petsList.html @@ -0,0 +1,58 @@ + + + + + + +

    Pets

    + + + + + + + + + + + + + + +
    NameOwner
    + + + +
    +
    + Pages: + [ + + [[${i}]] + [[${i}]] + + + + + + + + + + + + + + + + + + +
    + + + diff --git a/src/test/java/org/springframework/samples/petclinic/owner/PetControllerTests.java b/src/test/java/org/springframework/samples/petclinic/owner/OwnerPetControllerTests.java old mode 100755 new mode 100644 similarity index 97% rename from src/test/java/org/springframework/samples/petclinic/owner/PetControllerTests.java rename to src/test/java/org/springframework/samples/petclinic/owner/OwnerPetControllerTests.java index 73b83f9f1..4263c7863 --- a/src/test/java/org/springframework/samples/petclinic/owner/PetControllerTests.java +++ b/src/test/java/org/springframework/samples/petclinic/owner/OwnerPetControllerTests.java @@ -36,15 +36,15 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view; /** - * Test class for the {@link PetController} + * Test class for the {@link OwnerPetController} * * @author Colin But */ -@WebMvcTest(value = PetController.class, +@WebMvcTest(value = OwnerPetController.class, includeFilters = @ComponentScan.Filter(value = PetTypeFormatter.class, type = FilterType.ASSIGNABLE_TYPE)) @DisabledInNativeImage @DisabledInAotMode -class PetControllerTests { +class OwnerPetControllerTests { private static final int TEST_OWNER_ID = 1;