diff --git a/pom.xml b/pom.xml
index fb38cc3db..9bbe1c7d6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
org.springframework.bootspring-boot-starter-parent
- 4.0.1
+ 3.2.2org.springframework.samplesspring-petclinic
@@ -41,122 +41,93 @@
-
- org.springframework.boot
- spring-boot-starter-actuator
+ org.springframework.boot
+ spring-boot-starter-web
+
+
- org.springframework.boot
- spring-boot-starter-cache
+ org.springframework.boot
+ spring-boot-starter-thymeleaf
+
+
- org.springframework.boot
- spring-boot-starter-data-jpa
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
- org.springframework.boot
- spring-boot-starter-thymeleaf
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
- org.springframework.boot
- spring-boot-starter-validation
+ org.springframework.boot
+ spring-boot-starter-aop
+
+
- org.springframework.boot
- spring-boot-starter-webmvc
+ org.springframework.boot
+ spring-boot-starter-cache
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+
+
+ com.h2database
+ h2
+ runtime
- javax.cache
- cache-api
+ org.postgresql
+ postgresql
+ runtime
+
+
- jakarta.xml.bind
- jakarta.xml.bind-api
+ org.springframework.boot
+ spring-boot-devtools
+ true
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+ org.testcontainers
+ junit-jupiter
+ test
- com.h2database
- h2
- runtime
-
-
- com.github.ben-manes.caffeine
- caffeine
- runtime
-
-
- com.mysql
- mysql-connector-j
- runtime
-
-
- org.postgresql
- postgresql
- runtime
-
-
- org.webjars
- webjars-locator-lite
- ${webjars-locator.version}
- runtime
-
-
- org.webjars.npm
- bootstrap
- ${webjars-bootstrap.version}
- runtime
-
-
- org.webjars.npm
- font-awesome
- ${webjars-font-awesome.version}
- runtime
+ org.testcontainers
+ mysql
+ test
- org.springframework.boot
- spring-boot-devtools
- true
+ jakarta.xml.bind
+ jakarta.xml.bind-api
-
- org.springframework.boot
- spring-boot-starter-data-jpa-test
- test
-
-
- org.springframework.boot
- spring-boot-starter-restclient-test
- test
-
-
- org.springframework.boot
- spring-boot-starter-webmvc-test
- test
-
-
- org.springframework.boot
- spring-boot-testcontainers
- test
-
-
- org.springframework.boot
- spring-boot-docker-compose
- test
-
-
- org.testcontainers
- testcontainers-junit-jupiter
- test
-
-
- org.testcontainers
- testcontainers-mysql
- test
-
-
+
@@ -193,6 +164,30 @@
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ true
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.11.0
+
+
+ true
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+ true
+
+org.apache.maven.pluginsmaven-checkstyle-plugin
diff --git a/src/main/java/org/springframework/samples/petclinic/feature/FeatureFlag.java b/src/main/java/org/springframework/samples/petclinic/feature/FeatureFlag.java
new file mode 100644
index 000000000..f09275763
--- /dev/null
+++ b/src/main/java/org/springframework/samples/petclinic/feature/FeatureFlag.java
@@ -0,0 +1,108 @@
+package org.springframework.samples.petclinic.feature;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.EnumType;
+import jakarta.persistence.Enumerated;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.Table;
+import jakarta.persistence.UniqueConstraint;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+
+@Entity
+@Table(name = "feature_flags", uniqueConstraints = @UniqueConstraint(columnNames = "name"))
+public class FeatureFlag {
+
+ public enum Strategy {
+
+ BOOLEAN, PERCENTAGE, WHITELIST, BLACKLIST
+
+ }
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Integer id;
+
+ @NotEmpty
+ @Column(nullable = false, length = 100)
+ private String name;
+
+ @Column(nullable = false)
+ private boolean enabled = false;
+
+ @NotNull
+ @Enumerated(EnumType.STRING)
+ @Column(nullable = false)
+ private Strategy strategy = Strategy.BOOLEAN;
+
+ @Column
+ private int percentage = 100;
+
+ @Column(length = 2000)
+ private String userList;
+
+ @Column(length = 500)
+ private String description;
+
+ // Getters & Setters
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public Strategy getStrategy() {
+ return strategy;
+ }
+
+ public void setStrategy(Strategy strategy) {
+ this.strategy = strategy;
+ }
+
+ public int getPercentage() {
+ return percentage;
+ }
+
+ public void setPercentage(int percentage) {
+ this.percentage = Math.max(0, Math.min(100, percentage));
+ }
+
+ public String getUserList() {
+ return userList;
+ }
+
+ public void setUserList(String userList) {
+ this.userList = userList;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+}
diff --git a/src/main/java/org/springframework/samples/petclinic/feature/FeatureFlagController.java b/src/main/java/org/springframework/samples/petclinic/feature/FeatureFlagController.java
new file mode 100644
index 000000000..35e962559
--- /dev/null
+++ b/src/main/java/org/springframework/samples/petclinic/feature/FeatureFlagController.java
@@ -0,0 +1,43 @@
+package org.springframework.samples.petclinic.feature;
+
+import java.util.List;
+
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/api/feature-flags")
+public class FeatureFlagController {
+
+ private final FeatureFlagService service;
+
+ public FeatureFlagController(FeatureFlagService service) {
+ this.service = service;
+ }
+
+ @GetMapping
+ public List getAll() {
+ return service.findAll();
+ }
+
+ @GetMapping("/{id}")
+ public FeatureFlag get(@PathVariable Integer id) {
+ return service.findById(id).orElseThrow();
+ }
+
+ @PostMapping
+ public FeatureFlag create(@RequestBody FeatureFlag flag) {
+ return service.save(flag);
+ }
+
+ @PutMapping("/{id}")
+ public FeatureFlag update(@PathVariable Integer id, @RequestBody FeatureFlag flag) {
+ flag.setId(id);
+ return service.save(flag);
+ }
+
+ @DeleteMapping("/{id}")
+ public void delete(@PathVariable Integer id) {
+ service.deleteById(id);
+ }
+
+}
diff --git a/src/main/java/org/springframework/samples/petclinic/feature/FeatureFlagRepository.java b/src/main/java/org/springframework/samples/petclinic/feature/FeatureFlagRepository.java
new file mode 100644
index 000000000..e16fd32de
--- /dev/null
+++ b/src/main/java/org/springframework/samples/petclinic/feature/FeatureFlagRepository.java
@@ -0,0 +1,11 @@
+package org.springframework.samples.petclinic.feature;
+
+import java.util.Optional;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface FeatureFlagRepository extends JpaRepository {
+
+ Optional findByName(String name);
+
+}
diff --git a/src/main/java/org/springframework/samples/petclinic/feature/FeatureFlagService.java b/src/main/java/org/springframework/samples/petclinic/feature/FeatureFlagService.java
new file mode 100644
index 000000000..32b6544a2
--- /dev/null
+++ b/src/main/java/org/springframework/samples/petclinic/feature/FeatureFlagService.java
@@ -0,0 +1,62 @@
+package org.springframework.samples.petclinic.feature;
+
+import java.util.List;
+import java.util.Optional;
+
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@Transactional
+public class FeatureFlagService {
+
+ private final FeatureFlagRepository repository;
+
+ public FeatureFlagService(FeatureFlagRepository repository) {
+ this.repository = repository;
+ }
+
+ /* ===================== CRUD ===================== */
+
+ public List findAll() {
+ return repository.findAll();
+ }
+
+ public Optional findById(Integer id) {
+ return repository.findById(id);
+ }
+
+ public FeatureFlag save(FeatureFlag flag) {
+ return repository.save(flag);
+ }
+
+ public void deleteById(Integer id) {
+ repository.deleteById(id);
+ }
+
+ /* ===================== FEATURE CHECK ===================== */
+
+ @Transactional(readOnly = true)
+ public boolean isEnabled(String flagName, String user) {
+
+ Optional flagOpt = repository.findByName(flagName);
+
+ if (flagOpt.isEmpty()) {
+ return false;
+ }
+
+ FeatureFlag flag = flagOpt.get();
+
+ if (!flag.isEnabled()) {
+ return false;
+ }
+
+ return switch (flag.getStrategy()) {
+ case BOOLEAN -> true;
+ case PERCENTAGE -> Math.abs(user.hashCode()) % 100 < flag.getPercentage();
+ case WHITELIST -> flag.getUserList() != null && flag.getUserList().contains(user);
+ case BLACKLIST -> flag.getUserList() == null || !flag.getUserList().contains(user);
+ };
+ }
+
+}
diff --git a/src/main/java/org/springframework/samples/petclinic/feature/FeatureToggle.java b/src/main/java/org/springframework/samples/petclinic/feature/FeatureToggle.java
new file mode 100644
index 000000000..9175c611a
--- /dev/null
+++ b/src/main/java/org/springframework/samples/petclinic/feature/FeatureToggle.java
@@ -0,0 +1,11 @@
+package org.springframework.samples.petclinic.feature;
+
+import java.lang.annotation.*;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface FeatureToggle {
+
+ String value();
+
+}
diff --git a/src/main/java/org/springframework/samples/petclinic/feature/FeatureToggleAspect.java b/src/main/java/org/springframework/samples/petclinic/feature/FeatureToggleAspect.java
new file mode 100644
index 000000000..91b923bee
--- /dev/null
+++ b/src/main/java/org/springframework/samples/petclinic/feature/FeatureToggleAspect.java
@@ -0,0 +1,29 @@
+package org.springframework.samples.petclinic.feature;
+
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Component;
+import org.springframework.web.server.ResponseStatusException;
+
+@Aspect
+@Component
+public class FeatureToggleAspect {
+
+ private final FeatureFlagService featureFlagService;
+
+ public FeatureToggleAspect(FeatureFlagService featureFlagService) {
+ this.featureFlagService = featureFlagService;
+ }
+
+ @Before("@annotation(featureToggle)")
+ public void checkFeature(JoinPoint joinPoint, FeatureToggle featureToggle) {
+ String user = "anonymous"; // can be replaced later
+ if (!featureFlagService.isEnabled(featureToggle.value(), user)) {
+ throw new ResponseStatusException(HttpStatus.FORBIDDEN,
+ "Feature " + featureToggle.value() + " is disabled");
+ }
+ }
+
+}
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 199ca3611..e5b327e0d 100644
--- a/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java
+++ b/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java
@@ -23,7 +23,6 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
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;
@@ -37,6 +36,10 @@ import org.springframework.web.servlet.ModelAndView;
import jakarta.validation.Valid;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.server.ResponseStatusException;
+import org.springframework.samples.petclinic.feature.FeatureFlagService;
+import org.springframework.ui.Model;
/**
* @author Juergen Hoeller
@@ -52,8 +55,11 @@ class OwnerController {
private final OwnerRepository owners;
- public OwnerController(OwnerRepository owners) {
+ private final FeatureFlagService featureFlagService;
+
+ public OwnerController(OwnerRepository owners, FeatureFlagService featureFlagService) {
this.owners = owners;
+ this.featureFlagService = featureFlagService;
}
@InitBinder
@@ -92,30 +98,24 @@ class OwnerController {
}
@GetMapping("/owners")
- public String processFindForm(@RequestParam(defaultValue = "1") int page, Owner owner, BindingResult result,
- Model model) {
- // allow parameterless GET request for /owners to return all records
- String lastName = owner.getLastName();
- if (lastName == null) {
- lastName = ""; // empty string signifies broadest possible search
+ public String processFindForm(Owner owner, BindingResult result, Model model) {
+
+ if (!featureFlagService.isEnabled("OWNER_SEARCH", "anonymous")) {
+ throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Owner search feature is disabled");
}
- // find owners by last name
- Page ownersResults = findPaginatedForOwnersLastName(page, lastName);
- if (ownersResults.isEmpty()) {
- // no owners found
+ if (owner.getLastName() == null) {
+ owner.setLastName("");
+ }
+
+ List results = this.owners.findByLastNameContaining(owner.getLastName());
+ if (results.isEmpty()) {
result.rejectValue("lastName", "notFound", "not found");
return "owners/findOwners";
}
- if (ownersResults.getTotalElements() == 1) {
- // 1 owner found
- owner = ownersResults.iterator().next();
- return "redirect:/owners/" + owner.getId();
- }
-
- // multiple owners found
- return addPaginationModel(page, model, ownersResults);
+ model.addAttribute("selections", results);
+ return "owners/ownersList";
}
private String addPaginationModel(int page, Model model, Page paginated) {
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 d2b3dde40..56902e9bb 100644
--- a/src/main/java/org/springframework/samples/petclinic/owner/OwnerRepository.java
+++ b/src/main/java/org/springframework/samples/petclinic/owner/OwnerRepository.java
@@ -20,6 +20,7 @@ import java.util.Optional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
+import java.util.List;
/**
* Repository class for Owner domain objects. All method names are compliant
@@ -59,4 +60,6 @@ public interface OwnerRepository extends JpaRepository {
*/
Optional findById(Integer id);
+ List findByLastNameContaining(String lastName);
+
}
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 8398e4f13..7861572c8 100644
--- a/src/main/java/org/springframework/samples/petclinic/owner/PetController.java
+++ b/src/main/java/org/springframework/samples/petclinic/owner/PetController.java
@@ -1,18 +1,3 @@
-/*
- * 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;
@@ -20,22 +5,18 @@ import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
+import jakarta.validation.Valid;
+
import org.springframework.stereotype.Controller;
-import org.springframework.ui.ModelMap;
+import org.springframework.ui.Model;
import org.springframework.util.Assert;
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.servlet.mvc.support.RedirectAttributes;
+import org.springframework.samples.petclinic.feature.FeatureToggle;
/**
* @author Juergen Hoeller
@@ -58,6 +39,10 @@ class PetController {
this.types = types;
}
+ /*
+ * ========================= MODEL ATTRIBUTES =========================
+ */
+
@ModelAttribute("types")
public Collection populatePetTypes() {
return this.types.findPetTypes();
@@ -65,10 +50,8 @@ class PetController {
@ModelAttribute("owner")
public Owner findOwner(@PathVariable("ownerId") int ownerId) {
- Optional optionalOwner = this.owners.findById(ownerId);
- Owner owner = optionalOwner.orElseThrow(() -> new IllegalArgumentException(
- "Owner not found with id: " + ownerId + ". Please ensure the ID is correct "));
- return owner;
+ return this.owners.findById(ownerId)
+ .orElseThrow(() -> new IllegalArgumentException("Owner not found with id: " + ownerId));
}
@ModelAttribute("pet")
@@ -79,12 +62,20 @@ class PetController {
return new Pet();
}
- Optional optionalOwner = this.owners.findById(ownerId);
- Owner owner = optionalOwner.orElseThrow(() -> new IllegalArgumentException(
- "Owner not found with id: " + ownerId + ". Please ensure the ID is correct "));
- return owner.getPet(petId);
+ Owner owner = findOwner(ownerId);
+ Pet pet = owner.getPet(petId);
+
+ if (pet == null) {
+ throw new IllegalArgumentException("Pet not found with id: " + petId);
+ }
+
+ return pet;
}
+ /*
+ * ========================= BINDERS =========================
+ */
+
@InitBinder("owner")
public void initOwnerBinder(WebDataBinder dataBinder) {
dataBinder.setDisallowedFields("id");
@@ -95,35 +86,36 @@ class PetController {
dataBinder.setValidator(new PetValidator());
}
+ /*
+ * ========================= CREATE PET =========================
+ */
+
@GetMapping("/pets/new")
- public String initCreationForm(Owner owner, ModelMap model) {
+ public String initCreationForm(Owner owner, Model model) {
Pet pet = new Pet();
- owner.addPet(pet);
+ model.addAttribute("pet", pet);
return VIEWS_PETS_CREATE_OR_UPDATE_FORM;
}
+ @FeatureToggle("ADD_PET")
@PostMapping("/pets/new")
- public String processCreationForm(Owner owner, @Valid Pet pet, BindingResult result,
- RedirectAttributes redirectAttributes) {
-
- if (StringUtils.hasText(pet.getName()) && pet.isNew() && owner.getPet(pet.getName(), true) != null)
- result.rejectValue("name", "duplicate", "already exists");
-
- LocalDate currentDate = LocalDate.now();
- if (pet.getBirthDate() != null && pet.getBirthDate().isAfter(currentDate)) {
- result.rejectValue("birthDate", "typeMismatch.birthDate");
- }
+ public String processCreationForm(@Valid Pet pet, BindingResult result, Owner owner, Model model) {
if (result.hasErrors()) {
+ model.addAttribute("pet", pet);
return VIEWS_PETS_CREATE_OR_UPDATE_FORM;
}
owner.addPet(pet);
this.owners.save(owner);
- redirectAttributes.addFlashAttribute("message", "New Pet has been Added");
+
return "redirect:/owners/{ownerId}";
}
+ /*
+ * ========================= UPDATE PET =========================
+ */
+
@GetMapping("/pets/{petId}/edit")
public String initUpdateForm() {
return VIEWS_PETS_CREATE_OR_UPDATE_FORM;
@@ -135,7 +127,6 @@ class PetController {
String petName = pet.getName();
- // checking if the pet name already exists for the owner
if (StringUtils.hasText(petName)) {
Pet existingPet = owner.getPet(petName, false);
if (existingPet != null && !Objects.equals(existingPet.getId(), pet.getId())) {
@@ -143,8 +134,7 @@ class PetController {
}
}
- LocalDate currentDate = LocalDate.now();
- if (pet.getBirthDate() != null && pet.getBirthDate().isAfter(currentDate)) {
+ if (pet.getBirthDate() != null && pet.getBirthDate().isAfter(LocalDate.now())) {
result.rejectValue("birthDate", "typeMismatch.birthDate");
}
@@ -153,21 +143,21 @@ class PetController {
}
updatePetDetails(owner, pet);
- redirectAttributes.addFlashAttribute("message", "Pet details has been edited");
+ redirectAttributes.addFlashAttribute("message", "Pet details updated");
+
return "redirect:/owners/{ownerId}";
}
- /**
- * Updates the pet details if it exists or adds a new pet to the owner.
- * @param owner The owner of the pet
- * @param pet The pet with updated details
+ /*
+ * ========================= HELPER =========================
*/
+
private void updatePetDetails(Owner owner, Pet pet) {
Integer id = pet.getId();
Assert.state(id != null, "'pet.getId()' must not be null");
+
Pet existingPet = owner.getPet(id);
if (existingPet != null) {
- // Update existing pet's properties
existingPet.setName(pet.getName());
existingPet.setBirthDate(pet.getBirthDate());
existingPet.setType(pet.getType());
@@ -175,6 +165,7 @@ class PetController {
else {
owner.addPet(pet);
}
+
this.owners.save(owner);
}
diff --git a/src/main/java/org/springframework/samples/petclinic/owner/VisitController.java b/src/main/java/org/springframework/samples/petclinic/owner/VisitController.java
index cc3e3ce1a..8910ac95c 100644
--- a/src/main/java/org/springframework/samples/petclinic/owner/VisitController.java
+++ b/src/main/java/org/springframework/samples/petclinic/owner/VisitController.java
@@ -29,6 +29,10 @@ import org.springframework.web.bind.annotation.PostMapping;
import jakarta.validation.Valid;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.server.ResponseStatusException;
+import org.springframework.samples.petclinic.feature.FeatureToggle;
+import org.springframework.samples.petclinic.feature.FeatureFlagService;
/**
* @author Juergen Hoeller
@@ -88,16 +92,21 @@ class VisitController {
// Spring MVC calls method loadPetWithVisit(...) before processNewVisitForm is
// called
+ @FeatureToggle("ADD_VISIT")
@PostMapping("/owners/{ownerId}/pets/{petId}/visits/new")
- public String processNewVisitForm(@ModelAttribute Owner owner, @PathVariable int petId, @Valid Visit visit,
- BindingResult result, RedirectAttributes redirectAttributes) {
+ public String processNewVisitForm(@PathVariable("ownerId") int ownerId, @PathVariable("petId") int petId,
+ @Valid Visit visit, BindingResult result) {
+
if (result.hasErrors()) {
return "pets/createOrUpdateVisitForm";
}
+ Owner owner = this.owners.findById(ownerId)
+ .orElseThrow(() -> new IllegalArgumentException("Owner not found with id: " + ownerId));
+
owner.addVisit(petId, visit);
this.owners.save(owner);
- redirectAttributes.addFlashAttribute("message", "Your visit has been booked");
+
return "redirect:/owners/{ownerId}";
}
diff --git a/src/main/java/org/springframework/samples/petclinic/system/CacheConfiguration.java b/src/main/java/org/springframework/samples/petclinic/system/CacheConfiguration.java
deleted file mode 100644
index 13cb74301..000000000
--- a/src/main/java/org/springframework/samples/petclinic/system/CacheConfiguration.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.system;
-
-import org.springframework.boot.cache.autoconfigure.JCacheManagerCustomizer;
-import org.springframework.cache.annotation.EnableCaching;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-import javax.cache.configuration.MutableConfiguration;
-
-/**
- * Cache configuration intended for caches providing the JCache API. This configuration
- * creates the used cache for the application and enables statistics that become
- * accessible via JMX.
- */
-@Configuration(proxyBeanMethods = false)
-@EnableCaching
-class CacheConfiguration {
-
- @Bean
- public JCacheManagerCustomizer petclinicCacheConfigurationCustomizer() {
- return cm -> cm.createCache("vets", cacheConfiguration());
- }
-
- /**
- * Create a simple configuration that enable statistics via the JCache programmatic
- * configuration API.
- *
- * Within the configuration object that is provided by the JCache API standard, there
- * is only a very limited set of configuration options. The really relevant
- * configuration options (like the size limit) must be set via a configuration
- * mechanism that is provided by the selected JCache implementation.
- */
- private javax.cache.configuration.Configuration