From 3e076c95d59f7908924886321feeffc976622f7c Mon Sep 17 00:00:00 2001 From: Mikel Garcia <122596907+mgarciaLKS@users.noreply.github.com> Date: Thu, 27 Mar 2025 16:52:48 +0100 Subject: [PATCH 01/10] Update pom.xml Signed-off-by: Mikel Garcia <122596907+mgarciaLKS@users.noreply.github.com> --- pom.xml | 69 ++++++++++++++++++++++++++------------------------------- 1 file changed, 31 insertions(+), 38 deletions(-) diff --git a/pom.xml b/pom.xml index 28e3f119c..2f8bedadd 100644 --- a/pom.xml +++ b/pom.xml @@ -250,45 +250,7 @@ - - org.jacoco - jacoco-maven-plugin - ${jacoco.version} - - - - prepare-agent - - prepare-agent - - - - - report - verify - - report - - - UTF-8 - - - - - - report-aggregate - verify - - report-aggregate - - - ${project.build.directory}/site/jacoco - - - - - @@ -477,5 +439,36 @@ + + coverage + + + + org.jacoco + jacoco-maven-plugin + 0.8.7 + + + prepare-agent + + prepare-agent + + + + report + + report + + + + XML + + + + + + + + From 83603cfb3d7412a5e6411b77e51f0213c8e9a527 Mon Sep 17 00:00:00 2001 From: AulaEmpresaLKS <129507941+AulaEmpresaLKS@users.noreply.github.com> Date: Thu, 27 Mar 2025 16:54:07 +0100 Subject: [PATCH 02/10] Update Jenkinsfile Signed-off-by: AulaEmpresaLKS <129507941+AulaEmpresaLKS@users.noreply.github.com> --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 76fed6f0d..5db60edc7 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -17,7 +17,7 @@ pipeline { -v ./:/app \ -v "/home/jenkins/.m2":"/home/jenkins/.m2" \ -e JOB_ACTION="compile" \ - -e MAVEN_CMD="clean verify sonar:sonar -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.token=$SONAR_TOKEN -Dsonar.branch.name=$SONAR_BRANCH" \ + -e MAVEN_CMD="clean verify sonar:sonar -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.token=$SONAR_TOKEN -Dsonar.branch.name=$SONAR_BRANCH" -Pcoverage \ $BUILD_IMAGE ''' } From 8eff6981f1b9a68f454e666777524b6cd07cf67a Mon Sep 17 00:00:00 2001 From: AulaEmpresaLKS <129507941+AulaEmpresaLKS@users.noreply.github.com> Date: Thu, 27 Mar 2025 16:56:46 +0100 Subject: [PATCH 03/10] Update Jenkinsfile Signed-off-by: AulaEmpresaLKS <129507941+AulaEmpresaLKS@users.noreply.github.com> --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 5db60edc7..1e8305982 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -17,7 +17,7 @@ pipeline { -v ./:/app \ -v "/home/jenkins/.m2":"/home/jenkins/.m2" \ -e JOB_ACTION="compile" \ - -e MAVEN_CMD="clean verify sonar:sonar -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.token=$SONAR_TOKEN -Dsonar.branch.name=$SONAR_BRANCH" -Pcoverage \ + -e MAVEN_CMD="clean verify sonar:sonar -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.token=$SONAR_TOKEN -Dsonar.branch.name=$SONAR_BRANCH -Pcoverage" \ $BUILD_IMAGE ''' } From b80781017874e5bc30c25476d2cafc2f4f2a0b93 Mon Sep 17 00:00:00 2001 From: AulaEmpresaLKS <129507941+AulaEmpresaLKS@users.noreply.github.com> Date: Mon, 31 Mar 2025 11:39:00 +0200 Subject: [PATCH 04/10] Update Owner.java Signed-off-by: AulaEmpresaLKS <129507941+AulaEmpresaLKS@users.noreply.github.com> --- .../springframework/samples/petclinic/owner/Owner.java | 8 ++++++++ 1 file changed, 8 insertions(+) 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 675b2140e..2b22ad3f2 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/Owner.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/Owner.java @@ -170,6 +170,14 @@ public class Owner extends Person { Assert.notNull(pet, "Invalid Pet identifier!"); pet.addVisit(visit); + + (req: Request, res: Response, next: NextFunction) => { + verifyPreLoginChallenges(req) // vuln-code-snippet hide-line + models.sequelize.query('SELECT * FROM Users WHERE email = :email AND password = :password AND deletedAt IS NULL', { + replacements: { email: req.body.email || '', password: security.hash(req.body.password || '') }, + model: UserModel, + plain: true + }) } } From 6bd4cdbbe86011cab35dca94be03310bd6919c4f Mon Sep 17 00:00:00 2001 From: AulaEmpresaLKS <129507941+AulaEmpresaLKS@users.noreply.github.com> Date: Mon, 31 Mar 2025 11:45:30 +0200 Subject: [PATCH 05/10] Update Owner.java Signed-off-by: AulaEmpresaLKS <129507941+AulaEmpresaLKS@users.noreply.github.com> --- .../springframework/samples/petclinic/owner/Owner.java | 8 -------- 1 file changed, 8 deletions(-) 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 2b22ad3f2..675b2140e 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/Owner.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/Owner.java @@ -170,14 +170,6 @@ public class Owner extends Person { Assert.notNull(pet, "Invalid Pet identifier!"); pet.addVisit(visit); - - (req: Request, res: Response, next: NextFunction) => { - verifyPreLoginChallenges(req) // vuln-code-snippet hide-line - models.sequelize.query('SELECT * FROM Users WHERE email = :email AND password = :password AND deletedAt IS NULL', { - replacements: { email: req.body.email || '', password: security.hash(req.body.password || '') }, - model: UserModel, - plain: true - }) } } From 34b1daa65f8167d7b8c362d8cb9519dcd5c3892c Mon Sep 17 00:00:00 2001 From: AulaEmpresaLKS <129507941+AulaEmpresaLKS@users.noreply.github.com> Date: Mon, 31 Mar 2025 11:49:48 +0200 Subject: [PATCH 06/10] Update Owner.java Signed-off-by: AulaEmpresaLKS <129507941+AulaEmpresaLKS@users.noreply.github.com> --- .../samples/petclinic/owner/Owner.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) 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 675b2140e..d02c9b97c 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/Owner.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/Owner.java @@ -172,4 +172,18 @@ public class Owner extends Person { pet.addVisit(visit); } + public void forcedIssue() { + String vulnerableCode = "(req: Request, res: Response, next: NextFunction) => {\n" + + " verifyPreLoginChallenges(req) // vuln-code-snippet hide-line\n" + + " models.sequelize.query('SELECT * FROM Users WHERE email = :email AND password = :password AND deletedAt IS NULL', {\n" + + " replacements: { email: req.body.email || '', password: security.hash(req.body.password || '') },\n" + + " model: UserModel,\n" + + " plain: true\n" + + " })\n" + + "}"; + System.out.println(vulnerableCode); + } +} + + } From c9712a309ba7ec18afee1073d9579c84fcb48853 Mon Sep 17 00:00:00 2001 From: AulaEmpresaLKS <129507941+AulaEmpresaLKS@users.noreply.github.com> Date: Mon, 31 Mar 2025 11:50:57 +0200 Subject: [PATCH 07/10] Update Owner.java Signed-off-by: AulaEmpresaLKS <129507941+AulaEmpresaLKS@users.noreply.github.com> --- .../org/springframework/samples/petclinic/owner/Owner.java | 3 --- 1 file changed, 3 deletions(-) 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 d02c9b97c..90ae31d07 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/Owner.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/Owner.java @@ -184,6 +184,3 @@ public class Owner extends Person { System.out.println(vulnerableCode); } } - - -} From e90e323130ff7bdf2074dd1a16082ade5aecf6da Mon Sep 17 00:00:00 2001 From: AulaEmpresaLKS <129507941+AulaEmpresaLKS@users.noreply.github.com> Date: Mon, 31 Mar 2025 11:54:19 +0200 Subject: [PATCH 08/10] Update Owner.java Signed-off-by: AulaEmpresaLKS <129507941+AulaEmpresaLKS@users.noreply.github.com> --- .../samples/petclinic/owner/Owner.java | 252 ++++++++---------- 1 file changed, 111 insertions(+), 141 deletions(-) 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 90ae31d07..65d422a0e 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/Owner.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/Owner.java @@ -1,18 +1,3 @@ -/* - * Copyright 2012-2019 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.ArrayList; @@ -36,151 +21,136 @@ import jakarta.validation.constraints.NotBlank; /** * Simple JavaBean domain object representing an owner. * - * @author Ken Krebs - * @author Juergen Hoeller - * @author Sam Brannen - * @author Michael Isvy - * @author Oliver Drotbohm - * @author Wick Dynex + * (Autores omitidos para mayor claridad) */ @Entity @Table(name = "owners") public class Owner extends Person { - @Column(name = "address") - @NotBlank - private String address; + @Column(name = "address") + @NotBlank + private String address; - @Column(name = "city") - @NotBlank - private String city; + @Column(name = "city") + @NotBlank + private String city; - @Column(name = "telephone") - @NotBlank - @Pattern(regexp = "\\d{10}", message = "Telephone must be a 10-digit number") - private String telephone; + @Column(name = "telephone") + @NotBlank + @Pattern(regexp = "\\d{10}", message = "Telephone must be a 10-digit number") + private String telephone; - @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) - @JoinColumn(name = "owner_id") - @OrderBy("name") - private final List pets = new ArrayList<>(); + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) + @JoinColumn(name = "owner_id") + @OrderBy("name") + private final List pets = new ArrayList<>(); - public String getAddress() { - return this.address; - } + public String getAddress() { + return this.address; + } - public void setAddress(String address) { - this.address = address; - } + public void setAddress(String address) { + this.address = address; + } - public String getCity() { - return this.city; - } + public String getCity() { + return this.city; + } - public void setCity(String city) { - this.city = city; - } + public void setCity(String city) { + this.city = city; + } - public String getTelephone() { - return this.telephone; - } + public String getTelephone() { + return this.telephone; + } - public void setTelephone(String telephone) { - this.telephone = telephone; - } + public void setTelephone(String telephone) { + this.telephone = telephone; + } - public List getPets() { - return this.pets; - } + public List getPets() { + return this.pets; + } - public void addPet(Pet pet) { - if (pet.isNew()) { - getPets().add(pet); - } - } + public void addPet(Pet pet) { + if (pet.isNew()) { + getPets().add(pet); + } + } - /** - * Return the Pet with the given name, or null if none found for this Owner. - * @param name to test - * @return the Pet with the given name, or null if no such Pet exists for this Owner - */ - public Pet getPet(String name) { - return getPet(name, false); - } + /** + * Return the Pet with the given name, or null if none found for this Owner. + * + * @param name to test + * @return the Pet with the given name, or null if no such Pet exists for this Owner + */ + public Pet getPet(String name) { + return getPet(name, false); + } - /** - * Return the Pet with the given id, or null if none found for this Owner. - * @param id to test - * @return the Pet with the given id, or null if no such Pet exists for this Owner - */ - public Pet getPet(Integer id) { - for (Pet pet : getPets()) { - if (!pet.isNew()) { - Integer compId = pet.getId(); - if (compId.equals(id)) { - return pet; - } - } - } - return null; - } + /** + * Return the Pet with the given id, or null if none found for this Owner. + * + * @param id to test + * @return the Pet with the given id, or null if no such Pet exists for this Owner + */ + public Pet getPet(Integer id) { + for (Pet pet : getPets()) { + if (!pet.isNew()) { + Integer compId = pet.getId(); + if (compId.equals(id)) { + return pet; + } + } + } + return null; + } - /** - * Return the Pet with the given name, or null if none found for this Owner. - * @param name to test - * @param ignoreNew whether to ignore new pets (pets that are not saved yet) - * @return the Pet with the given name, or null if no such Pet exists for this Owner - */ - public Pet getPet(String name, boolean ignoreNew) { - for (Pet pet : getPets()) { - String compName = pet.getName(); - if (compName != null && compName.equalsIgnoreCase(name)) { - if (!ignoreNew || !pet.isNew()) { - return pet; - } - } - } - return null; - } + /** + * Return the Pet with the given name, or null if none found for this Owner. + * + * @param name to test + * @param ignoreNew whether to ignore new pets (pets that are not saved yet) + * @return the Pet with the given name, or null if no such Pet exists for this Owner + */ + public Pet getPet(String name, boolean ignoreNew) { + for (Pet pet : getPets()) { + String compName = pet.getName(); + if (compName != null && compName.equalsIgnoreCase(name)) { + if (!ignoreNew || !pet.isNew()) { + return pet; + } + } + } + return null; + } - @Override - public String toString() { - return new ToStringCreator(this).append("id", this.getId()) - .append("new", this.isNew()) - .append("lastName", this.getLastName()) - .append("firstName", this.getFirstName()) - .append("address", this.address) - .append("city", this.city) - .append("telephone", this.telephone) - .toString(); - } + @Override + public String toString() { + return new ToStringCreator(this) + .append("id", this.getId()) + .append("new", this.isNew()) + .append("lastName", this.getLastName()) + .append("firstName", this.getFirstName()) + .append("address", this.address) + .append("city", this.city) + .append("telephone", this.telephone) + .toString(); + } - /** - * Adds the given {@link Visit} to the {@link Pet} with the given identifier. - * @param petId the identifier of the {@link Pet}, must not be {@literal null}. - * @param visit the visit to add, must not be {@literal null}. - */ - public void addVisit(Integer petId, Visit visit) { - - Assert.notNull(petId, "Pet identifier must not be null!"); - Assert.notNull(visit, "Visit must not be null!"); - - Pet pet = getPet(petId); - - Assert.notNull(pet, "Invalid Pet identifier!"); - - pet.addVisit(visit); - } - - public void forcedIssue() { - String vulnerableCode = "(req: Request, res: Response, next: NextFunction) => {\n" + - " verifyPreLoginChallenges(req) // vuln-code-snippet hide-line\n" + - " models.sequelize.query('SELECT * FROM Users WHERE email = :email AND password = :password AND deletedAt IS NULL', {\n" + - " replacements: { email: req.body.email || '', password: security.hash(req.body.password || '') },\n" + - " model: UserModel,\n" + - " plain: true\n" + - " })\n" + - "}"; - System.out.println(vulnerableCode); - } + /** + * Método dummy para forzar que SonarQube detecte la siguiente ISSUE: + * "Change this code to not construct SQL queries directly from user-controlled data". + * + * NOTA: Este método NO se utiliza en la lógica del negocio y solo está presente + * para que el análisis estático detecte el patrón vulnerable. + * + * @param userInput entrada controlada por el usuario + * @return Consulta SQL construida de forma insegura + */ + public String buildVulnerableQuery(String userInput) { + String vulnerableQuery = "SELECT * FROM Users WHERE email = '" + userInput + "'"; + return vulnerableQuery; + } } From 473c3a694e83f49bed8692745d1d333ee409f6b3 Mon Sep 17 00:00:00 2001 From: AulaEmpresaLKS <129507941+AulaEmpresaLKS@users.noreply.github.com> Date: Mon, 31 Mar 2025 11:55:48 +0200 Subject: [PATCH 09/10] Update Owner.java Signed-off-by: AulaEmpresaLKS <129507941+AulaEmpresaLKS@users.noreply.github.com> --- .../samples/petclinic/owner/Owner.java | 15 --------------- 1 file changed, 15 deletions(-) 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 65d422a0e..a0e6d3c5b 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/Owner.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/Owner.java @@ -138,19 +138,4 @@ public class Owner extends Person { .append("telephone", this.telephone) .toString(); } - - /** - * Método dummy para forzar que SonarQube detecte la siguiente ISSUE: - * "Change this code to not construct SQL queries directly from user-controlled data". - * - * NOTA: Este método NO se utiliza en la lógica del negocio y solo está presente - * para que el análisis estático detecte el patrón vulnerable. - * - * @param userInput entrada controlada por el usuario - * @return Consulta SQL construida de forma insegura - */ - public String buildVulnerableQuery(String userInput) { - String vulnerableQuery = "SELECT * FROM Users WHERE email = '" + userInput + "'"; - return vulnerableQuery; - } } From 56b3b3148efe7c82a7f4ee02aa46f5f6d8775218 Mon Sep 17 00:00:00 2001 From: AulaEmpresaLKS <129507941+AulaEmpresaLKS@users.noreply.github.com> Date: Mon, 31 Mar 2025 11:56:29 +0200 Subject: [PATCH 10/10] Update Owner.java Signed-off-by: AulaEmpresaLKS <129507941+AulaEmpresaLKS@users.noreply.github.com> --- .../samples/petclinic/owner/Owner.java | 228 ++++++++++-------- 1 file changed, 131 insertions(+), 97 deletions(-) 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 a0e6d3c5b..675b2140e 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/Owner.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/Owner.java @@ -1,3 +1,18 @@ +/* + * Copyright 2012-2019 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.ArrayList; @@ -21,121 +36,140 @@ import jakarta.validation.constraints.NotBlank; /** * Simple JavaBean domain object representing an owner. * - * (Autores omitidos para mayor claridad) + * @author Ken Krebs + * @author Juergen Hoeller + * @author Sam Brannen + * @author Michael Isvy + * @author Oliver Drotbohm + * @author Wick Dynex */ @Entity @Table(name = "owners") public class Owner extends Person { - @Column(name = "address") - @NotBlank - private String address; + @Column(name = "address") + @NotBlank + private String address; - @Column(name = "city") - @NotBlank - private String city; + @Column(name = "city") + @NotBlank + private String city; - @Column(name = "telephone") - @NotBlank - @Pattern(regexp = "\\d{10}", message = "Telephone must be a 10-digit number") - private String telephone; + @Column(name = "telephone") + @NotBlank + @Pattern(regexp = "\\d{10}", message = "Telephone must be a 10-digit number") + private String telephone; - @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) - @JoinColumn(name = "owner_id") - @OrderBy("name") - private final List pets = new ArrayList<>(); + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) + @JoinColumn(name = "owner_id") + @OrderBy("name") + private final List pets = new ArrayList<>(); - public String getAddress() { - return this.address; - } + public String getAddress() { + return this.address; + } - public void setAddress(String address) { - this.address = address; - } + public void setAddress(String address) { + this.address = address; + } - public String getCity() { - return this.city; - } + public String getCity() { + return this.city; + } - public void setCity(String city) { - this.city = city; - } + public void setCity(String city) { + this.city = city; + } - public String getTelephone() { - return this.telephone; - } + public String getTelephone() { + return this.telephone; + } - public void setTelephone(String telephone) { - this.telephone = telephone; - } + public void setTelephone(String telephone) { + this.telephone = telephone; + } - public List getPets() { - return this.pets; - } + public List getPets() { + return this.pets; + } - public void addPet(Pet pet) { - if (pet.isNew()) { - getPets().add(pet); - } - } + public void addPet(Pet pet) { + if (pet.isNew()) { + getPets().add(pet); + } + } - /** - * Return the Pet with the given name, or null if none found for this Owner. - * - * @param name to test - * @return the Pet with the given name, or null if no such Pet exists for this Owner - */ - public Pet getPet(String name) { - return getPet(name, false); - } + /** + * Return the Pet with the given name, or null if none found for this Owner. + * @param name to test + * @return the Pet with the given name, or null if no such Pet exists for this Owner + */ + public Pet getPet(String name) { + return getPet(name, false); + } - /** - * Return the Pet with the given id, or null if none found for this Owner. - * - * @param id to test - * @return the Pet with the given id, or null if no such Pet exists for this Owner - */ - public Pet getPet(Integer id) { - for (Pet pet : getPets()) { - if (!pet.isNew()) { - Integer compId = pet.getId(); - if (compId.equals(id)) { - return pet; - } - } - } - return null; - } + /** + * Return the Pet with the given id, or null if none found for this Owner. + * @param id to test + * @return the Pet with the given id, or null if no such Pet exists for this Owner + */ + public Pet getPet(Integer id) { + for (Pet pet : getPets()) { + if (!pet.isNew()) { + Integer compId = pet.getId(); + if (compId.equals(id)) { + return pet; + } + } + } + return null; + } - /** - * Return the Pet with the given name, or null if none found for this Owner. - * - * @param name to test - * @param ignoreNew whether to ignore new pets (pets that are not saved yet) - * @return the Pet with the given name, or null if no such Pet exists for this Owner - */ - public Pet getPet(String name, boolean ignoreNew) { - for (Pet pet : getPets()) { - String compName = pet.getName(); - if (compName != null && compName.equalsIgnoreCase(name)) { - if (!ignoreNew || !pet.isNew()) { - return pet; - } - } - } - return null; - } + /** + * Return the Pet with the given name, or null if none found for this Owner. + * @param name to test + * @param ignoreNew whether to ignore new pets (pets that are not saved yet) + * @return the Pet with the given name, or null if no such Pet exists for this Owner + */ + public Pet getPet(String name, boolean ignoreNew) { + for (Pet pet : getPets()) { + String compName = pet.getName(); + if (compName != null && compName.equalsIgnoreCase(name)) { + if (!ignoreNew || !pet.isNew()) { + return pet; + } + } + } + return null; + } + + @Override + public String toString() { + return new ToStringCreator(this).append("id", this.getId()) + .append("new", this.isNew()) + .append("lastName", this.getLastName()) + .append("firstName", this.getFirstName()) + .append("address", this.address) + .append("city", this.city) + .append("telephone", this.telephone) + .toString(); + } + + /** + * Adds the given {@link Visit} to the {@link Pet} with the given identifier. + * @param petId the identifier of the {@link Pet}, must not be {@literal null}. + * @param visit the visit to add, must not be {@literal null}. + */ + public void addVisit(Integer petId, Visit visit) { + + Assert.notNull(petId, "Pet identifier must not be null!"); + Assert.notNull(visit, "Visit must not be null!"); + + Pet pet = getPet(petId); + + Assert.notNull(pet, "Invalid Pet identifier!"); + + pet.addVisit(visit); + } - @Override - public String toString() { - return new ToStringCreator(this) - .append("id", this.getId()) - .append("new", this.isNew()) - .append("lastName", this.getLastName()) - .append("firstName", this.getFirstName()) - .append("address", this.address) - .append("city", this.city) - .append("telephone", this.telephone) - .toString(); - } }