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 480a7a6900a..f8fb6ebbced 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/Owner.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/Owner.java @@ -173,4 +173,21 @@ public void addVisit(Integer petId, Visit visit) { pet.addVisit(visit); } + /** + * Adds the given {@link Vaccine} to the {@link Pet} with the given identifier. + * @param petId the identifier of the {@link Pet}, must not be {@literal null}. + * @param vaccine the vaccine to add, must not be {@literal null}. + */ + public void addVaccine(Integer petId, Vaccine vaccine) { + + Assert.notNull(petId, "Pet identifier must not be null!"); + Assert.notNull(vaccine, "Vaccine must not be null!"); + + Pet pet = getPet(petId); + + Assert.notNull(pet, "Invalid Pet identifier!"); + + pet.addVaccine(vaccine); + } + } diff --git a/src/main/java/org/springframework/samples/petclinic/owner/Pet.java b/src/main/java/org/springframework/samples/petclinic/owner/Pet.java index 4f8409ef247..93e75eb4d5d 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/Pet.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/Pet.java @@ -58,6 +58,11 @@ public class Pet extends NamedEntity { @OrderBy("date ASC") private final Set visits = new LinkedHashSet<>(); + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) + @JoinColumn(name = "pet_id") + @OrderBy("vaccinationDate DESC") + private final Set vaccines = new LinkedHashSet<>(); + public void setBirthDate(LocalDate birthDate) { this.birthDate = birthDate; } @@ -82,4 +87,12 @@ public void addVisit(Visit visit) { getVisits().add(visit); } + public Collection getVaccines() { + return this.vaccines; + } + + public void addVaccine(Vaccine vaccine) { + getVaccines().add(vaccine); + } + } diff --git a/src/main/java/org/springframework/samples/petclinic/owner/Vaccine.java b/src/main/java/org/springframework/samples/petclinic/owner/Vaccine.java new file mode 100644 index 00000000000..dd7ba11a8d0 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/owner/Vaccine.java @@ -0,0 +1,80 @@ +/* + * Copyright 2012-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.owner; + +import java.time.LocalDate; + +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.samples.petclinic.model.BaseEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotBlank; + +/** + * Simple JavaBean domain object representing a vaccine record. + * + * @author Spring PetClinic + */ +@Entity +@Table(name = "vaccines") +public class Vaccine extends BaseEntity { + + @Column(name = "vaccine_name") + @NotBlank + private String name; + + @Column(name = "vaccination_date") + @DateTimeFormat(pattern = "yyyy-MM-dd") + private LocalDate vaccinationDate; + + @Column(name = "next_reminder_date") + @DateTimeFormat(pattern = "yyyy-MM-dd") + private LocalDate nextReminderDate; + + /** + * Creates a new instance of Vaccine for the current date + */ + public Vaccine() { + this.vaccinationDate = LocalDate.now(); + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public LocalDate getVaccinationDate() { + return this.vaccinationDate; + } + + public void setVaccinationDate(LocalDate vaccinationDate) { + this.vaccinationDate = vaccinationDate; + } + + public LocalDate getNextReminderDate() { + return this.nextReminderDate; + } + + public void setNextReminderDate(LocalDate nextReminderDate) { + this.nextReminderDate = nextReminderDate; + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/owner/VaccineController.java b/src/main/java/org/springframework/samples/petclinic/owner/VaccineController.java new file mode 100644 index 00000000000..eddbec338d4 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/owner/VaccineController.java @@ -0,0 +1,99 @@ +/* + * Copyright 2012-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.owner; + +import java.util.Map; +import java.util.Optional; + +import org.springframework.stereotype.Controller; +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 jakarta.validation.Valid; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; + +/** + * @author Spring PetClinic + */ +@Controller +class VaccineController { + + private final OwnerRepository owners; + + public VaccineController(OwnerRepository owners) { + this.owners = owners; + } + + @InitBinder + public void setAllowedFields(WebDataBinder dataBinder) { + dataBinder.setDisallowedFields("id"); + } + + /** + * Called before each and every @RequestMapping annotated method. 2 goals: - Make sure + * we always have fresh data - Since we do not use the session scope, make sure that + * Pet object always has an id (Even though id is not part of the form fields) + * @param petId + * @return Pet + */ + @ModelAttribute("vaccine") + public Vaccine loadPetWithVaccine(@PathVariable("ownerId") int ownerId, @PathVariable("petId") int petId, + Map model) { + Optional optionalOwner = owners.findById(ownerId); + Owner owner = optionalOwner.orElseThrow(() -> new IllegalArgumentException( + "Owner not found with id: " + ownerId + ". Please ensure the ID is correct ")); + + Pet pet = owner.getPet(petId); + if (pet == null) { + throw new IllegalArgumentException( + "Pet with id " + petId + " not found for owner with id " + ownerId + "."); + } + model.put("pet", pet); + model.put("owner", owner); + + Vaccine vaccine = new Vaccine(); + pet.addVaccine(vaccine); + return vaccine; + } + + // Spring MVC calls method loadPetWithVaccine(...) before initNewVaccineForm is + // called + @GetMapping("/owners/{ownerId}/pets/{petId}/vaccines/new") + public String initNewVaccineForm() { + return "pets/createOrUpdateVaccineForm"; + } + + // Spring MVC calls method loadPetWithVaccine(...) before processNewVaccineForm is + // called + @PostMapping("/owners/{ownerId}/pets/{petId}/vaccines/new") + public String processNewVaccineForm(@ModelAttribute Owner owner, @PathVariable int petId, @Valid Vaccine vaccine, + BindingResult result, RedirectAttributes redirectAttributes) { + if (result.hasErrors()) { + return "pets/createOrUpdateVaccineForm"; + } + + owner.addVaccine(petId, vaccine); + this.owners.save(owner); + redirectAttributes.addFlashAttribute("message", "Vaccine record has been added"); + return "redirect:/owners/{ownerId}"; + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/owner/VaccineRepository.java b/src/main/java/org/springframework/samples/petclinic/owner/VaccineRepository.java new file mode 100644 index 00000000000..1bee59e3e6d --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/owner/VaccineRepository.java @@ -0,0 +1,29 @@ +/* + * Copyright 2012-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.owner; + +import org.springframework.data.jpa.repository.JpaRepository; + +/** + * Repository class for Vaccine domain objects. All method names are compliant + * with Spring Data naming conventions so this interface can easily be extended for Spring + * Data. + * + * @author Spring PetClinic + */ +public interface VaccineRepository extends JpaRepository { + +}