diff --git a/build.gradle b/build.gradle
index c19b5f77d..8c1f8f79b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -26,6 +26,11 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'javax.cache:cache-api'
implementation 'jakarta.xml.bind:jakarta.xml.bind-api'
+ implementation('io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations:1.28.0')
+ implementation('io.opentelemetry:opentelemetry-api')
+ implementation 'com.squareup.okhttp3:okhttp'
+ implementation 'org.json:json:20171018'
+
runtimeOnly 'org.springframework.boot:spring-boot-starter-actuator'
runtimeOnly "org.webjars.npm:bootstrap:${webjarsBootstrapVersion}"
runtimeOnly "org.webjars.npm:font-awesome:${webjarsFontawesomeVersion}"
@@ -37,6 +42,17 @@ dependencies {
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
+tasks.named("bootRun") {
+ if (project.hasProperty('digma')) {
+ def tempDir = System.getProperty("java.io.tmpdir")
+ environment["JAVA_TOOL_OPTIONS"] = "-javaagent:${tempDir}/temp-digma-otel-jars/opentelemetry-javaagent.jar"
+ systemProperty 'otel.exporter.otlp.traces.endpoint', 'http://localhost:5050'
+ systemProperty 'otel.traces.exporter', 'otlp'
+ systemProperty 'otel.metrics.exporter', 'none'
+ systemProperty 'otel.service.name', "${project.name}"
+ systemProperty 'otel.javaagent.extensions', "${tempDir}/temp-digma-otel-jars/digma-otel-agent-extension.jar"
+ }
+}
tasks.named('test') {
useJUnitPlatform()
}
diff --git a/digma-profile.gradle b/digma-profile.gradle
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/digma-profile.gradle
@@ -0,0 +1 @@
+
diff --git a/opentelemetry-agent.jar b/opentelemetry-agent.jar
new file mode 100644
index 000000000..de24faaee
Binary files /dev/null and b/opentelemetry-agent.jar differ
diff --git a/otel/digma-otel-agent-extension.jar b/otel/digma-otel-agent-extension.jar
new file mode 100644
index 000000000..a4a9ba051
Binary files /dev/null and b/otel/digma-otel-agent-extension.jar differ
diff --git a/otel/opentelemetry-javaagent.jar b/otel/opentelemetry-javaagent.jar
new file mode 100644
index 000000000..de24faaee
Binary files /dev/null and b/otel/opentelemetry-javaagent.jar differ
diff --git a/pom.xml b/pom.xml
index b45f3a66c..68eb84d5e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,6 +5,8 @@
spring-petclinic
3.1.0-SNAPSHOT
+
+
org.springframework.boot
spring-boot-starter-parent
@@ -67,6 +69,27 @@
test
+
+ org.springframework.boot
+ spring-boot-testcontainers
+ test
+
+
+ org.testcontainers
+ junit-jupiter
+ test
+
+
+ org.testcontainers
+ postgresql
+ test
+
+
+ io.rest-assured
+ rest-assured
+ test
+
+
com.h2database
@@ -120,7 +143,12 @@
spring-boot-devtools
true
-
+
+ com.github.javafaker
+ javafaker
+ 1.0.2
+ test
+
jakarta.xml.bind
jakarta.xml.bind-api
@@ -135,9 +163,20 @@
android-json
0.0.20131108.vaadin1
-
+
+
+
+ org.testcontainers
+ testcontainers-bom
+ ${testcontainers.version}
+ pom
+ import
+
+
+
+
@@ -183,28 +222,8 @@
checkstyle
${checkstyle.version}
-
- io.spring.nohttp
- nohttp-checkstyle
- ${nohttp-checkstyle.version}
-
+
-
-
- nohttp-checkstyle-validation
- validate
-
- src/checkstyle/nohttp-checkstyle.xml
- src/checkstyle/nohttp-checkstyle-suppressions.xml
- ${basedir}
- **/*
- **/.git/**/*,**/.idea/**/*,**/target/**/,**/.flattened-pom.xml,**/*.class
-
-
- check
-
-
-
org.graalvm.buildtools
@@ -311,6 +330,27 @@
+
+ digma
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+ -javaagent:${env.TMPDIR}/temp-digma-otel-jars/opentelemetry-javaagent.jar
+
+ http://localhost:5050
+ otlp
+ none
+ ${pom.artifactId}
+ ${env.TMPDIR}/temp-digma-otel-jars/digma-otel-agent-extension.jar
+
+
+
+
+
+
css
diff --git a/src/main/java/org/springframework/samples/petclinic/PetClinicRuntimeHints.java b/src/main/java/org/springframework/samples/petclinic/PetClinicRuntimeHints.java
index 2ac7e02e9..ad21a5984 100644
--- a/src/main/java/org/springframework/samples/petclinic/PetClinicRuntimeHints.java
+++ b/src/main/java/org/springframework/samples/petclinic/PetClinicRuntimeHints.java
@@ -16,8 +16,13 @@
package org.springframework.samples.petclinic;
+import jakarta.servlet.ServletContext;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
public class PetClinicRuntimeHints implements RuntimeHintsRegistrar {
diff --git a/src/main/java/org/springframework/samples/petclinic/adapters/PetVaccinationService.java b/src/main/java/org/springframework/samples/petclinic/adapters/PetVaccinationService.java
index 737bb3616..5aa3c9b2d 100644
--- a/src/main/java/org/springframework/samples/petclinic/adapters/PetVaccinationService.java
+++ b/src/main/java/org/springframework/samples/petclinic/adapters/PetVaccinationService.java
@@ -6,9 +6,11 @@ import org.json.JSONException;
import java.io.IOException;
public interface PetVaccinationService {
+
@WithSpan
VaccinnationRecord[] AllVaccines() throws JSONException, IOException;
@WithSpan
VaccinnationRecord VaccineRecord(int vaccinationRecordId) throws JSONException, IOException;
+
}
diff --git a/src/main/java/org/springframework/samples/petclinic/adapters/PetVaccinationServiceFacade.java b/src/main/java/org/springframework/samples/petclinic/adapters/PetVaccinationServiceFacade.java
index b1b1737ab..b65176313 100644
--- a/src/main/java/org/springframework/samples/petclinic/adapters/PetVaccinationServiceFacade.java
+++ b/src/main/java/org/springframework/samples/petclinic/adapters/PetVaccinationServiceFacade.java
@@ -20,7 +20,7 @@ public class PetVaccinationServiceFacade implements PetVaccinationService {
public static final String VACCINES_RECORDS_URL = "https://647f4bb4c246f166da9084c7.mockapi.io/api/vetcheck/vaccines";
- private String MakeHttpCall(String url) throws IOException{
+ private String MakeHttpCall(String url) throws IOException {
Request getAllVaccinesRequest = new Request.Builder().url(url).build();
OkHttpClient client = new OkHttpClient();
@@ -34,8 +34,7 @@ public class PetVaccinationServiceFacade implements PetVaccinationService {
var vaccineListString = MakeHttpCall(VACCINES_RECORDS_URL);
JSONArray jArr = new JSONArray(vaccineListString);
- var vaccinnationRecords =
- new ArrayList();
+ var vaccinnationRecords = new ArrayList();
for (int i = 0; i < jArr.length(); i++) {
@@ -69,6 +68,4 @@ public class PetVaccinationServiceFacade implements PetVaccinationService {
return new VaccinnationRecord(id, petId, vaccineDate);
}
-
-
}
diff --git a/src/main/java/org/springframework/samples/petclinic/domain/OwnerValidation.java b/src/main/java/org/springframework/samples/petclinic/domain/OwnerValidation.java
index e11a19867..138760090 100644
--- a/src/main/java/org/springframework/samples/petclinic/domain/OwnerValidation.java
+++ b/src/main/java/org/springframework/samples/petclinic/domain/OwnerValidation.java
@@ -52,7 +52,7 @@ public class OwnerValidation {
}
- @WithSpan
+
// This function and classes were generated by ChatGPT
public boolean ValidateUserAccess(String usr, String pswd, String sysCode) {
@@ -62,7 +62,6 @@ public class OwnerValidation {
return false;
}
-
boolean vldPswd = pwdUtils.vldtPswd(usr, pswd);
if (!vldPswd) {
return false;
diff --git a/src/main/java/org/springframework/samples/petclinic/domain/PetVaccinationStatusService.java b/src/main/java/org/springframework/samples/petclinic/domain/PetVaccinationStatusService.java
index ca8b34f4b..12cbe425b 100644
--- a/src/main/java/org/springframework/samples/petclinic/domain/PetVaccinationStatusService.java
+++ b/src/main/java/org/springframework/samples/petclinic/domain/PetVaccinationStatusService.java
@@ -1,6 +1,7 @@
package org.springframework.samples.petclinic.domain;
import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.instrumentation.annotations.WithSpan;
import org.json.JSONException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.samples.petclinic.adapters.PetVaccinationService;
@@ -20,15 +21,16 @@ public class PetVaccinationStatusService {
@Autowired
private PetVaccinationService adapter;
- public void UpdateVaccinationStatus(Pet[] pets){
+ @WithSpan
+ public void UpdateVaccinationStatus(Pet[] pets) {
- for (Pet pet: pets){
+ for (Pet pet : pets) {
try {
var vaccinationRecords = this.adapter.AllVaccines();
- for (VaccinnationRecord record : vaccinationRecords){
+ for (VaccinnationRecord record : vaccinationRecords) {
var recordInfo = this.adapter.VaccineRecord(record.recordId());
- if (recordInfo.petId()==pet.getId()){
+ if (recordInfo.petId() == pet.getId()) {
var date = LocalDateTime.ofInstant(recordInfo.vaccineDate(), ZoneId.systemDefault());
PetVaccine petVaccine = new PetVaccine();
petVaccine.setDate(date.toLocalDate());
@@ -36,13 +38,14 @@ public class PetVaccinationStatusService {
}
}
- } catch (JSONException |IOException e) {
- //Fail silently
+ }
+ catch (JSONException | IOException e) {
+ // Fail silently
Span.current().recordException(e);
}
}
-
}
+
}
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 f77ce4d28..c9684c751 100644
--- a/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java
+++ b/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java
@@ -129,11 +129,6 @@ class OwnerController {
return addPaginationModel(page, model, ownersResults);
}
-
-
-
-
-
private String addPaginationModel(int page, Model model, Page paginated) {
model.addAttribute("listOwners", paginated);
List listOwners = paginated.getContent();
@@ -151,11 +146,6 @@ class OwnerController {
return owners.findByLastName(lastname, pageable);
}
-
-
-
-
-
@GetMapping("/owners/{ownerId}/edit")
public String initUpdateOwnerForm(@PathVariable("ownerId") int ownerId, Model model) {
Owner owner = this.owners.findById(ownerId);
@@ -163,24 +153,6 @@ class OwnerController {
return VIEWS_OWNER_CREATE_OR_UPDATE_FORM;
}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@PostMapping("/owners/{ownerId}/edit")
public String processUpdateOwnerForm(@Valid Owner owner, BindingResult result,
@PathVariable("ownerId") int ownerId) {
@@ -199,10 +171,8 @@ class OwnerController {
* @return a ModelMap with the model attributes for the view
*/
@GetMapping("/owners/{ownerId}")
- public ModelAndView showOwner(@PathVariable("ownerId")
- int ownerId) {
- ModelAndView mav =
- new ModelAndView("owners/ownerDetails");
+ public ModelAndView showOwner(@PathVariable("ownerId") int ownerId) {
+ ModelAndView mav = new ModelAndView("owners/ownerDetails");
Owner owner = this.owners.findById(ownerId);
mav.addObject(owner);
return mav;
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..02df0e743 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,8 @@ public interface OwnerRepository extends Repository {
@Transactional(readOnly = true)
Page findAll(Pageable pageable);
+ // @Query("DROP Table Owner")
+ // @Transactional(readOnly = true)
+ // void deleteAll();
+
}
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 5ab40f7bd..ac76af0a1 100644
--- a/src/main/java/org/springframework/samples/petclinic/owner/PetController.java
+++ b/src/main/java/org/springframework/samples/petclinic/owner/PetController.java
@@ -87,20 +87,9 @@ class PetController {
return VIEWS_PETS_CREATE_OR_UPDATE_FORM;
}
-
-
-
-
-
-
-
-
-
-
-
@PostMapping("/pets/new")
public String processCreationForm(Owner owner, @Valid Pet pet, BindingResult result, ModelMap model) {
- if (StringUtils.hasLength(pet.getName()) && pet.isNew() && owner.getPet(pet.getName(), true) != null) {
+ if (StringUtils.hasLength(pet.getName()) && pet.isNew() && owner.getPet(pet.getName(), true) != null) {
result.rejectValue("name", "duplicate", "already exists");
}
@@ -110,19 +99,13 @@ class PetController {
return VIEWS_PETS_CREATE_OR_UPDATE_FORM;
}
+
this.owners.save(owner);
petVaccinationStatus.UpdateVaccinationStatus(owner.getPets().toArray(Pet[]::new));
return "redirect:/owners/{ownerId}";
}
-
-
-
-
-
-
-
@GetMapping("/pets/{petId}/edit")
public String initUpdateForm(Owner owner, @PathVariable("petId") int petId, ModelMap model) {
Pet pet = owner.getPet(petId);
diff --git a/src/main/java/org/springframework/samples/petclinic/system/WelcomeController.java b/src/main/java/org/springframework/samples/petclinic/system/WelcomeController.java
index 9224015bc..a3074fb29 100644
--- a/src/main/java/org/springframework/samples/petclinic/system/WelcomeController.java
+++ b/src/main/java/org/springframework/samples/petclinic/system/WelcomeController.java
@@ -16,6 +16,7 @@
package org.springframework.samples.petclinic.system;
+import io.opentelemetry.instrumentation.annotations.WithSpan;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@@ -27,4 +28,6 @@ class WelcomeController {
return "welcome";
}
+
+
}
diff --git a/src/main/resources/db/postgres/schema.sql b/src/main/resources/db/postgres/schema.sql
index f09c06f4c..6cee046d9 100644
--- a/src/main/resources/db/postgres/schema.sql
+++ b/src/main/resources/db/postgres/schema.sql
@@ -54,5 +54,5 @@ CREATE INDEX ON visits (pet_id);
CREATE TABLE IF NOT EXISTS pet_vaccines (
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
pet_id INT REFERENCES pets (id),
- vaccine_date DATE,
+ vaccine_date DATE
);
diff --git a/src/test/java/org/springframework/samples/petclinic/integration/OwnerControllerTests.java b/src/test/java/org/springframework/samples/petclinic/integration/OwnerControllerTests.java
new file mode 100644
index 000000000..b7b35a3fe
--- /dev/null
+++ b/src/test/java/org/springframework/samples/petclinic/integration/OwnerControllerTests.java
@@ -0,0 +1,154 @@
+package org.springframework.samples.petclinic.integration;
+
+import com.github.javafaker.Faker;
+import io.opentelemetry.api.trace.SpanKind;
+import io.opentelemetry.instrumentation.annotations.WithSpan;
+import io.restassured.RestAssured;
+import io.restassured.http.ContentType;
+import jakarta.persistence.EntityManagerFactory;
+import org.hamcrest.Matchers;
+import org.jetbrains.annotations.NotNull;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.server.LocalServerPort;
+import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
+import org.springframework.samples.petclinic.owner.*;
+import org.springframework.test.context.ActiveProfiles;
+import org.testcontainers.containers.PostgreSQLContainer;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+
+import java.time.ZoneId;
+import java.util.concurrent.TimeUnit;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import static io.restassured.RestAssured.given;
+import static org.hamcrest.Matchers.hasSize;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@Testcontainers
+@ActiveProfiles(value = "postgres")
+public class OwnerControllerTests {
+
+ @LocalServerPort
+ private Integer port;
+
+ @BeforeAll
+ static void beforeAll() {
+ postgres.start();
+ }
+
+ @AfterAll
+ static void afterAll() {
+ postgres.stop();
+ }
+
+ @Container
+ @ServiceConnection
+ static PostgreSQLContainer> postgres = new PostgreSQLContainer<>("postgres:15-alpine");
+
+ @BeforeEach
+ void setUp() {
+ // ownerRepository.deleteAll();
+
+ RestAssured.baseURI = "http://localhost:" + port;
+ }
+
+ Faker faker = new Faker();
+
+ @Autowired
+ EntityManagerFactory emf;
+
+ @Autowired
+ OwnerRepository ownerRepository;
+
+ @Test
+ @WithSpan(kind = SpanKind.SERVER)
+ void shouldSaveNewOwnerPet() {
+
+ Owner owner = CreateOwner();
+
+ for (int i = 0; i < 3; i++) {
+
+ String newPetName = faker.dog().name();
+ given().contentType("multipart/form-data")
+ .multiPart("id", "")
+ .multiPart("birthDate", "0222-02-02")
+ .multiPart("name", newPetName)
+ .multiPart("type", "dog")
+ .when()
+ .post(String.format("/owners/%s/pets/new", owner.getId()))
+ .then()
+ .statusCode(Matchers.not(Matchers.greaterThan(499)));
+ try {
+ Thread.sleep(800);
+ }
+ catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+
+ // var updatedOwner = ownerRepository.findById(owner.getId());
+ // assertThat(updatedOwner.getPets())
+ // .hasSize(2)
+ // .extracting(Pet::getName)
+ // .contains(newPetName);
+
+ }
+
+ @Test
+ void shouldGetAllOwners() {
+
+ Owner owner = CreateOwner();
+
+ var ownerLinkMatcher =
+ String.format("**.findAll { node -> node.@href=='/owners/%s'}",
+ owner.getId());
+
+ given()
+ .contentType(ContentType.JSON)
+ .when()
+ .get("/owners")
+ .then()
+ .contentType(ContentType.HTML)
+ .statusCode(200)
+ .body(ownerLinkMatcher,Matchers.notNullValue());
+
+ }
+
+ @NotNull
+ private Owner CreateOwner() {
+ var owner = new Owner();
+ owner.setFirstName(faker.name().firstName());
+ owner.setLastName(faker.name().lastName());
+ owner.setAddress(faker.address().streetAddress());
+ owner.setTelephone("5555555");
+ owner.setCity(faker.address().city());
+
+ Pet pet = new Pet();
+ pet.setName(faker.dog().name());
+ pet.setBirthDate(faker.date().birthday().toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
+ PetType dog = new PetType();
+ dog.setName(faker.dog().name());
+ dog.setId(2);
+ pet.setType(dog);
+ PetVaccine vaccine = new PetVaccine();
+ vaccine.setDate(faker.date()
+ .past(30, TimeUnit.DAYS, new java.util.Date())
+ .toInstant()
+ .atZone(ZoneId.systemDefault())
+ .toLocalDate());
+ pet.addVaccine(vaccine);
+
+ owner.addPet(pet);
+ ownerRepository.save(owner);
+ return owner;
+ }
+
+}