mirror of
https://github.com/spring-projects/spring-petclinic.git
synced 2025-07-20 14:55:50 +00:00
example scenarios
This commit is contained in:
parent
0d9e882e54
commit
2163b7c7f8
44 changed files with 1177 additions and 411 deletions
19
Dockerfile
Normal file
19
Dockerfile
Normal file
|
@ -0,0 +1,19 @@
|
|||
FROM eclipse-temurin:17-jre
|
||||
EXPOSE 8082
|
||||
|
||||
ENV OTEL_SERVICE_NAME=PetClinic
|
||||
ENV OTEL_EXPORTER_OTLP_ENDPOINT=http://host.docker.internal:5050
|
||||
ENV OTEL_LOGS_EXPORTER="otlp"
|
||||
ENV OTEL_METRICS_EXPORTER=none
|
||||
|
||||
ENV CODE_PACKAGE_PREFIXES="org.springframework.samples.petclinic"
|
||||
ENV DEPLOYMENT_ENV="SAMPLE_ENV"
|
||||
|
||||
ADD target/spring-petclinic-*.jar /app.jar
|
||||
ADD build/otel/opentelemetry-javaagent.jar /opentelemetry-javaagent.jar
|
||||
ADD build/otel/digma-otel-agent-extension.jar /digma-otel-agent-extension.jar
|
||||
|
||||
HEALTHCHECK --interval=20s --timeout=3s --start-period=10s --retries=4 \
|
||||
CMD curl -f http://localhost:8082/ || exit 1
|
||||
|
||||
ENTRYPOINT java -jar -javaagent:/opentelemetry-javaagent.jar -Dotel.javaagent.extensions=/digma-otel-agent-extension.jar app.jar
|
BIN
digma-otel-agent-extension.jar
Normal file
BIN
digma-otel-agent-extension.jar
Normal file
Binary file not shown.
15
docker-compose.override.otel.yml
Normal file
15
docker-compose.override.otel.yml
Normal file
|
@ -0,0 +1,15 @@
|
|||
version: '3'
|
||||
|
||||
services:
|
||||
|
||||
pet-clinic:
|
||||
volumes:
|
||||
- "./otel/opentelemetry-javaagent.jar:/otel/opentelemetry-javaagent.jar"
|
||||
- "./otel/digma-otel-agent-extension.jar:/otel/digma-otel-agent-extension.jar"
|
||||
|
||||
environment:
|
||||
- JAVA_TOOL_OPTIONS=-javaagent:/otel/opentelemetry-javaagent.jar -Dotel.exporter.otlp.endpoint=http://host.docker.internal:5050 -Dotel.javaagent.extensions=/otel/digma-otel-agent-extension.jar
|
||||
- OTEL_SERVICE_NAME=pet-clinic
|
||||
- DEPLOYMENT_ENV=LOCAL_DOCKER
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
|
@ -1,6 +1,18 @@
|
|||
version: "2.2"
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
pet-clinic:
|
||||
build: ./
|
||||
healthcheck:
|
||||
test: [ "CMD", "curl", "-f", "http://localhost:8082/" ]
|
||||
interval: 20s
|
||||
timeout: 10s
|
||||
retries: 4
|
||||
start_period: 5s
|
||||
ports:
|
||||
- "8082:8082"
|
||||
entrypoint: java -jar app.jar
|
||||
|
||||
mysql:
|
||||
image: mysql:8.0
|
||||
ports:
|
||||
|
|
BIN
opentelemetry-javaagent.jar
Normal file
BIN
opentelemetry-javaagent.jar
Normal file
Binary file not shown.
BIN
otel/digma-otel-agent-extension.jar
Normal file
BIN
otel/digma-otel-agent-extension.jar
Normal file
Binary file not shown.
BIN
otel/opentelemetry-javaagent.jar
Normal file
BIN
otel/opentelemetry-javaagent.jar
Normal file
Binary file not shown.
23
petshop-chart/.helmignore
Normal file
23
petshop-chart/.helmignore
Normal file
|
@ -0,0 +1,23 @@
|
|||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*.orig
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
.vscode/
|
24
petshop-chart/Chart.yaml
Normal file
24
petshop-chart/Chart.yaml
Normal file
|
@ -0,0 +1,24 @@
|
|||
apiVersion: v2
|
||||
name: petshop-chart
|
||||
description: A Helm chart for Kubernetes
|
||||
|
||||
# A chart can be either an 'application' or a 'library' chart.
|
||||
#
|
||||
# Application charts are a collection of templates that can be packaged into versioned archives
|
||||
# to be deployed.
|
||||
#
|
||||
# Library charts provide useful utilities or functions for the chart developer. They're included as
|
||||
# a dependency of application charts to inject those utilities and functions into the rendering
|
||||
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
|
||||
type: application
|
||||
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: 0.1.3
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
# It is recommended to use it with quotes.
|
||||
appVersion: "1.16.0"
|
22
petshop-chart/templates/NOTES.txt
Normal file
22
petshop-chart/templates/NOTES.txt
Normal file
|
@ -0,0 +1,22 @@
|
|||
1. Get the application URL by running these commands:
|
||||
{{- if .Values.ingress.enabled }}
|
||||
{{- range $host := .Values.ingress.hosts }}
|
||||
{{- range .paths }}
|
||||
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- else if contains "NodePort" .Values.service.type }}
|
||||
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "petshop-chart.fullname" . }})
|
||||
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
|
||||
echo http://$NODE_IP:$NODE_PORT
|
||||
{{- else if contains "LoadBalancer" .Values.service.type }}
|
||||
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
|
||||
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "petshop-chart.fullname" . }}'
|
||||
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "petshop-chart.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
|
||||
echo http://$SERVICE_IP:{{ .Values.service.port }}
|
||||
{{- else if contains "ClusterIP" .Values.service.type }}
|
||||
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "petshop-chart.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
|
||||
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
|
||||
echo "Visit http://127.0.0.1:8080 to use your application"
|
||||
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
|
||||
{{- end }}
|
62
petshop-chart/templates/_helpers.tpl
Normal file
62
petshop-chart/templates/_helpers.tpl
Normal file
|
@ -0,0 +1,62 @@
|
|||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "petshop-chart.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
If release name contains chart name it will be used as a full name.
|
||||
*/}}
|
||||
{{- define "petshop-chart.fullname" -}}
|
||||
{{- if .Values.fullnameOverride }}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||
{{- if contains $name .Release.Name }}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create chart name and version as used by the chart label.
|
||||
*/}}
|
||||
{{- define "petshop-chart.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Common labels
|
||||
*/}}
|
||||
{{- define "petshop-chart.labels" -}}
|
||||
helm.sh/chart: {{ include "petshop-chart.chart" . }}
|
||||
{{ include "petshop-chart.selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Selector labels
|
||||
*/}}
|
||||
{{- define "petshop-chart.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "petshop-chart.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create the name of the service account to use
|
||||
*/}}
|
||||
{{- define "petshop-chart.serviceAccountName" -}}
|
||||
{{- if .Values.serviceAccount.create }}
|
||||
{{- default (include "petshop-chart.fullname" .) .Values.serviceAccount.name }}
|
||||
{{- else }}
|
||||
{{- default "default" .Values.serviceAccount.name }}
|
||||
{{- end }}
|
||||
{{- end }}
|
72
petshop-chart/templates/deployment.yaml
Normal file
72
petshop-chart/templates/deployment.yaml
Normal file
|
@ -0,0 +1,72 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "petshop-chart.fullname" . }}
|
||||
labels:
|
||||
{{- include "petshop-chart.labels" . | nindent 4 }}
|
||||
spec:
|
||||
{{- if not .Values.autoscaling.enabled }}
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
{{- end }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "petshop-chart.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
{{- with .Values.podAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "petshop-chart.selectorLabels" . | nindent 8 }}
|
||||
spec:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
serviceAccountName: {{ include "petshop-chart.serviceAccountName" . }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8082
|
||||
protocol: TCP
|
||||
env:
|
||||
- name: "OTEL_SERVICE_NAME"
|
||||
value: "PetClinicWithAgent"
|
||||
- name: "OTEL_EXPORTER_OTLP_ENDPOINT"
|
||||
value: "http://digma-collector-api.digma.svc.cluster.local:5050"
|
||||
- name: "OTEL_LOGS_EXPORTER"
|
||||
value: "otlp"
|
||||
- name: "CODE_PACKAGE_PREFIXES"
|
||||
value: "org.springframework.samples.petclinic"
|
||||
- name: "DEPLOYMENT_ENV"
|
||||
value: "Load test"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
28
petshop-chart/templates/hpa.yaml
Normal file
28
petshop-chart/templates/hpa.yaml
Normal file
|
@ -0,0 +1,28 @@
|
|||
{{- if .Values.autoscaling.enabled }}
|
||||
apiVersion: autoscaling/v2beta1
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: {{ include "petshop-chart.fullname" . }}
|
||||
labels:
|
||||
{{- include "petshop-chart.labels" . | nindent 4 }}
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: {{ include "petshop-chart.fullname" . }}
|
||||
minReplicas: {{ .Values.autoscaling.minReplicas }}
|
||||
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
|
||||
metrics:
|
||||
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||
{{- end }}
|
||||
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
|
||||
{{- end }}
|
||||
{{- end }}
|
61
petshop-chart/templates/ingress.yaml
Normal file
61
petshop-chart/templates/ingress.yaml
Normal file
|
@ -0,0 +1,61 @@
|
|||
{{- if .Values.ingress.enabled -}}
|
||||
{{- $fullName := include "petshop-chart.fullname" . -}}
|
||||
{{- $svcPort := .Values.service.port -}}
|
||||
{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
|
||||
{{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }}
|
||||
{{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
{{- else -}}
|
||||
apiVersion: extensions/v1beta1
|
||||
{{- end }}
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ $fullName }}
|
||||
labels:
|
||||
{{- include "petshop-chart.labels" . | nindent 4 }}
|
||||
{{- with .Values.ingress.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
|
||||
ingressClassName: {{ .Values.ingress.className }}
|
||||
{{- end }}
|
||||
{{- if .Values.ingress.tls }}
|
||||
tls:
|
||||
{{- range .Values.ingress.tls }}
|
||||
- hosts:
|
||||
{{- range .hosts }}
|
||||
- {{ . | quote }}
|
||||
{{- end }}
|
||||
secretName: {{ .secretName }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
rules:
|
||||
{{- range .Values.ingress.hosts }}
|
||||
- host: {{ .host | quote }}
|
||||
http:
|
||||
paths:
|
||||
{{- range .paths }}
|
||||
- path: {{ .path }}
|
||||
{{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }}
|
||||
pathType: {{ .pathType }}
|
||||
{{- end }}
|
||||
backend:
|
||||
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
|
||||
service:
|
||||
name: {{ $fullName }}
|
||||
port:
|
||||
number: {{ $svcPort }}
|
||||
{{- else }}
|
||||
serviceName: {{ $fullName }}
|
||||
servicePort: {{ $svcPort }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
15
petshop-chart/templates/service.yaml
Normal file
15
petshop-chart/templates/service.yaml
Normal file
|
@ -0,0 +1,15 @@
|
|||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "petshop-chart.fullname" . }}
|
||||
labels:
|
||||
{{- include "petshop-chart.labels" . | nindent 4 }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
ports:
|
||||
- port: {{ .Values.service.port }}
|
||||
targetPort: 8082
|
||||
protocol: TCP
|
||||
name: api
|
||||
selector:
|
||||
{{- include "petshop-chart.selectorLabels" . | nindent 4 }}
|
12
petshop-chart/templates/serviceaccount.yaml
Normal file
12
petshop-chart/templates/serviceaccount.yaml
Normal file
|
@ -0,0 +1,12 @@
|
|||
{{- if .Values.serviceAccount.create -}}
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ include "petshop-chart.serviceAccountName" . }}
|
||||
labels:
|
||||
{{- include "petshop-chart.labels" . | nindent 4 }}
|
||||
{{- with .Values.serviceAccount.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
15
petshop-chart/templates/tests/test-connection.yaml
Normal file
15
petshop-chart/templates/tests/test-connection.yaml
Normal file
|
@ -0,0 +1,15 @@
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: "{{ include "petshop-chart.fullname" . }}-test-connection"
|
||||
labels:
|
||||
{{- include "petshop-chart.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
"helm.sh/hook": test
|
||||
spec:
|
||||
containers:
|
||||
- name: wget
|
||||
image: busybox
|
||||
command: ['wget']
|
||||
args: ['{{ include "petshop-chart.fullname" . }}:{{ .Values.service.port }}']
|
||||
restartPolicy: Never
|
82
petshop-chart/values.yaml
Normal file
82
petshop-chart/values.yaml
Normal file
|
@ -0,0 +1,82 @@
|
|||
# Default values for petshop-chart.
|
||||
# This is a YAML-formatted file.
|
||||
# Declare variables to be passed into your templates.
|
||||
|
||||
replicaCount: 1
|
||||
|
||||
image:
|
||||
repository: digmaai/petshop-app-deploy
|
||||
pullPolicy: IfNotPresent
|
||||
# Overrides the image tag whose default is the chart appVersion.
|
||||
tag: 0.5.5
|
||||
|
||||
imagePullSecrets: []
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
|
||||
serviceAccount:
|
||||
# Specifies whether a service account should be created
|
||||
create: true
|
||||
# Annotations to add to the service account
|
||||
annotations: {}
|
||||
# The name of the service account to use.
|
||||
# If not set and create is true, a name is generated using the fullname template
|
||||
name: ""
|
||||
|
||||
podAnnotations: {}
|
||||
|
||||
podSecurityContext: {}
|
||||
# fsGroup: 2000
|
||||
|
||||
securityContext: {}
|
||||
# capabilities:
|
||||
# drop:
|
||||
# - ALL
|
||||
# readOnlyRootFilesystem: true
|
||||
# runAsNonRoot: true
|
||||
# runAsUser: 1000
|
||||
|
||||
service:
|
||||
type: LoadBalancer
|
||||
port: 80
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
className: ""
|
||||
annotations: {}
|
||||
# kubernetes.io/ingress.class: nginx
|
||||
# kubernetes.io/tls-acme: "true"
|
||||
hosts:
|
||||
- host: chart-example.local
|
||||
paths:
|
||||
- path: /
|
||||
pathType: ImplementationSpecific
|
||||
tls: []
|
||||
# - secretName: chart-example-tls
|
||||
# hosts:
|
||||
# - chart-example.local
|
||||
|
||||
resources: {}
|
||||
# We usually recommend not to specify default resources and to leave this as a conscious
|
||||
# choice for the user. This also increases chances charts run on environments with little
|
||||
# resources, such as Minikube. If you do want to specify resources, uncomment the following
|
||||
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
|
||||
# limits:
|
||||
# cpu: 100m
|
||||
# memory: 128Mi
|
||||
# requests:
|
||||
# cpu: 100m
|
||||
# memory: 128Mi
|
||||
|
||||
autoscaling:
|
||||
enabled: false
|
||||
minReplicas: 1
|
||||
maxReplicas: 5
|
||||
targetCPUUtilizationPercentage: 80
|
||||
# targetMemoryUtilizationPercentage: 80
|
||||
|
||||
nodeSelector: {}
|
||||
|
||||
tolerations: []
|
||||
|
||||
affinity: {}
|
22
pom.xml
22
pom.xml
|
@ -83,7 +83,11 @@
|
|||
<artifactId>postgresql</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.micrometer</groupId>
|
||||
<artifactId>micrometer-registry-prometheus</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<!-- caching -->
|
||||
<dependency>
|
||||
<groupId>javax.cache</groupId>
|
||||
|
@ -106,7 +110,11 @@
|
|||
<version>${webjars-font-awesome.version}</version>
|
||||
</dependency>
|
||||
<!-- end of webjars -->
|
||||
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
<version>4.9.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
|
@ -117,6 +125,16 @@
|
|||
<groupId>jakarta.xml.bind</groupId>
|
||||
<artifactId>jakarta.xml.bind-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.opentelemetry.instrumentation</groupId>
|
||||
<artifactId>opentelemetry-instrumentation-annotations</artifactId>
|
||||
<version>1.26.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.vaadin.external.google</groupId>
|
||||
<artifactId>android-json</artifactId>
|
||||
<version>0.0.20131108.vaadin1</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package org.springframework.samples.petclinic.adapters;
|
||||
|
||||
import io.opentelemetry.instrumentation.annotations.WithSpan;
|
||||
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;
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
package org.springframework.samples.petclinic.adapters;
|
||||
|
||||
import io.opentelemetry.instrumentation.annotations.WithSpan;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
|
||||
@Component
|
||||
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{
|
||||
|
||||
Request getAllVaccinesRequest = new Request.Builder().url(url).build();
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
Response getAllVaccinesResult = client.newCall(getAllVaccinesRequest).execute();
|
||||
return getAllVaccinesResult.body().string();
|
||||
}
|
||||
|
||||
@Override
|
||||
@WithSpan
|
||||
public VaccinnationRecord[] AllVaccines() throws JSONException, IOException {
|
||||
|
||||
var vaccineListString = MakeHttpCall(VACCINES_RECORDS_URL);
|
||||
JSONArray jArr = new JSONArray(vaccineListString);
|
||||
var vaccinnationRecords =
|
||||
new ArrayList<VaccinnationRecord>();
|
||||
|
||||
for (int i = 0; i < jArr.length(); i++) {
|
||||
|
||||
VaccinnationRecord record = parseVaccinationRecord(jArr.getJSONObject(i));
|
||||
vaccinnationRecords.add(record);
|
||||
|
||||
}
|
||||
return vaccinnationRecords.toArray(VaccinnationRecord[]::new);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@WithSpan
|
||||
public VaccinnationRecord VaccineRecord(int vaccinationRecordId) throws JSONException, IOException {
|
||||
|
||||
var idUrl = VACCINES_RECORDS_URL + "/" + vaccinationRecordId;
|
||||
|
||||
var vaccineListString = MakeHttpCall(idUrl);
|
||||
|
||||
JSONObject vaccineJson = new JSONObject(vaccineListString);
|
||||
return parseVaccinationRecord(vaccineJson);
|
||||
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static VaccinnationRecord parseVaccinationRecord(JSONObject jsonObject) throws JSONException {
|
||||
Integer petId = jsonObject.getInt("pet_id");
|
||||
Integer id = jsonObject.getInt("id");
|
||||
String vaccineDateString = jsonObject.getString("vaccine_date");
|
||||
var vaccineDate = Instant.parse(vaccineDateString);
|
||||
return new VaccinnationRecord(id, petId, vaccineDate);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package org.springframework.samples.petclinic.adapters;
|
||||
|
||||
public record VaccinnationRecord(int recordId, int petId, java.time.Instant vaccineDate) {
|
||||
|
||||
}
|
|
@ -0,0 +1,172 @@
|
|||
package org.springframework.samples.petclinic.domain;
|
||||
|
||||
import io.opentelemetry.api.trace.Span;
|
||||
import io.opentelemetry.api.trace.SpanKind;
|
||||
import io.opentelemetry.api.trace.Tracer;
|
||||
import io.opentelemetry.instrumentation.annotations.WithSpan;
|
||||
import org.springframework.samples.petclinic.owner.Owner;
|
||||
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class OwnerValidation {
|
||||
|
||||
private int counter = 0;
|
||||
|
||||
private UserValidationService usrValSvc;
|
||||
|
||||
private PasswordUtils pwdUtils;
|
||||
|
||||
private Tracer otelTracer;
|
||||
|
||||
private RoleService roleSvc;
|
||||
|
||||
private TwoFactorAuthenticationService twoFASvc;
|
||||
|
||||
public OwnerValidation(Tracer otelTracer) {
|
||||
this.pwdUtils = new PasswordUtils();
|
||||
this.roleSvc = new RoleService();
|
||||
this.otelTracer = otelTracer;
|
||||
this.usrValSvc = new UserValidationService();
|
||||
this.twoFASvc = new TwoFactorAuthenticationService();
|
||||
}
|
||||
|
||||
@WithSpan
|
||||
public void ValidateOwnerWithExternalService(Owner owner) {
|
||||
|
||||
this.AuthServiceValidateUser(owner);
|
||||
}
|
||||
|
||||
@WithSpan
|
||||
private void NewFunction() {
|
||||
|
||||
}
|
||||
|
||||
@WithSpan
|
||||
public boolean UserNameMustStartWithR(String usr) {
|
||||
|
||||
if (!usr.toLowerCase().startsWith("r")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
@WithSpan
|
||||
// This function and classes were generated by ChatGPT
|
||||
public boolean ValidateUserAccess(String usr, String pswd, String sysCode) {
|
||||
|
||||
UserNameMustStartWithR(usr);
|
||||
boolean vldUsr = usrValSvc.vldtUsr(usr);
|
||||
if (!vldUsr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
boolean vldPswd = pwdUtils.vldtPswd(usr, pswd);
|
||||
if (!vldPswd) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean vldUsrRole = roleSvc.vldtUsrRole(usr, sysCode);
|
||||
if (!vldUsrRole) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean is2FASuccess = twoFASvc.init2FA(usr);
|
||||
if (!is2FASuccess) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean is2FATokenValid = false;
|
||||
int retry = 0;
|
||||
while (retry < 3 && !is2FATokenValid) {
|
||||
String token = twoFASvc.getTokenInput();
|
||||
is2FATokenValid = twoFASvc.vldtToken(usr, token);
|
||||
retry++;
|
||||
}
|
||||
|
||||
if (!is2FATokenValid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@WithSpan
|
||||
private synchronized void AuthServiceValidateUser(Owner owner) {
|
||||
// This is the actual Root Cause!!
|
||||
try {
|
||||
Thread.sleep(4200 + ThreadLocalRandom.current().nextInt(90, 1100 + 1));
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@WithSpan
|
||||
public boolean checkOwnerValidity(Owner owner) {
|
||||
|
||||
this.ValidateOwnerUserBad(owner);
|
||||
return ValidateOwnerUser(owner);
|
||||
|
||||
}
|
||||
|
||||
@WithSpan
|
||||
private boolean ValidateOwnerUserBad(Owner owner) {
|
||||
{
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
ValidateOwner();
|
||||
}
|
||||
return true;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@WithSpan
|
||||
private boolean ValidateOwnerUser(Owner owner) {
|
||||
|
||||
Span span = otelTracer.spanBuilder("db_access_01").startSpan();
|
||||
|
||||
var max = ThreadLocalRandom.current().nextInt(90, 110 + 1);
|
||||
try {
|
||||
for (int i = 0; i < max; i++) {
|
||||
ValidateOwner();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
span.end();
|
||||
}
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
@WithSpan
|
||||
private void ValidateOwner() {
|
||||
// simulate SpanKind of DB query
|
||||
// see
|
||||
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/database.md
|
||||
Span span = otelTracer.spanBuilder("query_users_by_id")
|
||||
.setSpanKind(SpanKind.CLIENT)
|
||||
.setAttribute("db.system", "other_sql")
|
||||
.setAttribute("db.statement", "select * from users where id = :id")
|
||||
.startSpan();
|
||||
|
||||
try {
|
||||
Thread.sleep(14);
|
||||
}
|
||||
catch (Exception e) {
|
||||
|
||||
}
|
||||
finally {
|
||||
span.end();
|
||||
}
|
||||
}
|
||||
|
||||
@WithSpan
|
||||
public void PerformValidationFlow(Owner owner) {
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package org.springframework.samples.petclinic.domain;
|
||||
|
||||
import io.opentelemetry.instrumentation.annotations.WithSpan;
|
||||
|
||||
public class PasswordUtils {
|
||||
|
||||
@WithSpan
|
||||
public boolean vldtPswd(String usr, String pswd) {
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@WithSpan
|
||||
public String encPswd(String pswd) {
|
||||
try {
|
||||
Thread.sleep(300);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package org.springframework.samples.petclinic.domain;
|
||||
|
||||
import io.opentelemetry.api.trace.Span;
|
||||
import org.json.JSONException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.samples.petclinic.adapters.PetVaccinationService;
|
||||
import org.springframework.samples.petclinic.adapters.PetVaccinationServiceFacade;
|
||||
import org.springframework.samples.petclinic.adapters.VaccinnationRecord;
|
||||
import org.springframework.samples.petclinic.owner.Pet;
|
||||
import org.springframework.samples.petclinic.owner.PetVaccine;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
|
||||
@Component
|
||||
public class PetVaccinationStatusService {
|
||||
|
||||
@Autowired
|
||||
private PetVaccinationService adapter;
|
||||
|
||||
public void UpdateVaccinationStatus(Pet[] pets){
|
||||
|
||||
for (Pet pet: pets){
|
||||
try {
|
||||
var vaccinationRecords = this.adapter.AllVaccines();
|
||||
for (VaccinnationRecord record : vaccinationRecords){
|
||||
|
||||
var recordInfo = this.adapter.VaccineRecord(record.recordId());
|
||||
if (recordInfo.petId()==pet.getId()){
|
||||
var date = LocalDateTime.ofInstant(recordInfo.vaccineDate(), ZoneId.systemDefault());
|
||||
PetVaccine petVaccine = new PetVaccine();
|
||||
petVaccine.setDate(date.toLocalDate());
|
||||
pet.addVaccine(petVaccine);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (JSONException |IOException e) {
|
||||
//Fail silently
|
||||
Span.current().recordException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package org.springframework.samples.petclinic.domain;
|
||||
|
||||
import io.opentelemetry.instrumentation.annotations.WithSpan;
|
||||
|
||||
public class RoleService {
|
||||
|
||||
@WithSpan
|
||||
public boolean vldtUsrRole(String usr, String sysCode) {
|
||||
try {
|
||||
Thread.sleep(40);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package org.springframework.samples.petclinic.domain;
|
||||
|
||||
import io.opentelemetry.instrumentation.annotations.WithSpan;
|
||||
|
||||
public class TwoFactorAuthenticationService {
|
||||
|
||||
@WithSpan
|
||||
public boolean init2FA(String usr) {
|
||||
try {
|
||||
Thread.sleep(400);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getTokenInput() {
|
||||
return "";
|
||||
}
|
||||
|
||||
public boolean vldtToken(String usr, String token) {
|
||||
try {
|
||||
Thread.sleep(40);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package org.springframework.samples.petclinic.domain;
|
||||
|
||||
import io.opentelemetry.instrumentation.annotations.WithSpan;
|
||||
|
||||
public class UserValidationService {
|
||||
|
||||
@WithSpan
|
||||
public boolean vldtUsr(String usr) {
|
||||
|
||||
try {
|
||||
Thread.sleep(300);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -15,7 +15,11 @@
|
|||
*/
|
||||
package org.springframework.samples.petclinic.owner;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
|
@ -124,6 +128,27 @@ public class Owner extends Person {
|
|||
return null;
|
||||
}
|
||||
|
||||
public boolean isVaccineExpired() {
|
||||
|
||||
for (Pet pet : getPets()) {
|
||||
|
||||
if (!pet.isNew()) {
|
||||
|
||||
for (PetVaccine vaccine : pet.getPetVaccines()) {
|
||||
|
||||
Duration duration = Duration.between(LocalDateTime.now(), vaccine.getDate());
|
||||
if (duration.toDays() > 360) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Pet with the given name, or null if none found for this Owner.
|
||||
* @param name to test
|
||||
|
|
|
@ -18,9 +18,11 @@ package org.springframework.samples.petclinic.owner;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import io.opentelemetry.instrumentation.annotations.WithSpan;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.samples.petclinic.domain.OwnerValidation;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.validation.BindingResult;
|
||||
|
@ -35,6 +37,8 @@ import org.springframework.web.servlet.ModelAndView;
|
|||
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
import static io.opentelemetry.api.GlobalOpenTelemetry.getTracer;
|
||||
|
||||
/**
|
||||
* @author Juergen Hoeller
|
||||
* @author Ken Krebs
|
||||
|
@ -48,8 +52,14 @@ class OwnerController {
|
|||
|
||||
private final OwnerRepository owners;
|
||||
|
||||
private OwnerValidation validator;
|
||||
|
||||
public OwnerController(OwnerRepository clinicService) {
|
||||
this.owners = clinicService;
|
||||
var otelTracer = getTracer("OwnerController");
|
||||
|
||||
validator = new OwnerValidation(otelTracer);
|
||||
|
||||
}
|
||||
|
||||
@InitBinder
|
||||
|
@ -64,7 +74,10 @@ class OwnerController {
|
|||
|
||||
@GetMapping("/owners/new")
|
||||
public String initCreationForm(Map<String, Object> model) {
|
||||
|
||||
Owner owner = new Owner();
|
||||
validator.ValidateOwnerWithExternalService(owner);
|
||||
|
||||
model.put("owner", owner);
|
||||
return VIEWS_OWNER_CREATE_OR_UPDATE_FORM;
|
||||
}
|
||||
|
@ -74,8 +87,12 @@ class OwnerController {
|
|||
if (result.hasErrors()) {
|
||||
return VIEWS_OWNER_CREATE_OR_UPDATE_FORM;
|
||||
}
|
||||
validator.ValidateOwnerWithExternalService(owner);
|
||||
validator.PerformValidationFlow(owner);
|
||||
|
||||
validator.checkOwnerValidity(owner);
|
||||
this.owners.save(owner);
|
||||
validator.ValidateUserAccess("admin", "pwd", "fullaccess");
|
||||
return "redirect:/owners/" + owner.getId();
|
||||
}
|
||||
|
||||
|
@ -87,6 +104,8 @@ class OwnerController {
|
|||
@GetMapping("/owners")
|
||||
public String processFindForm(@RequestParam(defaultValue = "1") int page, Owner owner, BindingResult result,
|
||||
Model model) {
|
||||
validator.ValidateUserAccess("admin", "pwd", "fullaccess");
|
||||
|
||||
// allow parameterless GET request for /owners to return all records
|
||||
if (owner.getLastName() == null) {
|
||||
owner.setLastName(""); // empty string signifies broadest possible search
|
||||
|
@ -110,6 +129,11 @@ class OwnerController {
|
|||
return addPaginationModel(page, model, ownersResults);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private String addPaginationModel(int page, Model model, Page<Owner> paginated) {
|
||||
model.addAttribute("listOwners", paginated);
|
||||
List<Owner> listOwners = paginated.getContent();
|
||||
|
@ -120,12 +144,18 @@ class OwnerController {
|
|||
return "owners/ownersList";
|
||||
}
|
||||
|
||||
@WithSpan
|
||||
private Page<Owner> findPaginatedForOwnersLastName(int page, String lastname) {
|
||||
int pageSize = 5;
|
||||
int pageSize = 25;
|
||||
Pageable pageable = PageRequest.of(page - 1, pageSize);
|
||||
return owners.findByLastName(lastname, pageable);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@GetMapping("/owners/{ownerId}/edit")
|
||||
public String initUpdateOwnerForm(@PathVariable("ownerId") int ownerId, Model model) {
|
||||
Owner owner = this.owners.findById(ownerId);
|
||||
|
@ -133,6 +163,24 @@ 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) {
|
||||
|
@ -151,8 +199,10 @@ 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;
|
||||
|
|
|
@ -20,6 +20,7 @@ import java.util.Collection;
|
|||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import io.opentelemetry.instrumentation.annotations.WithSpan;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.samples.petclinic.model.NamedEntity;
|
||||
|
||||
|
@ -57,6 +58,11 @@ public class Pet extends NamedEntity {
|
|||
@OrderBy("visit_date ASC")
|
||||
private Set<Visit> visits = new LinkedHashSet<>();
|
||||
|
||||
@OneToMany(cascade = CascadeType.ALL)
|
||||
@JoinColumn(name = "pet_id")
|
||||
@OrderBy("vaccine_date ASC")
|
||||
private Set<PetVaccine> pet_vaccines = new LinkedHashSet<>();
|
||||
|
||||
public void setBirthDate(LocalDate birthDate) {
|
||||
this.birthDate = birthDate;
|
||||
}
|
||||
|
@ -77,8 +83,16 @@ public class Pet extends NamedEntity {
|
|||
return this.visits;
|
||||
}
|
||||
|
||||
public Collection<PetVaccine> getPetVaccines() {
|
||||
return this.pet_vaccines;
|
||||
}
|
||||
|
||||
public void addVisit(Visit visit) {
|
||||
getVisits().add(visit);
|
||||
}
|
||||
|
||||
public void addVaccine(PetVaccine vaccine) {
|
||||
getPetVaccines().add(vaccine);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@ package org.springframework.samples.petclinic.owner;
|
|||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.samples.petclinic.domain.PetVaccinationStatusService;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.ModelMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
@ -44,6 +46,9 @@ class PetController {
|
|||
|
||||
private final OwnerRepository owners;
|
||||
|
||||
@Autowired
|
||||
private PetVaccinationStatusService petVaccinationStatus;
|
||||
|
||||
public PetController(OwnerRepository owners) {
|
||||
this.owners = owners;
|
||||
}
|
||||
|
@ -82,6 +87,17 @@ 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) {
|
||||
|
@ -95,9 +111,18 @@ class PetController {
|
|||
}
|
||||
|
||||
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);
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
package org.springframework.samples.petclinic.owner;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Table;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.samples.petclinic.model.BaseEntity;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* Simple JavaBean domain object representing a visit.
|
||||
*
|
||||
* @author Ken Krebs
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "pet_vaccines")
|
||||
public class PetVaccine extends BaseEntity {
|
||||
|
||||
@Column(name = "vaccine_date")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
private LocalDate date;
|
||||
|
||||
/**
|
||||
* Creates a new instance of Visit for the current date
|
||||
*/
|
||||
public PetVaccine() {
|
||||
}
|
||||
|
||||
public LocalDate getDate() {
|
||||
return this.date;
|
||||
}
|
||||
|
||||
public void setDate(LocalDate date) {
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
}
|
|
@ -15,10 +15,18 @@
|
|||
*/
|
||||
package org.springframework.samples.petclinic.owner;
|
||||
|
||||
import io.opentelemetry.instrumentation.annotations.WithSpan;
|
||||
import okhttp3.*;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.Validator;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* <code>Validator</code> for <code>Pet</code> forms.
|
||||
* <p>
|
||||
|
@ -51,6 +59,35 @@ public class PetValidator implements Validator {
|
|||
if (pet.getBirthDate() == null) {
|
||||
errors.rejectValue("birthDate", REQUIRED, REQUIRED);
|
||||
}
|
||||
|
||||
try {
|
||||
updateVaccinationStatus(pet);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@WithSpan
|
||||
private boolean updateVaccinationStatus(Pet pet) throws IOException {
|
||||
// OkHttpClient client = new OkHttpClient();
|
||||
//
|
||||
// Request request = new
|
||||
// Request.Builder().url("https://647f4bb4c246f166da9084c7.mockapi.io/api/vetcheck/vaccines")
|
||||
// .build();
|
||||
//
|
||||
// String responseText = "";
|
||||
// try (Response response = client.newCall(request).execute()) {
|
||||
// responseText = response.body().string();
|
||||
// JSONObject Jobject = new JSONObject(responseText);
|
||||
// JSONArray Jarray = Jobject.getJSONArray("employees");
|
||||
//
|
||||
// }
|
||||
// catch (JSONException e) {
|
||||
// throw new RuntimeException(e);
|
||||
// }
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
database=postgres
|
||||
spring.datasource.url=${POSTGRES_URL:jdbc:postgresql://localhost/petclinic}
|
||||
spring.datasource.url=${POSTGRES_URL:jdbc:postgresql://localhost/postgres}
|
||||
spring.datasource.username=${POSTGRES_USER:petclinic}
|
||||
spring.datasource.password=${POSTGRES_PASS:petclinic}
|
||||
# SQL is written to be idempotent so this is safe
|
||||
|
|
|
@ -15,6 +15,11 @@ spring.messages.basename=messages/messages
|
|||
|
||||
# Actuator
|
||||
management.endpoints.web.exposure.include=*
|
||||
management.metrics.distribution.slo.http.server.requests=50ms, 100ms, 200ms, 400ms
|
||||
management.metrics.distribution.percentiles.http.server.requests=0.5, 0.9, 0.95, 0.99, 0.999
|
||||
management.metrics.web.server.request.autotime.percentiles=0.95
|
||||
management.metrics.distribution.percentiles-histogram.http.server.requests=true
|
||||
|
||||
|
||||
# Logging
|
||||
logging.level.org.springframework=INFO
|
||||
|
@ -23,3 +28,4 @@ logging.level.org.springframework=INFO
|
|||
|
||||
# Maximum time static resources should be cached
|
||||
spring.web.resources.cache.cachecontrol.max-age=12h
|
||||
server.port=8082
|
||||
|
|
|
@ -34,6 +34,18 @@ INSERT INTO owners VALUES (default, 'David', 'Schroeder', '2749 Blackhawk Trail'
|
|||
INSERT INTO owners VALUES (default, 'Carlos', 'Estaban', '2335 Independence La.', 'Waunakee', '6085555487');
|
||||
|
||||
INSERT INTO pets VALUES (default, 'Leo', '2010-09-07', 1, 1);
|
||||
INSERT INTO pets VALUES (default, 'Mocka', '2010-09-07', 1, 1);
|
||||
INSERT INTO pets VALUES (default, 'Abby', '2010-09-07', 1, 1);
|
||||
INSERT INTO pets VALUES (default, 'Tesla', '2010-09-07', 1, 1);
|
||||
INSERT INTO pets VALUES (default, 'Freer', '2010-09-07', 1, 1);
|
||||
INSERT INTO pets VALUES (default, 'Castro', '2010-09-07', 1, 1);
|
||||
INSERT INTO pets VALUES (default, 'Mohawk', '2010-09-07', 1, 1);
|
||||
INSERT INTO pets VALUES (default, 'Jerome', '2010-09-07', 1, 1);
|
||||
INSERT INTO pets VALUES (default, 'Niley', '2010-09-07', 1, 1);
|
||||
INSERT INTO pets VALUES (default, 'Timmy', '2010-09-07', 1, 1);
|
||||
INSERT INTO pets VALUES (default, 'Bob', '2010-09-07', 1, 1);
|
||||
|
||||
|
||||
INSERT INTO pets VALUES (default, 'Basil', '2012-08-06', 6, 2);
|
||||
INSERT INTO pets VALUES (default, 'Rosy', '2011-04-17', 2, 3);
|
||||
INSERT INTO pets VALUES (default, 'Jewel', '2010-03-07', 2, 3);
|
||||
|
|
|
@ -62,3 +62,12 @@ CREATE TABLE visits (
|
|||
);
|
||||
ALTER TABLE visits ADD CONSTRAINT fk_visits_pets FOREIGN KEY (pet_id) REFERENCES pets (id);
|
||||
CREATE INDEX visits_pet_id ON visits (pet_id);
|
||||
|
||||
CREATE TABLE pet_vaccines (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
pet_id INTEGER,
|
||||
vaccine_date DATE
|
||||
);
|
||||
|
||||
ALTER TABLE pet_vaccines ADD CONSTRAINT fk_pet_vaccines_pets FOREIGN KEY (pet_id) REFERENCES pets (id);
|
||||
CREATE INDEX pet_vaccines_pet_id ON pet_vaccines (pet_id);
|
||||
|
|
|
@ -1,53 +1,53 @@
|
|||
INSERT INTO vets (first_name, last_name) SELECT 'James', 'Carter' WHERE NOT EXISTS (SELECT * FROM vets WHERE id=1);
|
||||
INSERT INTO vets (first_name, last_name) SELECT 'Helen', 'Leary' WHERE NOT EXISTS (SELECT * FROM vets WHERE id=2);
|
||||
INSERT INTO vets (first_name, last_name) SELECT 'Linda', 'Douglas' WHERE NOT EXISTS (SELECT * FROM vets WHERE id=3);
|
||||
INSERT INTO vets (first_name, last_name) SELECT 'Rafael', 'Ortega' WHERE NOT EXISTS (SELECT * FROM vets WHERE id=4);
|
||||
INSERT INTO vets (first_name, last_name) SELECT 'Henry', 'Stevens' WHERE NOT EXISTS (SELECT * FROM vets WHERE id=5);
|
||||
INSERT INTO vets (first_name, last_name) SELECT 'Sharon', 'Jenkins' WHERE NOT EXISTS (SELECT * FROM vets WHERE id=6);
|
||||
INSERT INTO vets (first_name, last_name) SELECT 'James', 'Carter' WHERE NOT EXISTS (SELECT * FROM vets WHERE id=1);
|
||||
INSERT INTO vets (first_name, last_name) SELECT 'Helen', 'Leary' WHERE NOT EXISTS (SELECT * FROM vets WHERE id=2);
|
||||
INSERT INTO vets (first_name, last_name) SELECT 'Linda', 'Douglas' WHERE NOT EXISTS (SELECT * FROM vets WHERE id=3);
|
||||
INSERT INTO vets (first_name, last_name) SELECT 'Rafael', 'Ortega' WHERE NOT EXISTS (SELECT * FROM vets WHERE id=4);
|
||||
INSERT INTO vets (first_name, last_name) SELECT 'Henry', 'Stevens' WHERE NOT EXISTS (SELECT * FROM vets WHERE id=5);
|
||||
INSERT INTO vets (first_name, last_name) SELECT 'Sharon', 'Jenkins' WHERE NOT EXISTS (SELECT * FROM vets WHERE id=6);
|
||||
|
||||
INSERT INTO specialties (name) SELECT 'radiology' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='radiology');
|
||||
INSERT INTO specialties (name) SELECT 'surgery' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='surgery');
|
||||
INSERT INTO specialties (name) SELECT 'dentistry' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='dentistry');
|
||||
INSERT INTO specialties (name) SELECT 'radiology' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='radiology');
|
||||
INSERT INTO specialties (name) SELECT 'surgery' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='surgery');
|
||||
INSERT INTO specialties (name) SELECT 'dentistry' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='dentistry');
|
||||
|
||||
INSERT INTO vet_specialties VALUES (2, 1) ON CONFLICT (vet_id, specialty_id) DO NOTHING;
|
||||
INSERT INTO vet_specialties VALUES (3, 2) ON CONFLICT (vet_id, specialty_id) DO NOTHING;
|
||||
INSERT INTO vet_specialties VALUES (3, 3) ON CONFLICT (vet_id, specialty_id) DO NOTHING;
|
||||
INSERT INTO vet_specialties VALUES (4, 2) ON CONFLICT (vet_id, specialty_id) DO NOTHING;
|
||||
INSERT INTO vet_specialties VALUES (5, 1) ON CONFLICT (vet_id, specialty_id) DO NOTHING;
|
||||
INSERT INTO vet_specialties VALUES (2, 1) ON CONFLICT (vet_id, specialty_id) DO NOTHING;
|
||||
INSERT INTO vet_specialties VALUES (3, 2) ON CONFLICT (vet_id, specialty_id) DO NOTHING;
|
||||
INSERT INTO vet_specialties VALUES (3, 3) ON CONFLICT (vet_id, specialty_id) DO NOTHING;
|
||||
INSERT INTO vet_specialties VALUES (4, 2) ON CONFLICT (vet_id, specialty_id) DO NOTHING;
|
||||
INSERT INTO vet_specialties VALUES (5, 1) ON CONFLICT (vet_id, specialty_id) DO NOTHING;
|
||||
|
||||
INSERT INTO types (name) SELECT 'cat' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='cat');
|
||||
INSERT INTO types (name) SELECT 'dog' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='dog');
|
||||
INSERT INTO types (name) SELECT 'lizard' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='lizard');
|
||||
INSERT INTO types (name) SELECT 'snake' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='snake');
|
||||
INSERT INTO types (name) SELECT 'bird' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='bird');
|
||||
INSERT INTO types (name) SELECT 'hamster' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='cat');
|
||||
INSERT INTO types (name) SELECT 'cat' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='cat');
|
||||
INSERT INTO types (name) SELECT 'dog' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='dog');
|
||||
INSERT INTO types (name) SELECT 'lizard' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='lizard');
|
||||
INSERT INTO types (name) SELECT 'snake' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='snake');
|
||||
INSERT INTO types (name) SELECT 'bird' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='bird');
|
||||
INSERT INTO types (name) SELECT 'hamster' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='cat');
|
||||
|
||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'George', 'Franklin', '110 W. Liberty St.', 'Madison', '6085551023' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=1);
|
||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Betty', 'Davis', '638 Cardinal Ave.', 'Sun Prairie', '6085551749' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=2);
|
||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Eduardo', 'Rodriquez', '2693 Commerce St.', 'McFarland', '6085558763' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=3);
|
||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Harold', 'Davis', '563 Friendly St.', 'Windsor', '6085553198' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=4);
|
||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Peter', 'McTavish', '2387 S. Fair Way', 'Madison', '6085552765' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=5);
|
||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Jean', 'Coleman', '105 N. Lake St.', 'Monona', '6085552654' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=6);
|
||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Jeff', 'Black', '1450 Oak Blvd.', 'Monona', '6085555387' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=7);
|
||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Maria', 'Escobito', '345 Maple St.', 'Madison', '6085557683' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=8);
|
||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'David', 'Schroeder', '2749 Blackhawk Trail', 'Madison', '6085559435' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=9);
|
||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Carlos', 'Estaban', '2335 Independence La.', 'Waunakee', '6085555487' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=10);
|
||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'George', 'Franklin', '110 W. Liberty St.', 'Madison', '6085551023' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=1);
|
||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Betty', 'Davis', '638 Cardinal Ave.', 'Sun Prairie', '6085551749' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=2);
|
||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Eduardo', 'Rodriquez', '2693 Commerce St.', 'McFarland', '6085558763' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=3);
|
||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Harold', 'Davis', '563 Friendly St.', 'Windsor', '6085553198' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=4);
|
||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Peter', 'McTavish', '2387 S. Fair Way', 'Madison', '6085552765' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=5);
|
||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Jean', 'Coleman', '105 N. Lake St.', 'Monona', '6085552654' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=6);
|
||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Jeff', 'Black', '1450 Oak Blvd.', 'Monona', '6085555387' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=7);
|
||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Maria', 'Escobito', '345 Maple St.', 'Madison', '6085557683' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=8);
|
||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'David', 'Schroeder', '2749 Blackhawk Trail', 'Madison', '6085559435' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=9);
|
||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Carlos', 'Estaban', '2335 Independence La.', 'Waunakee', '6085555487' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=10);
|
||||
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Leo', '2000-09-07', 1, 1 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=1);
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Basil', '2002-08-06', 6, 2 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=2);
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Rosy', '2001-04-17', 2, 3 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=3);
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Jewel', '2000-03-07', 2, 3 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=4);
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Iggy', '2000-11-30', 3, 4 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=5);
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'George', '2000-01-20', 4, 5 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=6);
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Samantha', '1995-09-04', 1, 6 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=7);
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Max', '1995-09-04', 1, 6 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=8);
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Lucky', '1999-08-06', 5, 7 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=9);
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Mulligan', '1997-02-24', 2, 8 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=10);
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Freddy', '2000-03-09', 5, 9 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=11);
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Lucky', '2000-06-24', 2, 10 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=12);
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Sly', '2002-06-08', 1, 10 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=13);
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Leo', '2000-09-07', 1, 1 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=1);
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Basil', '2002-08-06', 6, 2 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=2);
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Rosy', '2001-04-17', 2, 3 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=3);
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Jewel', '2000-03-07', 2, 3 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=4);
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Iggy', '2000-11-30', 3, 4 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=5);
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'George', '2000-01-20', 4, 5 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=6);
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Samantha', '1995-09-04', 1, 6 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=7);
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Max', '1995-09-04', 1, 6 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=8);
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Lucky', '1999-08-06', 5, 7 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=9);
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Mulligan', '1997-02-24', 2, 8 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=10);
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Freddy', '2000-03-09', 5, 9 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=11);
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Lucky', '2000-06-24', 2, 10 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=12);
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Sly', '2002-06-08', 1, 10 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=13);
|
||||
|
||||
INSERT INTO visits (pet_id, visit_date, description) SELECT 7, '2010-03-04', 'rabies shot' WHERE NOT EXISTS (SELECT * FROM visits WHERE id=1);
|
||||
INSERT INTO visits (pet_id, visit_date, description) SELECT 8, '2011-03-04', 'rabies shot' WHERE NOT EXISTS (SELECT * FROM visits WHERE id=2);
|
||||
INSERT INTO visits (pet_id, visit_date, description) SELECT 8, '2009-06-04', 'neutered' WHERE NOT EXISTS (SELECT * FROM visits WHERE id=3);
|
||||
INSERT INTO visits (pet_id, visit_date, description) SELECT 7, '2008-09-04', 'spayed' WHERE NOT EXISTS (SELECT * FROM visits WHERE id=4);
|
||||
INSERT INTO visits (pet_id, visit_date, description) SELECT 7, '2010-03-04', 'rabies shot' WHERE NOT EXISTS (SELECT * FROM visits WHERE id=1);
|
||||
INSERT INTO visits (pet_id, visit_date, description) SELECT 8, '2011-03-04', 'rabies shot' WHERE NOT EXISTS (SELECT * FROM visits WHERE id=2);
|
||||
INSERT INTO visits (pet_id, visit_date, description) SELECT 8, '2009-06-04', 'neutered' WHERE NOT EXISTS (SELECT * FROM visits WHERE id=3);
|
||||
INSERT INTO visits (pet_id, visit_date, description) SELECT 7, '2008-09-04', 'spayed' WHERE NOT EXISTS (SELECT * FROM visits WHERE id=4);
|
||||
|
|
|
@ -50,3 +50,9 @@ CREATE TABLE IF NOT EXISTS visits (
|
|||
description TEXT
|
||||
);
|
||||
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,
|
||||
);
|
||||
|
|
|
@ -26,6 +26,11 @@
|
|||
<th>Telephone</th>
|
||||
<td th:text="*{telephone}"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Needs Vaccine</th>
|
||||
<td th:text="*{isVaccineExpired()}"></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<a th:href="@{__${owner.id}__/edit}" class="btn btn-primary">Edit
|
||||
|
|
|
@ -1,126 +0,0 @@
|
|||
/*
|
||||
* 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 org.assertj.core.util.Lists;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.FilterType;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||||
|
||||
/**
|
||||
* Test class for the {@link PetController}
|
||||
*
|
||||
* @author Colin But
|
||||
*/
|
||||
@WebMvcTest(value = PetController.class,
|
||||
includeFilters = @ComponentScan.Filter(value = PetTypeFormatter.class, type = FilterType.ASSIGNABLE_TYPE))
|
||||
class PetControllerTests {
|
||||
|
||||
private static final int TEST_OWNER_ID = 1;
|
||||
|
||||
private static final int TEST_PET_ID = 1;
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@MockBean
|
||||
private OwnerRepository owners;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
PetType cat = new PetType();
|
||||
cat.setId(3);
|
||||
cat.setName("hamster");
|
||||
given(this.owners.findPetTypes()).willReturn(Lists.newArrayList(cat));
|
||||
Owner owner = new Owner();
|
||||
Pet pet = new Pet();
|
||||
owner.addPet(pet);
|
||||
pet.setId(TEST_PET_ID);
|
||||
given(this.owners.findById(TEST_OWNER_ID)).willReturn(owner);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInitCreationForm() throws Exception {
|
||||
mockMvc.perform(get("/owners/{ownerId}/pets/new", TEST_OWNER_ID))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(view().name("pets/createOrUpdatePetForm"))
|
||||
.andExpect(model().attributeExists("pet"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessCreationFormSuccess() throws Exception {
|
||||
mockMvc
|
||||
.perform(post("/owners/{ownerId}/pets/new", TEST_OWNER_ID).param("name", "Betty")
|
||||
.param("type", "hamster")
|
||||
.param("birthDate", "2015-02-12"))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andExpect(view().name("redirect:/owners/{ownerId}"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessCreationFormHasErrors() throws Exception {
|
||||
mockMvc
|
||||
.perform(post("/owners/{ownerId}/pets/new", TEST_OWNER_ID).param("name", "Betty")
|
||||
.param("birthDate", "2015-02-12"))
|
||||
.andExpect(model().attributeHasNoErrors("owner"))
|
||||
.andExpect(model().attributeHasErrors("pet"))
|
||||
.andExpect(model().attributeHasFieldErrors("pet", "type"))
|
||||
.andExpect(model().attributeHasFieldErrorCode("pet", "type", "required"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(view().name("pets/createOrUpdatePetForm"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInitUpdateForm() throws Exception {
|
||||
mockMvc.perform(get("/owners/{ownerId}/pets/{petId}/edit", TEST_OWNER_ID, TEST_PET_ID))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(model().attributeExists("pet"))
|
||||
.andExpect(view().name("pets/createOrUpdatePetForm"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessUpdateFormSuccess() throws Exception {
|
||||
mockMvc
|
||||
.perform(post("/owners/{ownerId}/pets/{petId}/edit", TEST_OWNER_ID, TEST_PET_ID).param("name", "Betty")
|
||||
.param("type", "hamster")
|
||||
.param("birthDate", "2015-02-12"))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andExpect(view().name("redirect:/owners/{ownerId}"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessUpdateFormHasErrors() throws Exception {
|
||||
mockMvc
|
||||
.perform(post("/owners/{ownerId}/pets/{petId}/edit", TEST_OWNER_ID, TEST_PET_ID).param("name", "Betty")
|
||||
.param("birthDate", "2015/02/12"))
|
||||
.andExpect(model().attributeHasNoErrors("owner"))
|
||||
.andExpect(model().attributeHasErrors("pet"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(view().name("pets/createOrUpdatePetForm"));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,228 +0,0 @@
|
|||
/*
|
||||
* 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.service;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
|
||||
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace;
|
||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.samples.petclinic.owner.Owner;
|
||||
import org.springframework.samples.petclinic.owner.OwnerRepository;
|
||||
import org.springframework.samples.petclinic.owner.Pet;
|
||||
import org.springframework.samples.petclinic.owner.PetType;
|
||||
import org.springframework.samples.petclinic.owner.Visit;
|
||||
import org.springframework.samples.petclinic.vet.Vet;
|
||||
import org.springframework.samples.petclinic.vet.VetRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Integration test of the Service and the Repository layer.
|
||||
* <p>
|
||||
* ClinicServiceSpringDataJpaTests subclasses benefit from the following services provided
|
||||
* by the Spring TestContext Framework:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li><strong>Spring IoC container caching</strong> which spares us unnecessary set up
|
||||
* time between test execution.</li>
|
||||
* <li><strong>Dependency Injection</strong> of test fixture instances, meaning that we
|
||||
* don't need to perform application context lookups. See the use of
|
||||
* {@link Autowired @Autowired} on the <code> </code> instance variable, which uses
|
||||
* autowiring <em>by type</em>.
|
||||
* <li><strong>Transaction management</strong>, meaning each test method is executed in
|
||||
* its own transaction, which is automatically rolled back by default. Thus, even if tests
|
||||
* insert or otherwise change database state, there is no need for a teardown or cleanup
|
||||
* script.
|
||||
* <li>An {@link org.springframework.context.ApplicationContext ApplicationContext} is
|
||||
* also inherited and can be used for explicit bean lookup if necessary.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Ken Krebs
|
||||
* @author Rod Johnson
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @author Michael Isvy
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@DataJpaTest(includeFilters = @ComponentScan.Filter(Service.class))
|
||||
// Ensure that if the mysql profile is active we connect to the real database:
|
||||
@AutoConfigureTestDatabase(replace = Replace.NONE)
|
||||
// @TestPropertySource("/application-postgres.properties")
|
||||
class ClinicServiceTests {
|
||||
|
||||
@Autowired
|
||||
protected OwnerRepository owners;
|
||||
|
||||
@Autowired
|
||||
protected VetRepository vets;
|
||||
|
||||
Pageable pageable;
|
||||
|
||||
@Test
|
||||
void shouldFindOwnersByLastName() {
|
||||
Page<Owner> owners = this.owners.findByLastName("Davis", pageable);
|
||||
assertThat(owners).hasSize(2);
|
||||
|
||||
owners = this.owners.findByLastName("Daviss", pageable);
|
||||
assertThat(owners).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindSingleOwnerWithPet() {
|
||||
Owner owner = this.owners.findById(1);
|
||||
assertThat(owner.getLastName()).startsWith("Franklin");
|
||||
assertThat(owner.getPets()).hasSize(1);
|
||||
assertThat(owner.getPets().get(0).getType()).isNotNull();
|
||||
assertThat(owner.getPets().get(0).getType().getName()).isEqualTo("cat");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Transactional
|
||||
void shouldInsertOwner() {
|
||||
Page<Owner> owners = this.owners.findByLastName("Schultz", pageable);
|
||||
int found = (int) owners.getTotalElements();
|
||||
|
||||
Owner owner = new Owner();
|
||||
owner.setFirstName("Sam");
|
||||
owner.setLastName("Schultz");
|
||||
owner.setAddress("4, Evans Street");
|
||||
owner.setCity("Wollongong");
|
||||
owner.setTelephone("4444444444");
|
||||
this.owners.save(owner);
|
||||
assertThat(owner.getId().longValue()).isNotEqualTo(0);
|
||||
|
||||
owners = this.owners.findByLastName("Schultz", pageable);
|
||||
assertThat(owners.getTotalElements()).isEqualTo(found + 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Transactional
|
||||
void shouldUpdateOwner() {
|
||||
Owner owner = this.owners.findById(1);
|
||||
String oldLastName = owner.getLastName();
|
||||
String newLastName = oldLastName + "X";
|
||||
|
||||
owner.setLastName(newLastName);
|
||||
this.owners.save(owner);
|
||||
|
||||
// retrieving new name from database
|
||||
owner = this.owners.findById(1);
|
||||
assertThat(owner.getLastName()).isEqualTo(newLastName);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindAllPetTypes() {
|
||||
Collection<PetType> petTypes = this.owners.findPetTypes();
|
||||
|
||||
PetType petType1 = EntityUtils.getById(petTypes, PetType.class, 1);
|
||||
assertThat(petType1.getName()).isEqualTo("cat");
|
||||
PetType petType4 = EntityUtils.getById(petTypes, PetType.class, 4);
|
||||
assertThat(petType4.getName()).isEqualTo("snake");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Transactional
|
||||
void shouldInsertPetIntoDatabaseAndGenerateId() {
|
||||
Owner owner6 = this.owners.findById(6);
|
||||
int found = owner6.getPets().size();
|
||||
|
||||
Pet pet = new Pet();
|
||||
pet.setName("bowser");
|
||||
Collection<PetType> types = this.owners.findPetTypes();
|
||||
pet.setType(EntityUtils.getById(types, PetType.class, 2));
|
||||
pet.setBirthDate(LocalDate.now());
|
||||
owner6.addPet(pet);
|
||||
assertThat(owner6.getPets().size()).isEqualTo(found + 1);
|
||||
|
||||
this.owners.save(owner6);
|
||||
|
||||
owner6 = this.owners.findById(6);
|
||||
assertThat(owner6.getPets().size()).isEqualTo(found + 1);
|
||||
// checks that id has been generated
|
||||
pet = owner6.getPet("bowser");
|
||||
assertThat(pet.getId()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Transactional
|
||||
void shouldUpdatePetName() throws Exception {
|
||||
Owner owner6 = this.owners.findById(6);
|
||||
Pet pet7 = owner6.getPet(7);
|
||||
String oldName = pet7.getName();
|
||||
|
||||
String newName = oldName + "X";
|
||||
pet7.setName(newName);
|
||||
this.owners.save(owner6);
|
||||
|
||||
owner6 = this.owners.findById(6);
|
||||
pet7 = owner6.getPet(7);
|
||||
assertThat(pet7.getName()).isEqualTo(newName);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindVets() {
|
||||
Collection<Vet> vets = this.vets.findAll();
|
||||
|
||||
Vet vet = EntityUtils.getById(vets, Vet.class, 3);
|
||||
assertThat(vet.getLastName()).isEqualTo("Douglas");
|
||||
assertThat(vet.getNrOfSpecialties()).isEqualTo(2);
|
||||
assertThat(vet.getSpecialties().get(0).getName()).isEqualTo("dentistry");
|
||||
assertThat(vet.getSpecialties().get(1).getName()).isEqualTo("surgery");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Transactional
|
||||
void shouldAddNewVisitForPet() {
|
||||
Owner owner6 = this.owners.findById(6);
|
||||
Pet pet7 = owner6.getPet(7);
|
||||
int found = pet7.getVisits().size();
|
||||
Visit visit = new Visit();
|
||||
visit.setDescription("test");
|
||||
|
||||
owner6.addVisit(pet7.getId(), visit);
|
||||
this.owners.save(owner6);
|
||||
|
||||
owner6 = this.owners.findById(6);
|
||||
|
||||
assertThat(pet7.getVisits()) //
|
||||
.hasSize(found + 1) //
|
||||
.allMatch(value -> value.getId() != null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindVisitsByPetId() throws Exception {
|
||||
Owner owner6 = this.owners.findById(6);
|
||||
Pet pet7 = owner6.getPet(7);
|
||||
Collection<Visit> visits = pet7.getVisits();
|
||||
|
||||
assertThat(visits) //
|
||||
.hasSize(2) //
|
||||
.element(0)
|
||||
.extracting(Visit::getDate)
|
||||
.isNotNull();
|
||||
}
|
||||
|
||||
}
|
|
@ -9,12 +9,12 @@
|
|||
<collectionProp name="Arguments.arguments">
|
||||
<elementProp name="PETCLINIC_HOST" elementType="Argument">
|
||||
<stringProp name="Argument.name">PETCLINIC_HOST</stringProp>
|
||||
<stringProp name="Argument.value">localhost</stringProp>
|
||||
<stringProp name="Argument.value">a25c892af46df47589102bdef28fda12-1374796034.eu-west-1.elb.amazonaws.com</stringProp>
|
||||
<stringProp name="Argument.metadata">=</stringProp>
|
||||
</elementProp>
|
||||
<elementProp name="PETCLINIC_PORT" elementType="Argument">
|
||||
<stringProp name="Argument.name">PETCLINIC_PORT</stringProp>
|
||||
<stringProp name="Argument.value">8080</stringProp>
|
||||
<stringProp name="Argument.value">80</stringProp>
|
||||
<stringProp name="Argument.metadata">=</stringProp>
|
||||
</elementProp>
|
||||
<elementProp name="CONTEXT_WEB" elementType="Argument">
|
||||
|
|
Loading…
Reference in a new issue