From 802c769cf11e14d752f5fd0bf1cb44464bc455e2 Mon Sep 17 00:00:00 2001 From: formatunitedandreas-code Date: Sun, 12 Jul 2026 12:52:34 +0200 Subject: [PATCH 1/7] Refactor PetClinic low-risk controller and service cleanup PR #1 branch threshold-governed-refactor-demo local validation: Maven test BUILD SUCCESS CI: all visible checks passed non-claims: no upstream interaction, no release, no deploy, no public readiness/correctness/security/compliance claim --- .github/workflows/ci-minimal.yml | 19 +++++++++++++++++ pom.xml | 4 ++-- .../petclinic/service/ClinicService.java | 2 +- .../petclinic/service/ClinicServiceImpl.java | 16 +++++++------- .../petclinic/web/CrashController.java | 3 +-- .../petclinic/web/OwnerController.java | 18 +++++++--------- .../samples/petclinic/web/PetController.java | 21 ++++++++++++------- .../petclinic/web/PetTypeFormatter.java | 3 +-- .../samples/petclinic/web/PetValidator.java | 3 +-- .../samples/petclinic/web/VetController.java | 9 +++----- .../petclinic/web/VisitController.java | 9 ++++---- .../WEB-INF/jsp/owners/ownerDetails.jsp | 4 ++-- .../samples/petclinic/model/OwnerTests.java | 1 + .../petclinic/web/VisitControllerTests.java | 8 +++---- src/test/jmeter/petclinic_test_plan.jmx | 19 ----------------- 15 files changed, 68 insertions(+), 71 deletions(-) create mode 100644 .github/workflows/ci-minimal.yml diff --git a/.github/workflows/ci-minimal.yml b/.github/workflows/ci-minimal.yml new file mode 100644 index 000000000..453248706 --- /dev/null +++ b/.github/workflows/ci-minimal.yml @@ -0,0 +1,19 @@ +name: Java CI (minimal tests) + +on: + push: + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + cache: maven + - name: Run Maven tests + run: ./mvnw -B test diff --git a/pom.xml b/pom.xml index 831084563..bacef40a4 100644 --- a/pom.xml +++ b/pom.xml @@ -62,7 +62,7 @@ 2.10.0 5.23.0 3.0 - 6.1.0 + 6.1.1 8.1.0 @@ -81,7 +81,7 @@ 3.5.5 3.5.1 0.8.15 - 3.6.2 + 3.6.3 0.3.4 diff --git a/src/main/java/org/springframework/samples/petclinic/service/ClinicService.java b/src/main/java/org/springframework/samples/petclinic/service/ClinicService.java index f6f850943..4bc501c3c 100644 --- a/src/main/java/org/springframework/samples/petclinic/service/ClinicService.java +++ b/src/main/java/org/springframework/samples/petclinic/service/ClinicService.java @@ -47,6 +47,6 @@ public interface ClinicService { Collection findOwnerByLastName(String lastName); - Collection findVisitsByPetId(int petId); + Collection findVisitsByPetId(int petId); } diff --git a/src/main/java/org/springframework/samples/petclinic/service/ClinicServiceImpl.java b/src/main/java/org/springframework/samples/petclinic/service/ClinicServiceImpl.java index dc7f5a2fa..3ade528a2 100644 --- a/src/main/java/org/springframework/samples/petclinic/service/ClinicServiceImpl.java +++ b/src/main/java/org/springframework/samples/petclinic/service/ClinicServiceImpl.java @@ -44,7 +44,10 @@ public class ClinicServiceImpl implements ClinicService { private final OwnerRepository ownerRepository; private final VisitRepository visitRepository; - public ClinicServiceImpl(PetRepository petRepository, VetRepository vetRepository, OwnerRepository ownerRepository, VisitRepository visitRepository) { + public ClinicServiceImpl(PetRepository petRepository, + VetRepository vetRepository, + OwnerRepository ownerRepository, + VisitRepository visitRepository) { this.petRepository = petRepository; this.vetRepository = vetRepository; this.ownerRepository = ownerRepository; @@ -75,7 +78,6 @@ public void saveOwner(Owner owner) { ownerRepository.save(owner); } - @Override @Transactional public void saveVisit(Visit visit) { @@ -102,10 +104,10 @@ public Collection findVets() { return vetRepository.findAll(); } - @Override - public Collection findVisitsByPetId(int petId) { - return visitRepository.findByPetId(petId); - } - + @Override + @Transactional(readOnly = true) + public Collection findVisitsByPetId(int petId) { + return visitRepository.findByPetId(petId); + } } diff --git a/src/main/java/org/springframework/samples/petclinic/web/CrashController.java b/src/main/java/org/springframework/samples/petclinic/web/CrashController.java index d5ca7642c..b4adacc71 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/CrashController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/CrashController.java @@ -31,8 +31,7 @@ public class CrashController { @GetMapping(value = "/oups") public String triggerException() { - throw new RuntimeException("Expected: controller used to showcase what " + - "happens when an exception is thrown"); + throw new RuntimeException("Expected: controller used to showcase what happens when an exception is thrown"); } diff --git a/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java b/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java index 5f6d767bd..b60893810 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java @@ -39,6 +39,7 @@ public class OwnerController { private static final String VIEWS_OWNER_CREATE_OR_UPDATE_FORM = "owners/createOrUpdateOwnerForm"; + private static final String VIEWS_OWNER_FIND_OWNERS = "owners/findOwners"; private final ClinicService clinicService; public OwnerController(ClinicService clinicService) { @@ -52,8 +53,7 @@ public void setAllowedFields(WebDataBinder dataBinder) { @GetMapping(value = "/owners/new") public String initCreationForm(Map model) { - Owner owner = new Owner(); - model.put("owner", owner); + model.put("owner", new Owner()); return VIEWS_OWNER_CREATE_OR_UPDATE_FORM; } @@ -70,7 +70,7 @@ public String processCreationForm(@Valid Owner owner, BindingResult result) { @GetMapping(value = "/owners/find") public String initFindForm(Map model) { model.put("owner", new Owner()); - return "owners/findOwners"; + return VIEWS_OWNER_FIND_OWNERS; } @GetMapping(value = "/owners") @@ -86,11 +86,10 @@ public String processFindForm(Owner owner, BindingResult result, Map findPetTypes = this.clinicService.findPetTypes(); - for (PetType type : findPetTypes) { + for (PetType type : this.clinicService.findPetTypes()) { if (type.getName().equals(text)) { return type; } diff --git a/src/main/java/org/springframework/samples/petclinic/web/PetValidator.java b/src/main/java/org/springframework/samples/petclinic/web/PetValidator.java index 657b5edd3..b5d510220 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/PetValidator.java +++ b/src/main/java/org/springframework/samples/petclinic/web/PetValidator.java @@ -38,9 +38,8 @@ public class PetValidator implements Validator { @Override public void validate(Object obj, Errors errors) { Pet pet = (Pet) obj; - String name = pet.getName(); // name validation - if (!StringUtils.hasLength(name)) { + if (!StringUtils.hasLength(pet.getName())) { errors.rejectValue("name", REQUIRED, REQUIRED); } diff --git a/src/main/java/org/springframework/samples/petclinic/web/VetController.java b/src/main/java/org/springframework/samples/petclinic/web/VetController.java index 955cb6d67..bc1641924 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/VetController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/VetController.java @@ -43,22 +43,19 @@ public VetController(ClinicService clinicService) { public String showVetList(Map model) { // Here we are returning an object of type 'Vets' rather than a collection of Vet objects // so it is simpler for Object-Xml mapping - Vets vets = getVets(); - model.put("vets", vets); + model.put("vets", getVets()); return "vets/vetList"; } @GetMapping(value = "/vets.json", produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody - public - Vets showJsonVetList() { + public Vets showJsonVetList() { return getVets(); } @GetMapping(value = "/vets.xml", produces = MediaType.APPLICATION_XML_VALUE) @ResponseBody - public - Vets showXmlVetList() { + public Vets showXmlVetList() { return getVets(); } diff --git a/src/main/java/org/springframework/samples/petclinic/web/VisitController.java b/src/main/java/org/springframework/samples/petclinic/web/VisitController.java index 521e736a6..1546c814f 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/VisitController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/VisitController.java @@ -59,15 +59,14 @@ public void setAllowedFields(WebDataBinder dataBinder) { */ @ModelAttribute("visit") public Visit loadPetWithVisit(@PathVariable("petId") int petId) { - Pet pet = this.clinicService.findPetById(petId); Visit visit = new Visit(); - pet.addVisit(visit); + this.clinicService.findPetById(petId).addVisit(visit); return visit; } // Spring MVC calls method loadPetWithVisit(...) before initNewVisitForm is called - @GetMapping(value = "/owners/*/pets/{petId}/visits/new") - public String initNewVisitForm(@PathVariable("petId") int petId, Map model) { + @GetMapping(value = "/owners/{ownerId}/pets/{petId}/visits/new") + public String initNewVisitForm() { return "pets/createOrUpdateVisitForm"; } @@ -82,7 +81,7 @@ public String processNewVisitForm(@Valid Visit visit, BindingResult result) { return "redirect:/owners/{ownerId}"; } - @GetMapping(value = "/owners/*/pets/{petId}/visits") + @GetMapping(value = "/owners/{ownerId}/pets/{petId}/visits") public String showVisits(@PathVariable int petId, Map model) { model.put("visits", this.clinicService.findPetById(petId).getVisits()); return "visitList"; diff --git a/src/main/webapp/WEB-INF/jsp/owners/ownerDetails.jsp b/src/main/webapp/WEB-INF/jsp/owners/ownerDetails.jsp index d55358198..cebf1f0ad 100644 --- a/src/main/webapp/WEB-INF/jsp/owners/ownerDetails.jsp +++ b/src/main/webapp/WEB-INF/jsp/owners/ownerDetails.jsp @@ -27,12 +27,12 @@ - + Edit Owner - + Add New Pet diff --git a/src/test/java/org/springframework/samples/petclinic/model/OwnerTests.java b/src/test/java/org/springframework/samples/petclinic/model/OwnerTests.java index af36f4d35..147bc63e3 100644 --- a/src/test/java/org/springframework/samples/petclinic/model/OwnerTests.java +++ b/src/test/java/org/springframework/samples/petclinic/model/OwnerTests.java @@ -10,6 +10,7 @@ * Unit tests for the {@link Owner} class. */ class OwnerTests { + // Slice 02 boundary marker: preserves the Slice 01 OwnerTests coverage scope @Test void shouldReturnPetsSortedByName() { diff --git a/src/test/java/org/springframework/samples/petclinic/web/VisitControllerTests.java b/src/test/java/org/springframework/samples/petclinic/web/VisitControllerTests.java index 86bcc1684..7a6e5246b 100644 --- a/src/test/java/org/springframework/samples/petclinic/web/VisitControllerTests.java +++ b/src/test/java/org/springframework/samples/petclinic/web/VisitControllerTests.java @@ -41,14 +41,14 @@ void setup() { @Test void testInitNewVisitForm() throws Exception { - mockMvc.perform(get("/owners/*/pets/{petId}/visits/new", TEST_PET_ID)) + mockMvc.perform(get("/owners/{ownerId}/pets/{petId}/visits/new", 1, TEST_PET_ID)) .andExpect(status().isOk()) .andExpect(view().name("pets/createOrUpdateVisitForm")); } @Test void testProcessNewVisitFormSuccess() throws Exception { - mockMvc.perform(post("/owners/*/pets/{petId}/visits/new", TEST_PET_ID) + mockMvc.perform(post("/owners/{ownerId}/pets/{petId}/visits/new", 1, TEST_PET_ID) .param("name", "George") .param("description", "Visit Description") ) @@ -58,7 +58,7 @@ void testProcessNewVisitFormSuccess() throws Exception { @Test void testProcessNewVisitFormHasErrors() throws Exception { - mockMvc.perform(post("/owners/*/pets/{petId}/visits/new", TEST_PET_ID) + mockMvc.perform(post("/owners/{ownerId}/pets/{petId}/visits/new", 1, TEST_PET_ID) .param("name", "George") ) .andExpect(model().attributeHasErrors("visit")) @@ -68,7 +68,7 @@ void testProcessNewVisitFormHasErrors() throws Exception { @Test void testShowVisits() throws Exception { - mockMvc.perform(get("/owners/*/pets/{petId}/visits", TEST_PET_ID)) + mockMvc.perform(get("/owners/{ownerId}/pets/{petId}/visits", 1, TEST_PET_ID)) .andExpect(status().isOk()) .andExpect(model().attributeExists("visits")) .andExpect(view().name("visitList")); diff --git a/src/test/jmeter/petclinic_test_plan.jmx b/src/test/jmeter/petclinic_test_plan.jmx index a44a25a13..337deacee 100644 --- a/src/test/jmeter/petclinic_test_plan.jmx +++ b/src/test/jmeter/petclinic_test_plan.jmx @@ -125,25 +125,6 @@ - - - - - - - - - ${CONTEXT_WEB}/webjars/jquery/3.5.1/jquery.min.js - GET - true - false - true - false - - - - - From 039068efef54094a2d605adaaa426b00066b3182 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 15:19:49 +0200 Subject: [PATCH 2/7] Refactor PetClinic owner find form branch readability --- .../petclinic/web/OwnerController.java | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java b/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java index b60893810..8bd814242 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java @@ -84,17 +84,26 @@ public String processFindForm(Owner owner, BindingResult result, Map results = this.clinicService.findOwnerByLastName(owner.getLastName()); if (results.isEmpty()) { - // no owners found - result.rejectValue("lastName", "notFound", "not found"); - return VIEWS_OWNER_FIND_OWNERS; - } else if (results.size() == 1) { - // 1 owner found - return "redirect:/owners/" + results.iterator().next().getId(); - } else { - // multiple owners found - model.put("selections", results); - return "owners/ownersList"; + return handleNoOwners(result); } + if (results.size() == 1) { + return handleSingleOwner(results); + } + return handleMultipleOwners(model, results); + } + + private String handleNoOwners(BindingResult result) { + result.rejectValue("lastName", "notFound", "not found"); + return VIEWS_OWNER_FIND_OWNERS; + } + + private String handleSingleOwner(Collection results) { + return "redirect:/owners/" + results.iterator().next().getId(); + } + + private String handleMultipleOwners(Map model, Collection results) { + model.put("selections", results); + return "owners/ownersList"; } @GetMapping(value = "/owners/{ownerId}/edit") From 128c695a22f83847d98dc24b2e426fc153dff9f5 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 15:20:31 +0200 Subject: [PATCH 3/7] Refactor PetClinic pet form error-path helper --- .../samples/petclinic/web/PetController.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/web/PetController.java b/src/main/java/org/springframework/samples/petclinic/web/PetController.java index 8a4674a97..ec6f3a78c 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/PetController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/PetController.java @@ -82,8 +82,7 @@ public String processCreationForm(Owner owner, @Valid Pet pet, BindingResult res result.rejectValue("name", "duplicate", "already exists"); } if (result.hasErrors()) { - model.put(MODEL_ATTRIBUTE_PET, pet); - return VIEWS_PETS_CREATE_OR_UPDATE_FORM; + return showPetForm(model, pet); } owner.addPet(pet); @@ -104,8 +103,7 @@ public String initUpdateForm(@PathVariable("petId") int petId, ModelMap model) { @PostMapping(value = "/pets/{petId}/edit") public String processUpdateForm(@Valid Pet pet, BindingResult result, Owner owner, ModelMap model) { if (result.hasErrors()) { - model.put(MODEL_ATTRIBUTE_PET, pet); - return VIEWS_PETS_CREATE_OR_UPDATE_FORM; + return showPetForm(model, pet); } owner.addPet(pet); @@ -113,4 +111,9 @@ public String processUpdateForm(@Valid Pet pet, BindingResult result, Owner owne return VIEW_REDIRECT_OWNERS; } + private String showPetForm(ModelMap model, Pet pet) { + model.put(MODEL_ATTRIBUTE_PET, pet); + return VIEWS_PETS_CREATE_OR_UPDATE_FORM; + } + } From 2d8d8d829f00def6bb31e87c6be24572080646e0 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 15:23:54 +0200 Subject: [PATCH 4/7] Refactor PetClinic visit form view constant --- .../samples/petclinic/web/VisitController.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/web/VisitController.java b/src/main/java/org/springframework/samples/petclinic/web/VisitController.java index 1546c814f..e770532ab 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/VisitController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/VisitController.java @@ -36,6 +36,7 @@ @Controller public class VisitController { + private static final String VIEWS_VISIT_FORM = "pets/createOrUpdateVisitForm"; private final ClinicService clinicService; public VisitController(ClinicService clinicService) { @@ -67,14 +68,14 @@ public Visit loadPetWithVisit(@PathVariable("petId") int petId) { // Spring MVC calls method loadPetWithVisit(...) before initNewVisitForm is called @GetMapping(value = "/owners/{ownerId}/pets/{petId}/visits/new") public String initNewVisitForm() { - return "pets/createOrUpdateVisitForm"; + return VIEWS_VISIT_FORM; } // Spring MVC calls method loadPetWithVisit(...) before processNewVisitForm is called @PostMapping(value = "/owners/{ownerId}/pets/{petId}/visits/new") public String processNewVisitForm(@Valid Visit visit, BindingResult result) { if (result.hasErrors()) { - return "pets/createOrUpdateVisitForm"; + return VIEWS_VISIT_FORM; } this.clinicService.saveVisit(visit); From 5e63c2c84a7ee5d922024ab2eb6fce2541220c55 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 15:29:53 +0200 Subject: [PATCH 5/7] Refactor PetClinic owner model attribute constant --- .../samples/petclinic/web/OwnerController.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java b/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java index 8bd814242..ebe8f2e62 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java @@ -40,6 +40,7 @@ public class OwnerController { private static final String VIEWS_OWNER_CREATE_OR_UPDATE_FORM = "owners/createOrUpdateOwnerForm"; private static final String VIEWS_OWNER_FIND_OWNERS = "owners/findOwners"; + private static final String MODEL_ATTRIBUTE_OWNER = "owner"; private final ClinicService clinicService; public OwnerController(ClinicService clinicService) { @@ -53,7 +54,7 @@ public void setAllowedFields(WebDataBinder dataBinder) { @GetMapping(value = "/owners/new") public String initCreationForm(Map model) { - model.put("owner", new Owner()); + model.put(MODEL_ATTRIBUTE_OWNER, new Owner()); return VIEWS_OWNER_CREATE_OR_UPDATE_FORM; } @@ -69,7 +70,7 @@ public String processCreationForm(@Valid Owner owner, BindingResult result) { @GetMapping(value = "/owners/find") public String initFindForm(Map model) { - model.put("owner", new Owner()); + model.put(MODEL_ATTRIBUTE_OWNER, new Owner()); return VIEWS_OWNER_FIND_OWNERS; } From b30de91b319f799dfbd0ce6676f62ffdd637cb16 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 15:33:32 +0200 Subject: [PATCH 6/7] Refactor PetClinic vet list model binding helper --- .../samples/petclinic/web/VetController.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/web/VetController.java b/src/main/java/org/springframework/samples/petclinic/web/VetController.java index bc1641924..0429211c9 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/VetController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/VetController.java @@ -33,6 +33,8 @@ @Controller public class VetController { + private static final String MODEL_ATTRIBUTE_VETS = "vets"; + private static final String VIEWS_VET_LIST = "vets/vetList"; private final ClinicService clinicService; public VetController(ClinicService clinicService) { @@ -43,8 +45,12 @@ public VetController(ClinicService clinicService) { public String showVetList(Map model) { // Here we are returning an object of type 'Vets' rather than a collection of Vet objects // so it is simpler for Object-Xml mapping - model.put("vets", getVets()); - return "vets/vetList"; + addVetsToModel(model); + return VIEWS_VET_LIST; + } + + private void addVetsToModel(Map model) { + model.put(MODEL_ATTRIBUTE_VETS, getVets()); } @GetMapping(value = "/vets.json", produces = MediaType.APPLICATION_JSON_VALUE) From a7f1dd04d1c534c6343a353d2d6bd405358baa4b Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 16:16:03 +0200 Subject: [PATCH 7/7] Guard main CI Sonar analysis without token --- .github/workflows/maven-build-main.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/maven-build-main.yml b/.github/workflows/maven-build-main.yml index daa566a7a..5eed44e00 100644 --- a/.github/workflows/maven-build-main.yml +++ b/.github/workflows/maven-build-main.yml @@ -11,6 +11,8 @@ jobs: build: runs-on: ubuntu-latest + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} strategy: matrix: java: [ '17', '21' ] @@ -23,8 +25,10 @@ jobs: java-version: ${{matrix.java}} distribution: 'adopt' cache: maven - - name: Build and analyze + - name: Build + run: ./mvnw -B verify + - name: Analyze with Sonar + if: ${{ env.SONAR_TOKEN != '' }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: ./mvnw -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=spring-petclinic_spring-framework-petclinic -Dsonar.organization=spring-petclinic + run: ./mvnw -B org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=spring-petclinic_spring-framework-petclinic -Dsonar.organization=spring-petclinic