commit c6920bb4f772f335e9b6204460b201c19fb7e2ec Author: Chris Dombroski Date: Thu Nov 21 20:22:38 2024 -0500 Import spring petclinic backend diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..baf9714 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake path:flake; diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..097f9f9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# Linux start script should use lf +/gradlew text eol=lf + +# These are Windows script files and should use crlf +*.bat text eol=crlf + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1bb72fa --- /dev/null +++ b/.gitignore @@ -0,0 +1,190 @@ +.direnv/ +.idea/ +.vscode/ + +# Created by https://www.toptal.com/developers/gitignore/api/intellij,linux,node,gradle +# Edit at https://www.toptal.com/developers/gitignore?templates=intellij,linux,node,gradle + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +### Node Patch ### +# Serverless Webpack directories +.webpack/ + +# Optional stylelint cache + +# SvelteKit build / generate output +.svelte-kit + +### Gradle ### +.gradle +**/build/ +!src/**/build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Avoid ignore Gradle wrappper properties +!gradle-wrapper.properties + +# Cache of project +.gradletasknamecache + +# Eclipse Gradle plugin generated files +# Eclipse Core +.project +# JDT-specific (Eclipse Java Development Tools) +.classpath + +### Gradle Patch ### +# Java heap dump +*.hprof + +# End of https://www.toptal.com/developers/gitignore/api/intellij,linux,node,gradle diff --git a/backend/.editorconfig b/backend/.editorconfig new file mode 100644 index 0000000..8d67bc7 --- /dev/null +++ b/backend/.editorconfig @@ -0,0 +1,12 @@ +# top-most EditorConfig file +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space + +[*.{java,xml}] +indent_size = 4 +trim_trailing_whitespace = true diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..ad07b8f --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,13 @@ +target/* +.settings/* +.classpath +.project +.idea +*.iml +/target + +generated/ + +# Easier branch switching +springboot-petclinic-client/ +springboot-petclinic-server/ diff --git a/backend/LICENSE.txt b/backend/LICENSE.txt new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/backend/LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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 + + http://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. diff --git a/backend/build.gradle.kts b/backend/build.gradle.kts new file mode 100644 index 0000000..8dffcf0 --- /dev/null +++ b/backend/build.gradle.kts @@ -0,0 +1,120 @@ +plugins { + java + id("org.springframework.boot") version "3.3.6" + id("io.spring.dependency-management") version "1.1.6" + id("org.openapi.generator") version "7.10.0" + idea + jacoco +} + +group = "org.springframework.samples" + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + +repositories { + mavenCentral() +} + +dependencies { + implementation("org.springframework.boot:spring-boot-starter-actuator") + implementation("org.springframework.boot:spring-boot-starter-cache") + implementation("org.springframework.boot:spring-boot-starter-data-jpa") + implementation("org.springframework.boot:spring-boot-starter-jdbc") + implementation("org.springframework.boot:spring-boot-starter-security") + implementation("org.springframework.boot:spring-boot-starter-validation") + implementation("org.springframework.boot:spring-boot-starter-web") + implementation("org.springframework.data:spring-data-jdbc-core:1.2.1.RELEASE") + implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0") + implementation("org.mapstruct:mapstruct:${project.property("mapstructVersion")}") +// implementation("org.openapitools:jackson-databind-nullable:0.2.6") + annotationProcessor("org.mapstruct:mapstruct-processor:${project.property("mapstructVersion")}") + runtimeOnly("org.hsqldb:hsqldb") + testImplementation("org.springframework.boot:spring-boot-starter-test") + testImplementation("org.springframework.security:spring-security-test") + testImplementation("com.jayway.jsonpath:json-path") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") +} + +openApiGenerate { + inputSpec.set("${projectDir}/src/main/resources/openapi.yml") + generatorName.set("spring") + library.set("spring-boot") + modelNameSuffix.set("Dto") + apiPackage.set("org.springframework.samples.petclinic.rest.api") + modelPackage.set("org.springframework.samples.petclinic.rest.dto") + supportingFilesConstrainedTo.set(listOf("ApiUtil.java")) + globalProperties.set( + mapOf( + "apis" to "", + "models" to "" + ) + ) + configOptions.set( + mutableMapOf( + "verbose" to "true", + "interfaceOnly" to "true", + "performBeanValidation" to "true", + "dateLibrary" to "java8", + "useSpringBoot3" to "true", + "openApiNullable" to "false", + "serializationLibrary" to "jackson", + "documentationProvider" to "springdoc", + ) + ) +} + +tasks.jacocoTestReport { + dependsOn(tasks.test) // tests are required to run before generating the report + classDirectories.setFrom( + files(classDirectories.files.map { + fileTree(it) { + exclude( + "**/org/springframework/samples/petclinic/rest/dto/**", + "**/org/springframework/samples/petclinic/rest/api/**" + ) + } + }) + ) +} + +tasks.jacocoTestCoverageVerification { + violationRules { + rule { + element = "BUNDLE" + limit { + counter = "LINE" + value = "COVEREDRATIO" + minimum = "0.85".toBigDecimal() + } + limit { + counter = "BRANCH" + value = "COVEREDRATIO" + minimum = "0.66".toBigDecimal() + } + } + } +} + +tasks.withType { + useJUnitPlatform() + finalizedBy(tasks.jacocoTestReport) // report is always generated after tests run + configure { + } +} + +java.sourceSets["main"].java { + srcDir(layout.buildDirectory.dir("generate-resources/main/src/main/java")) +} + +tasks.withType { + dependsOn(tasks.openApiGenerate) + options.compilerArgs = listOf( + "-Amapstruct.suppressGeneratorTimestamp=true", + "-Amapstruct.suppressGeneratorVersionInfoComment=true", + "-Amapstruct.defaultComponentModel=spring" + ) +} diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml new file mode 100644 index 0000000..caa9b49 --- /dev/null +++ b/backend/docker-compose.yml @@ -0,0 +1,26 @@ +services: + mysql: + image: mysql:8.4 + command: --mysql-native-password=ON + ports: + - "3306:3306" + environment: + - MYSQL_ROOT_PASSWORD= + - MYSQL_ALLOW_EMPTY_PASSWORD=true + - MYSQL_USER=petclinic + - MYSQL_PASSWORD=petclinic + - MYSQL_DATABASE=petclinic + volumes: + - "./conf.d:/etc/mysql/conf.d:ro" + profiles: + - mysql + postgres: + image: postgres:16.3 + ports: + - "5432:5432" + environment: + - POSTGRES_PASSWORD=petclinic + - POSTGRES_USER=petclinic + - POSTGRES_DB=petclinic + profiles: + - postgres diff --git a/backend/petclinic-ermodel.png b/backend/petclinic-ermodel.png new file mode 100644 index 0000000..6c790fb Binary files /dev/null and b/backend/petclinic-ermodel.png differ diff --git a/backend/readme.md b/backend/readme.md new file mode 100644 index 0000000..3d82659 --- /dev/null +++ b/backend/readme.md @@ -0,0 +1,223 @@ +# REST version of Spring PetClinic Sample Application (spring-framework-petclinic extend ) + +[![Java Build Status](https://github.com/spring-petclinic/spring-petclinic-rest/actions/workflows/maven-build.yml/badge.svg)](https://github.com/spring-petclinic/spring-petclinic-rest/actions/workflows/maven-build.yml) +[![Docker Build Status](https://github.com/spring-petclinic/spring-petclinic-rest/actions/workflows/docker-build.yml/badge.svg)](https://github.com/spring-petclinic/spring-petclinic-rest/actions/workflows/docker-build.yml) + +This backend version of the Spring Petclinic application only provides a REST API. **There is no UI**. +The [spring-petclinic-angular project](https://github.com/spring-petclinic/spring-petclinic-angular) is a Angular front-end application which consumes the REST API. + +## Understanding the Spring Petclinic application with a few diagrams + +[See the presentation of the Spring Petclinic Framework version](http://fr.slideshare.net/AntoineRey/spring-framework-petclinic-sample-application) + +### Petclinic ER Model + +![alt petclinic-ermodel](petclinic-ermodel.png) + +## Running Petclinic locally + +### With Maven command line +```sh +git clone https://github.com/spring-petclinic/spring-petclinic-rest.git +cd spring-petclinic-rest +./mvnw spring-boot:run +``` + +### With Docker +```sh +docker run -p 9966:9966 springcommunity/spring-petclinic-rest +``` + +You can then access petclinic here: [http://localhost:9966/petclinic/](http://localhost:9966/petclinic/) + +There is an actuator health check route as well: +* [http://localhost:9966/petclinic/actuator/health](http://localhost:9966/petclinic/actuator/health) + +## OpenAPI REST API documentation + +You can reach the Swagger UI with this URL (after application start): +[http://localhost:9966/petclinic/](http://localhost:9966/petclinic/swagger-ui.html). + +You then can get the Open API description reaching this URL: [localhost:9966/petclinic/v3/api-docs](localhost:9966/petclinic/v3/api-docs). + +## Screenshot of the Angular client + +See its repository here: https://github.com/spring-petclinic/spring-petclinic-angular + +spring-petclinic-angular2 + +## In case you find a bug/suggested improvement for Spring Petclinic +Our issue tracker is available here: https://github.com/spring-petclinic/spring-petclinic-rest/issues + + +## Database configuration + +In its default configuration, Petclinic uses an in-memory database (HSQLDB) which gets populated at startup with data. + +A similar setup is provided for MySQL and PostgreSQL if a persistent database configuration is needed. + +Note that whenever the database type changes, the app needs to run with a different profile: `spring.profiles.active=mysql` for MySQL or `spring.profiles.active=postgres` for PostgreSQL. +See the [Spring Boot documentation](https://docs.spring.io/spring-boot/how-to/properties-and-configuration.html#howto.properties-and-configuration.set-active-spring-profiles) for more detail on how to set the active profile. +You can also change profile defined in the `application.properties` file. +For MySQL database, it is needed to change param `hsqldb` to `mysql` in the following line of `application.properies` file: +```properties +spring.profiles.active=hsqldb,spring-data-jpa +``` + +You can start MySQL or PostgreSQL locally with whatever installer works for your OS or use docker: + +```bash +docker run -e MYSQL_USER=petclinic -e MYSQL_PASSWORD=petclinic -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=petclinic -p 3306:3306 mysql:8.4 +``` + +or + +```bash +docker run -e POSTGRES_USER=petclinic -e POSTGRES_PASSWORD=petclinic -e POSTGRES_DB=petclinic -p 5432:5432 postgres:16.3 +``` + +Further documentation is provided for [MySQL](https://github.com/spring-projects/spring-petclinic/blob/main/src/main/resources/db/mysql/petclinic_db_setup_mysql.txt) +and [PostgreSQL](https://github.com/spring-projects/spring-petclinic/blob/main/src/main/resources/db/postgres/petclinic_db_setup_postgres.txt). + +Instead of vanilla `docker` you can also use the provided `docker-compose.yml` file to start the database containers. Each one has a profile just like the Spring profile: + +```bash +docker-compose --profile mysql up +``` + +or + +```bash +docker-compose --profile postgres up +``` + + +## API First Approach + +This API is built following some [API First approach principles](https://swagger.io/resources/articles/adopting-an-api-first-approach/). + +It is specified through the [OpenAPI](https://oai.github.io/Documentation/). +It is specified in this [file](./src/main/resources/openapi.yml). + +Some of the required classes are generated during the build time. +Here are the generated file types: +* DTOs +* API template interfaces specifying methods to override in the controllers + +To see how to get them generated you can read the next chapter. + +## Generated code + +Some of the required classes are generated during the build time using maven or any IDE (e.g., IntelliJ Idea or Eclipse). + +All of these classes are generated into the ``target/generated-sources`` folder. + +Here is a list of the generated packages and the corresponding tooling: + +| Package name | Tool | +|------------------------------------------------|------------------| +| org.springframework.samples.petclinic.mapper | [MapStruct](https://mapstruct.org/) | +| org.springframework.samples.petclinic.rest.dto | [OpenAPI Generator maven plugin](https://github.com/OpenAPITools/openapi-generator/) | + + +To get both, you have to run the following command: + +```jshelllanguage +mvn clean install +``` + +## Security configuration +In its default configuration, Petclinic doesn't have authentication and authorization enabled. + +### Basic Authentication +In order to use the basic authentication functionality, turn in on from the `application.properties` file +```properties +petclinic.security.enable=true +``` +This will secure all APIs and in order to access them, basic authentication is required. +Apart from authentication, APIs also require authorization. This is done via roles that a user can have. +The existing roles are listed below with the corresponding permissions + +* `OWNER_ADMIN` -> `OwnerController`, `PetController`, `PetTypeController` (`getAllPetTypes` and `getPetType`), `VisitController` +* `VET_ADMIN` -> `PetTypeController`, `SpecialityController`, `VetController` +* `ADMIN` -> `UserController` + +There is an existing user with the username `admin` and password `admin` that has access to all APIs. + In order to add a new user, please make `POST /api/users` request with the following payload: + +```json +{ + "username": "secondAdmin", + "password": "password", + "enabled": true, + "roles": [ + { "name" : "OWNER_ADMIN" } + ] +} +``` + +## Working with Petclinic in Eclipse/STS + +### prerequisites +The following items should be installed in your system: +* Maven 3 (https://maven.apache.org/install.html) +* git command line tool (https://help.github.com/articles/set-up-git) +* Eclipse with the m2e plugin (m2e is installed by default when using the STS (http://www.springsource.org/sts) distribution of Eclipse) + +Note: when m2e is available, there is an m2 icon in Help -> About dialog. +If m2e is not there, just follow the install process here: http://eclipse.org/m2e/download/ +* Eclipse with the [mapstruct plugin](https://mapstruct.org/documentation/ide-support/) installed. + +### Steps: + +1) In the command line +```sh +git clone https://github.com/spring-petclinic/spring-petclinic-rest.git +``` +2) Inside Eclipse +``` +File -> Import -> Maven -> Existing Maven project +``` + + +## Looking for something in particular? + +| Layer | Source | +|--|--| +| REST API controllers | [REST folder](src/main/java/org/springframework/samples/petclinic/rest) | +| Service | [ClinicServiceImpl.java](src/main/java/org/springframework/samples/petclinic/service/ClinicServiceImpl.java) | +| JDBC | [jdbc folder](src/main/java/org/springframework/samples/petclinic/repository/jdbc) | +| JPA | [jpa folder](src/main/java/org/springframework/samples/petclinic/repository/jpa) | +| Spring Data JPA | [springdatajpa folder](src/main/java/org/springframework/samples/petclinic/repository/springdatajpa) | +| Tests | [AbstractClinicServiceTests.java](src/test/java/org/springframework/samples/petclinic/service/clinicService/AbstractClinicServiceTests.java) | + + +## Publishing a Docker image + +This application uses [Google Jib]([https://github.com/GoogleContainerTools/jib) to build an optimized Docker image into the [Docker Hub](https://cloud.docker.com/u/springcommunity/repository/docker/springcommunity/spring-petclinic-rest/) repository. +The [pom.xml](pom.xml) has been configured to publish the image with a the `springcommunity/spring-petclinic-rest`image name. + +Command line to run: +```sh +mvn compile jib:build -X -DjibSerialize=true -Djib.to.auth.username=xxx -Djib.to.auth.password=xxxxx +``` + +## Interesting Spring Petclinic forks + +The Spring Petclinic master branch in the main [spring-projects](https://github.com/spring-projects/spring-petclinic) +GitHub org is the "canonical" implementation, currently based on Spring Boot and Thymeleaf. + +This [spring-petclinic-rest](https://github.com/spring-petclinic/spring-petclinic-rest/) project is one of the [several forks](https://spring-petclinic.github.io/docs/forks.html) +hosted in a special GitHub org: [spring-petclinic](https://github.com/spring-petclinic). +If you have a special interest in a different technology stack +that could be used to implement the Pet Clinic then please join the community there. + + +# Contributing + +The [issue tracker](https://github.com/spring-petclinic/spring-petclinic-rest/issues) is the preferred channel for bug reports, features requests and submitting pull requests. + +For pull requests, editor preferences are available in the [editor config](https://github.com/spring-petclinic/spring-petclinic-rest/blob/master/.editorconfig) for easy use in common text editors. Read more and download plugins at . + + + diff --git a/backend/src/main/java/org/springframework/samples/petclinic/PetClinicApplication.java b/backend/src/main/java/org/springframework/samples/petclinic/PetClinicApplication.java new file mode 100644 index 0000000..b41f2f4 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/PetClinicApplication.java @@ -0,0 +1,14 @@ +package org.springframework.samples.petclinic; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; + +@SpringBootApplication +public class PetClinicApplication extends SpringBootServletInitializer { + + public static void main(String[] args) { + SpringApplication.run(PetClinicApplication.class, args); + } +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/config/SwaggerConfig.java b/backend/src/main/java/org/springframework/samples/petclinic/config/SwaggerConfig.java new file mode 100755 index 0000000..44fdae4 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/config/SwaggerConfig.java @@ -0,0 +1,66 @@ +/* + * Copyright 2016 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 + * + * http://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.config; + + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.Collections; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Contact; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.info.License; + +/** + * Java config for Springfox swagger documentation plugin + * + * @author Vitaliy Fedoriv + */ +@Configuration +public class SwaggerConfig { + + @Bean + OpenAPI customOpenAPI() { + + return new OpenAPI().components(new Components()).info(new Info() + .title("REST Petclinic backend Api Documentation").version("1.0") + .termsOfService("Petclinic backend terms of service") + .description( + "This is REST API documentation of the Spring Petclinic backend. If authentication is enabled, when calling the APIs use admin/admin") + .license(swaggerLicense()).contact(swaggerContact())); + } + + private Contact swaggerContact() { + Contact petclinicContact = new Contact(); + petclinicContact.setName("Vitaliy Fedoriv"); + petclinicContact.setEmail("vitaliy.fedoriv@gmail.com"); + petclinicContact.setUrl("https://github.com/spring-petclinic/spring-petclinic-rest"); + return petclinicContact; + } + + private License swaggerLicense() { + License petClinicLicense = new License(); + petClinicLicense.setName("Apache 2.0"); + petClinicLicense.setUrl("http://www.apache.org/licenses/LICENSE-2.0"); + petClinicLicense.setExtensions(Collections.emptyMap()); + return petClinicLicense; + } + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/mapper/OwnerMapper.java b/backend/src/main/java/org/springframework/samples/petclinic/mapper/OwnerMapper.java new file mode 100755 index 0000000..5acb8a1 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/mapper/OwnerMapper.java @@ -0,0 +1,29 @@ +package org.springframework.samples.petclinic.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.springframework.samples.petclinic.model.Owner; +import org.springframework.samples.petclinic.rest.dto.OwnerDto; +import org.springframework.samples.petclinic.rest.dto.OwnerFieldsDto; + +import java.util.Collection; +import java.util.List; + +/** + * Maps Owner & OwnerDto using Mapstruct + */ +@Mapper(uses = PetMapper.class) +public interface OwnerMapper { + + OwnerDto toOwnerDto(Owner owner); + + Owner toOwner(OwnerDto ownerDto); + + @Mapping(target = "id", ignore = true) + @Mapping(target = "pets", ignore = true) + Owner toOwner(OwnerFieldsDto ownerDto); + + List toOwnerDtoCollection(Collection ownerCollection); + + Collection toOwners(Collection ownerDtos); +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/mapper/PetMapper.java b/backend/src/main/java/org/springframework/samples/petclinic/mapper/PetMapper.java new file mode 100755 index 0000000..ae16100 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/mapper/PetMapper.java @@ -0,0 +1,39 @@ +package org.springframework.samples.petclinic.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.springframework.samples.petclinic.model.Pet; +import org.springframework.samples.petclinic.model.PetType; +import org.springframework.samples.petclinic.rest.dto.PetDto; +import org.springframework.samples.petclinic.rest.dto.PetFieldsDto; +import org.springframework.samples.petclinic.rest.dto.PetTypeDto; + +import java.util.Collection; + +/** + * Map Pet & PetDto using mapstruct + */ +@Mapper(uses = VisitMapper.class) +public interface PetMapper { + + @Mapping(source = "owner.id", target = "ownerId") + PetDto toPetDto(Pet pet); + + Collection toPetsDto(Collection pets); + + Collection toPets(Collection pets); + + @Mapping(source = "ownerId", target = "owner.id") + Pet toPet(PetDto petDto); + + @Mapping(target = "id", ignore = true) + @Mapping(target = "owner", ignore = true) + @Mapping(target = "visits", ignore = true) + Pet toPet(PetFieldsDto petFieldsDto); + + PetTypeDto toPetTypeDto(PetType petType); + + PetType toPetType(PetTypeDto petTypeDto); + + Collection toPetTypeDtos(Collection petTypes); +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/mapper/PetTypeMapper.java b/backend/src/main/java/org/springframework/samples/petclinic/mapper/PetTypeMapper.java new file mode 100644 index 0000000..efb6d60 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/mapper/PetTypeMapper.java @@ -0,0 +1,27 @@ +package org.springframework.samples.petclinic.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.springframework.samples.petclinic.model.PetType; +import org.springframework.samples.petclinic.rest.dto.PetTypeDto; +import org.springframework.samples.petclinic.rest.dto.PetTypeFieldsDto; + +import java.util.Collection; +import java.util.List; + +/** + * Map PetType & PetTypeDto using mapstruct + */ +@Mapper +public interface PetTypeMapper { + + PetType toPetType(PetTypeDto petTypeDto); + + @Mapping(target = "id", ignore = true) + PetType toPetType(PetTypeFieldsDto petTypeFieldsDto); + + PetTypeDto toPetTypeDto(PetType petType); + PetTypeFieldsDto toPetTypeFieldsDto(PetType petType); + + List toPetTypeDtos(Collection petTypes); +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/mapper/SpecialtyMapper.java b/backend/src/main/java/org/springframework/samples/petclinic/mapper/SpecialtyMapper.java new file mode 100644 index 0000000..c89334d --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/mapper/SpecialtyMapper.java @@ -0,0 +1,22 @@ +package org.springframework.samples.petclinic.mapper; + +import org.mapstruct.Mapper; +import org.springframework.samples.petclinic.rest.dto.SpecialtyDto; +import org.springframework.samples.petclinic.model.Specialty; + +import java.util.Collection; + +/** + * Map Specialty & SpecialtyDto using mapstruct + */ +@Mapper +public interface SpecialtyMapper { + Specialty toSpecialty(SpecialtyDto specialtyDto); + + SpecialtyDto toSpecialtyDto(Specialty specialty); + + Collection toSpecialtyDtos(Collection specialties); + + Collection toSpecialtys(Collection specialties); + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/mapper/UserMapper.java b/backend/src/main/java/org/springframework/samples/petclinic/mapper/UserMapper.java new file mode 100644 index 0000000..2b5aea0 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/mapper/UserMapper.java @@ -0,0 +1,32 @@ +package org.springframework.samples.petclinic.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.springframework.samples.petclinic.model.Role; +import org.springframework.samples.petclinic.model.User; +import org.springframework.samples.petclinic.rest.dto.RoleDto; +import org.springframework.samples.petclinic.rest.dto.UserDto; + +import java.util.Collection; + +/** + * Map User/Role & UserDto/RoleDto using mapstruct + */ +@Mapper +public interface UserMapper { + + @Mapping(target = "id", ignore = true) + @Mapping(target = "user", ignore = true) + Role toRole(RoleDto roleDto); + + RoleDto toRoleDto(Role role); + + Collection toRoleDtos(Collection roles); + + User toUser(UserDto userDto); + + UserDto toUserDto(User user); + + Collection toRoles(Collection roleDtos); + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/mapper/VetMapper.java b/backend/src/main/java/org/springframework/samples/petclinic/mapper/VetMapper.java new file mode 100644 index 0000000..fdcab6c --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/mapper/VetMapper.java @@ -0,0 +1,24 @@ +package org.springframework.samples.petclinic.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.springframework.samples.petclinic.model.Vet; +import org.springframework.samples.petclinic.rest.dto.VetDto; +import org.springframework.samples.petclinic.rest.dto.VetFieldsDto; + +import java.util.Collection; + +/** + * Map Vet & VetoDto using mapstruct + */ +@Mapper(uses = SpecialtyMapper.class) +public interface VetMapper { + Vet toVet(VetDto vetDto); + + @Mapping(target = "id", ignore = true) + Vet toVet(VetFieldsDto vetFieldsDto); + + VetDto toVetDto(Vet vet); + + Collection toVetDtos(Collection vets); +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/mapper/VisitMapper.java b/backend/src/main/java/org/springframework/samples/petclinic/mapper/VisitMapper.java new file mode 100644 index 0000000..98dc2db --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/mapper/VisitMapper.java @@ -0,0 +1,28 @@ +package org.springframework.samples.petclinic.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.springframework.samples.petclinic.model.Visit; +import org.springframework.samples.petclinic.rest.dto.VisitDto; +import org.springframework.samples.petclinic.rest.dto.VisitFieldsDto; + +import java.util.Collection; + +/** + * Map Visit & VisitDto using mapstruct + */ +@Mapper(uses = PetMapper.class) +public interface VisitMapper { + @Mapping(source = "petId", target = "pet.id") + Visit toVisit(VisitDto visitDto); + + @Mapping(target = "id", ignore = true) + @Mapping(target = "pet", ignore = true) + Visit toVisit(VisitFieldsDto visitFieldsDto); + + @Mapping(source = "pet.id", target = "petId") + VisitDto toVisitDto(Visit visit); + + Collection toVisitsDto(Collection visits); + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/model/BaseEntity.java b/backend/src/main/java/org/springframework/samples/petclinic/model/BaseEntity.java new file mode 100644 index 0000000..80bf755 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/model/BaseEntity.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2013 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 + * + * http://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.model; + +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.MappedSuperclass; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +/** + * Simple JavaBean domain object with an id property. Used as a base class for objects needing this property. + * + * @author Ken Krebs + * @author Juergen Hoeller + */ +@MappedSuperclass +public class BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + protected Integer id; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + @JsonIgnore + public boolean isNew() { + return this.id == null; + } + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/model/NamedEntity.java b/backend/src/main/java/org/springframework/samples/petclinic/model/NamedEntity.java new file mode 100644 index 0000000..0ae1953 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/model/NamedEntity.java @@ -0,0 +1,51 @@ +/* + * Copyright 2002-2013 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 + * + * http://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.model; + +import jakarta.persistence.Column; +import jakarta.persistence.MappedSuperclass; + +import jakarta.validation.constraints.NotEmpty; + + +/** + * Simple JavaBean domain object adds a name property to BaseEntity. Used as a base class for objects + * needing these properties. + * + * @author Ken Krebs + * @author Juergen Hoeller + */ +@MappedSuperclass +public class NamedEntity extends BaseEntity { + + @Column(name = "name") + @NotEmpty + private String name; + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return this.getName(); + } + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/model/Owner.java b/backend/src/main/java/org/springframework/samples/petclinic/model/Owner.java new file mode 100644 index 0000000..8cf3a6d --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/model/Owner.java @@ -0,0 +1,153 @@ +/* + * Copyright 2002-2013 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 + * + * http://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.model; + +import org.springframework.beans.support.MutableSortDefinition; +import org.springframework.beans.support.PropertyComparator; +import org.springframework.core.style.ToStringCreator; + +import jakarta.persistence.*; +import jakarta.validation.constraints.Digits; +import jakarta.validation.constraints.NotEmpty; +import java.util.*; + + +/** + * Simple JavaBean domain object representing an owner. + * + * @author Ken Krebs + * @author Juergen Hoeller + * @author Sam Brannen + * @author Michael Isvy + */ +@Entity +@Table(name = "owners") +public class Owner extends Person { + @Column(name = "address") + @NotEmpty + private String address; + + @Column(name = "city") + @NotEmpty + private String city; + + @Column(name = "telephone") + @NotEmpty + @Digits(fraction = 0, integer = 10) + private String telephone; + + @OneToMany(cascade = CascadeType.ALL, mappedBy = "owner", fetch = FetchType.EAGER) + private Set pets; + + + public String getAddress() { + return this.address; + } + + public void setAddress(String address) { + this.address = address; + } + + public String getCity() { + return this.city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getTelephone() { + return this.telephone; + } + + public void setTelephone(String telephone) { + this.telephone = telephone; + } + + protected Set getPetsInternal() { + if (this.pets == null) { + this.pets = new HashSet<>(); + } + return this.pets; + } + + protected void setPetsInternal(Set pets) { + this.pets = pets; + } + + public List getPets() { + List sortedPets = new ArrayList<>(getPetsInternal()); + PropertyComparator.sort(sortedPets, new MutableSortDefinition("name", true, true)); + return Collections.unmodifiableList(sortedPets); + } + + public void setPets(List pets) { + this.pets = new HashSet<>(pets); + } + + public void addPet(Pet pet) { + getPetsInternal().add(pet); + pet.setOwner(this); + } + + /** + * Return the Pet with the given name, or null if none found for this Owner. + * + * @param name to test + * @return true if pet name is already in use + */ + public Pet getPet(String name) { + return getPet(name, false); + } + + /** + * Return the Pet with the given name, or null if none found for this Owner. + * + * @param name to test + * @return true if pet name is already in use + */ + public Pet getPet(String name, boolean ignoreNew) { + name = name.toLowerCase(); + for (Pet pet : getPetsInternal()) { + if (!ignoreNew || !pet.isNew()) { + String compName = pet.getName(); + compName = compName.toLowerCase(); + if (compName.equals(name)) { + return pet; + } + } + } + return null; + } + + public Pet getPet(Integer petId) { + return getPetsInternal().stream().filter(p -> p.getId().equals(petId)).findFirst().orElse(null); + } + + @Override + public String toString() { + return new ToStringCreator(this) + + .append("id", this.getId()) + .append("new", this.isNew()) + .append("lastName", this.getLastName()) + .append("firstName", this.getFirstName()) + .append("address", this.address) + .append("city", this.city) + .append("telephone", this.telephone) + .toString(); + } +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/model/Person.java b/backend/src/main/java/org/springframework/samples/petclinic/model/Person.java new file mode 100644 index 0000000..6257f2e --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/model/Person.java @@ -0,0 +1,56 @@ +/* + * Copyright 2002-2013 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 + * + * http://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.model; + +import jakarta.persistence.Column; +import jakarta.persistence.MappedSuperclass; + +import jakarta.validation.constraints.NotEmpty; + +/** + * Simple JavaBean domain object representing an person. + * + * @author Ken Krebs + */ +@MappedSuperclass +public class Person extends BaseEntity { + + @Column(name = "first_name") + @NotEmpty + protected String firstName; + + @Column(name = "last_name") + @NotEmpty + protected String lastName; + + public String getFirstName() { + return this.firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return this.lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/model/Pet.java b/backend/src/main/java/org/springframework/samples/petclinic/model/Pet.java new file mode 100644 index 0000000..f5d3402 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/model/Pet.java @@ -0,0 +1,102 @@ +/* + * Copyright 2002-2013 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 + * + * http://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.model; + +import org.springframework.beans.support.MutableSortDefinition; +import org.springframework.beans.support.PropertyComparator; +import org.springframework.format.annotation.DateTimeFormat; + +import jakarta.persistence.*; +import java.time.LocalDate; +import java.util.*; + + +/** + * Simple business object representing a pet. + * + * @author Ken Krebs + * @author Juergen Hoeller + * @author Sam Brannen + */ +@Entity +@Table(name = "pets") +public class Pet extends NamedEntity { + + @Column(name = "birth_date", columnDefinition = "DATE") + private LocalDate birthDate; + + @ManyToOne + @JoinColumn(name = "type_id") + private PetType type; + + @ManyToOne + @JoinColumn(name = "owner_id") + private Owner owner; + + @OneToMany(cascade = CascadeType.ALL, mappedBy = "pet", fetch = FetchType.EAGER) + private Set visits; + + public LocalDate getBirthDate() { + return this.birthDate; + } + + public void setBirthDate(LocalDate birthDate) { + this.birthDate = birthDate; + } + + public PetType getType() { + return this.type; + } + + public void setType(PetType type) { + this.type = type; + } + + public Owner getOwner() { + return this.owner; + } + + public void setOwner(Owner owner) { + this.owner = owner; + } + + protected Set getVisitsInternal() { + if (this.visits == null) { + this.visits = new HashSet<>(); + } + return this.visits; + } + + protected void setVisitsInternal(Set visits) { + this.visits = visits; + } + + public List getVisits() { + List sortedVisits = new ArrayList<>(getVisitsInternal()); + PropertyComparator.sort(sortedVisits, new MutableSortDefinition("date", false, false)); + return Collections.unmodifiableList(sortedVisits); + } + + public void setVisits(List visits) { + this.visits = new HashSet<>(visits); + } + + public void addVisit(Visit visit) { + getVisitsInternal().add(visit); + visit.setPet(this); + } + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/model/PetType.java b/backend/src/main/java/org/springframework/samples/petclinic/model/PetType.java new file mode 100644 index 0000000..beecdec --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/model/PetType.java @@ -0,0 +1,29 @@ +/* + * Copyright 2002-2013 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 + * + * http://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.model; + +import jakarta.persistence.Entity; +import jakarta.persistence.Table; + +/** + * @author Juergen Hoeller + * Can be Cat, Dog, Hamster... + */ +@Entity +@Table(name = "types") +public class PetType extends NamedEntity { + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/model/Role.java b/backend/src/main/java/org/springframework/samples/petclinic/model/Role.java new file mode 100644 index 0000000..da35c8e --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/model/Role.java @@ -0,0 +1,39 @@ +package org.springframework.samples.petclinic.model; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +@Entity +@Table(name = "roles" ,uniqueConstraints = @UniqueConstraint(columnNames = {"username", "role"})) +public class Role extends BaseEntity { + + @ManyToOne + @JoinColumn(name = "username") + @JsonIgnore + private User user; + + @Column( name = "role") + private String name; + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/model/Specialty.java b/backend/src/main/java/org/springframework/samples/petclinic/model/Specialty.java new file mode 100644 index 0000000..8eefd31 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/model/Specialty.java @@ -0,0 +1,30 @@ +/* + * Copyright 2002-2013 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 + * + * http://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.model; + +import jakarta.persistence.Entity; +import jakarta.persistence.Table; + +/** + * Models a {@link Vet Vet's} specialty (for example, dentistry). + * + * @author Juergen Hoeller + */ +@Entity +@Table(name = "specialties") +public class Specialty extends NamedEntity { + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/model/User.java b/backend/src/main/java/org/springframework/samples/petclinic/model/User.java new file mode 100644 index 0000000..62d412f --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/model/User.java @@ -0,0 +1,74 @@ +package org.springframework.samples.petclinic.model; + +import java.util.HashSet; +import java.util.Set; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +@Entity +@Table(name = "users") +public class User { + + @Id + @Column(name = "username") + private String username; + + @Column(name = "password") + private String password; + + @Column(name = "enabled") + private Boolean enabled; + + @OneToMany(cascade = CascadeType.ALL, mappedBy = "user", fetch = FetchType.EAGER) + private Set roles; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Boolean getEnabled() { + return enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + public Set getRoles() { + return roles; + } + + public void setRoles(Set roles) { + this.roles = roles; + } + + @JsonIgnore + public void addRole(String roleName) { + if(this.roles == null) { + this.roles = new HashSet<>(); + } + Role role = new Role(); + role.setName(roleName); + this.roles.add(role); + } +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/model/Vet.java b/backend/src/main/java/org/springframework/samples/petclinic/model/Vet.java new file mode 100644 index 0000000..3add8c8 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/model/Vet.java @@ -0,0 +1,79 @@ +/* + * Copyright 2002-2018 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 + * + * http://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.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.springframework.beans.support.MutableSortDefinition; +import org.springframework.beans.support.PropertyComparator; + +import jakarta.persistence.*; +import jakarta.xml.bind.annotation.XmlElement; +import java.util.*; + +/** + * Simple JavaBean domain object representing a veterinarian. + * + * @author Ken Krebs + * @author Juergen Hoeller + * @author Sam Brannen + * @author Arjen Poutsma + */ +@Entity +@Table(name = "vets") +public class Vet extends Person { + + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name = "vet_specialties", joinColumns = @JoinColumn(name = "vet_id"), + inverseJoinColumns = @JoinColumn(name = "specialty_id")) + private Set specialties; + + @JsonIgnore + protected Set getSpecialtiesInternal() { + if (this.specialties == null) { + this.specialties = new HashSet<>(); + } + return this.specialties; + } + + protected void setSpecialtiesInternal(Set specialties) { + this.specialties = specialties; + } + + @XmlElement + public List getSpecialties() { + List sortedSpecs = new ArrayList<>(getSpecialtiesInternal()); + PropertyComparator.sort(sortedSpecs, new MutableSortDefinition("name", true, true)); + return Collections.unmodifiableList(sortedSpecs); + } + + public void setSpecialties(List specialties) { + this.specialties = new HashSet<>(specialties); + } + + @JsonIgnore + public int getNrOfSpecialties() { + return getSpecialtiesInternal().size(); + } + + public void addSpecialty(Specialty specialty) { + getSpecialtiesInternal().add(specialty); + } + + public void clearSpecialties() { + getSpecialtiesInternal().clear(); + } + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/model/Visit.java b/backend/src/main/java/org/springframework/samples/petclinic/model/Visit.java new file mode 100644 index 0000000..f98cb45 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/model/Visit.java @@ -0,0 +1,116 @@ +/* + * Copyright 2002-2013 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 + * + * http://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.model; + +import org.springframework.format.annotation.DateTimeFormat; + +import jakarta.persistence.*; +import jakarta.validation.constraints.NotEmpty; +import java.time.LocalDate; + +/** + * Simple JavaBean domain object representing a visit. + * + * @author Ken Krebs + */ +@Entity +@Table(name = "visits") +public class Visit extends BaseEntity { + + /** + * Holds value of property date. + */ + @Column(name = "visit_date", columnDefinition = "DATE") + private LocalDate date; + + /** + * Holds value of property description. + */ + @NotEmpty + @Column(name = "description") + private String description; + + /** + * Holds value of property pet. + */ + @ManyToOne + @JoinColumn(name = "pet_id") + private Pet pet; + + + /** + * Creates a new instance of Visit for the current date + */ + public Visit() { + this.date = LocalDate.now(); + } + + + /** + * Getter for property date. + * + * @return Value of property date. + */ + public LocalDate getDate() { + return this.date; + } + + /** + * Setter for property date. + * + * @param date New value of property date. + */ + public void setDate(LocalDate date) { + this.date = date; + } + + /** + * Getter for property description. + * + * @return Value of property description. + */ + public String getDescription() { + return this.description; + } + + /** + * Setter for property description. + * + * @param description New value of property description. + */ + public void setDescription(String description) { + this.description = description; + } + + /** + * Getter for property pet. + * + * @return Value of property pet. + */ + public Pet getPet() { + return this.pet; + } + + /** + * Setter for property pet. + * + * @param pet New value of property pet. + */ + public void setPet(Pet pet) { + this.pet = pet; + } + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/model/package-info.java b/backend/src/main/java/org/springframework/samples/petclinic/model/package-info.java new file mode 100644 index 0000000..c5d9a7b --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/model/package-info.java @@ -0,0 +1,5 @@ +/** + * The classes in this package represent PetClinic's business layer. + */ +package org.springframework.samples.petclinic.model; + diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/OwnerRepository.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/OwnerRepository.java new file mode 100644 index 0000000..feefee8 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/OwnerRepository.java @@ -0,0 +1,81 @@ +/* + * Copyright 2002-2017 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 + * + * http://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.repository; + +import java.util.Collection; + +import org.springframework.dao.DataAccessException; +import org.springframework.samples.petclinic.model.BaseEntity; +import org.springframework.samples.petclinic.model.Owner; + +/** + * Repository class for Owner domain objects All method names are compliant with Spring Data naming + * conventions so this interface can easily be extended for Spring Data See here: http://static.springsource.org/spring-data/jpa/docs/current/reference/html/jpa.repositories.html#jpa.query-methods.query-creation + * + * @author Ken Krebs + * @author Juergen Hoeller + * @author Sam Brannen + * @author Michael Isvy + * @author Vitaliy Fedoriv + */ +public interface OwnerRepository { + + /** + * Retrieve Owners from the data store by last name, returning all owners whose last name starts + * with the given name. + * + * @param lastName Value to search for + * @return a Collection of matching Owners (or an empty Collection if none + * found) + */ + Collection findByLastName(String lastName) throws DataAccessException; + + /** + * Retrieve an Owner from the data store by id. + * + * @param id the id to search for + * @return the Owner if found + * @throws org.springframework.dao.DataRetrievalFailureException if not found + */ + Owner findById(int id) throws DataAccessException; + + + /** + * Save an Owner to the data store, either inserting or updating it. + * + * @param owner the Owner to save + * @see BaseEntity#isNew + */ + void save(Owner owner) throws DataAccessException; + + /** + * Retrieve Owners from the data store, returning all owners + * + * @return a Collection of Owners (or an empty Collection if none + * found) + */ + Collection findAll() throws DataAccessException; + + /** + * Delete an Owner to the data store by Owner. + * + * @param owner the Owner to delete + * + */ + void delete(Owner owner) throws DataAccessException; + + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/PetRepository.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/PetRepository.java new file mode 100644 index 0000000..f531490 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/PetRepository.java @@ -0,0 +1,78 @@ +/* + * Copyright 2002-2017 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 + * + * http://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.repository; + +import java.util.Collection; +import java.util.List; + +import org.springframework.dao.DataAccessException; +import org.springframework.samples.petclinic.model.BaseEntity; +import org.springframework.samples.petclinic.model.Pet; +import org.springframework.samples.petclinic.model.PetType; + +/** + * Repository class for Pet domain objects All method names are compliant with Spring Data naming + * conventions so this interface can easily be extended for Spring Data See here: http://static.springsource.org/spring-data/jpa/docs/current/reference/html/jpa.repositories.html#jpa.query-methods.query-creation + * + * @author Ken Krebs + * @author Juergen Hoeller + * @author Sam Brannen + * @author Michael Isvy + * @author Vitaliy Fedoriv + */ +public interface PetRepository { + + /** + * Retrieve all PetTypes from the data store. + * + * @return a Collection of PetTypes + */ + List findPetTypes() throws DataAccessException; + + /** + * Retrieve a Pet from the data store by id. + * + * @param id the id to search for + * @return the Pet if found + * @throws org.springframework.dao.DataRetrievalFailureException if not found + */ + Pet findById(int id) throws DataAccessException; + + /** + * Save a Pet to the data store, either inserting or updating it. + * + * @param pet the Pet to save + * @see BaseEntity#isNew + */ + void save(Pet pet) throws DataAccessException; + + /** + * Retrieve Pets from the data store, returning all owners + * + * @return a Collection of Pets (or an empty Collection if none + * found) + */ + Collection findAll() throws DataAccessException; + + /** + * Delete an Pet to the data store by Pet. + * + * @param pet the Pet to delete + * + */ + void delete(Pet pet) throws DataAccessException; + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/PetTypeRepository.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/PetTypeRepository.java new file mode 100644 index 0000000..d1c05e9 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/PetTypeRepository.java @@ -0,0 +1,41 @@ +/* + * Copyright 2016-2017 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 + * + * http://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.repository; + +import java.util.Collection; + +import org.springframework.dao.DataAccessException; +import org.springframework.samples.petclinic.model.PetType; + +/** + * @author Vitaliy Fedoriv + * + */ + +public interface PetTypeRepository { + + PetType findById(int id) throws DataAccessException; + + PetType findByName(String name) throws DataAccessException; + + Collection findAll() throws DataAccessException; + + void save(PetType petType) throws DataAccessException; + + void delete(PetType petType) throws DataAccessException; + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/SpecialtyRepository.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/SpecialtyRepository.java new file mode 100644 index 0000000..256163c --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/SpecialtyRepository.java @@ -0,0 +1,43 @@ +/* + * Copyright 2016-2017 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 + * + * http://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.repository; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import org.springframework.dao.DataAccessException; +import org.springframework.samples.petclinic.model.Specialty; + +/** + * @author Vitaliy Fedoriv + * + */ + +public interface SpecialtyRepository { + + Specialty findById(int id) throws DataAccessException; + + List findSpecialtiesByNameIn(Set names); + + Collection findAll() throws DataAccessException; + + void save(Specialty specialty) throws DataAccessException; + + void delete(Specialty specialty) throws DataAccessException; + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/UserRepository.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/UserRepository.java new file mode 100644 index 0000000..3e9b45b --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/UserRepository.java @@ -0,0 +1,9 @@ +package org.springframework.samples.petclinic.repository; + +import org.springframework.dao.DataAccessException; +import org.springframework.samples.petclinic.model.User; + +public interface UserRepository { + + void save(User user) throws DataAccessException; +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/VetRepository.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/VetRepository.java new file mode 100644 index 0000000..0f3a3d1 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/VetRepository.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2017 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 + * + * http://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.repository; + +import java.util.Collection; + +import org.springframework.dao.DataAccessException; +import org.springframework.samples.petclinic.model.Vet; + +/** + * Repository class for Vet domain objects All method names are compliant with Spring Data naming + * conventions so this interface can easily be extended for Spring Data See here: http://static.springsource.org/spring-data/jpa/docs/current/reference/html/jpa.repositories.html#jpa.query-methods.query-creation + * + * @author Ken Krebs + * @author Juergen Hoeller + * @author Sam Brannen + * @author Michael Isvy + * @author Vitaliy Fedoriv + */ +public interface VetRepository { + + /** + * Retrieve all Vets from the data store. + * + * @return a Collection of Vets + */ + Collection findAll() throws DataAccessException; + + Vet findById(int id) throws DataAccessException; + + void save(Vet vet) throws DataAccessException; + + void delete(Vet vet) throws DataAccessException; + + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/VisitRepository.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/VisitRepository.java new file mode 100644 index 0000000..fac5659 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/VisitRepository.java @@ -0,0 +1,53 @@ +/* + * Copyright 2002-2017 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 + * + * http://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.repository; + +import java.util.Collection; +import java.util.List; + +import org.springframework.dao.DataAccessException; +import org.springframework.samples.petclinic.model.BaseEntity; +import org.springframework.samples.petclinic.model.Visit; + +/** + * Repository class for Visit domain objects All method names are compliant with Spring Data naming + * conventions so this interface can easily be extended for Spring Data See here: http://static.springsource.org/spring-data/jpa/docs/current/reference/html/jpa.repositories.html#jpa.query-methods.query-creation + * + * @author Ken Krebs + * @author Juergen Hoeller + * @author Sam Brannen + * @author Michael Isvy + * @author Vitaliy Fedoriv + */ +public interface VisitRepository { + + /** + * Save a Visit to the data store, either inserting or updating it. + * + * @param visit the Visit to save + * @see BaseEntity#isNew + */ + void save(Visit visit) throws DataAccessException; + + List findByPetId(Integer petId); + + Visit findById(int id) throws DataAccessException; + + Collection findAll() throws DataAccessException; + + void delete(Visit visit) throws DataAccessException; + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcOwnerRepositoryImpl.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcOwnerRepositoryImpl.java new file mode 100644 index 0000000..406286b --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcOwnerRepositoryImpl.java @@ -0,0 +1,196 @@ +/* + * Copyright 2002-2017 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 + * + * http://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.repository.jdbc; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Profile; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.BeanPropertyRowMapper; +import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.core.simple.SimpleJdbcInsert; +import org.springframework.orm.ObjectRetrievalFailureException; +import org.springframework.samples.petclinic.model.Owner; +import org.springframework.samples.petclinic.model.Pet; +import org.springframework.samples.petclinic.model.PetType; +import org.springframework.samples.petclinic.model.Visit; +import org.springframework.samples.petclinic.repository.OwnerRepository; +import org.springframework.samples.petclinic.util.EntityUtils; +import org.springframework.stereotype.Repository; + +import javax.sql.DataSource; +import jakarta.transaction.Transactional; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A simple JDBC-based implementation of the {@link OwnerRepository} interface. + * + * @author Ken Krebs + * @author Juergen Hoeller + * @author Rob Harrop + * @author Sam Brannen + * @author Thomas Risberg + * @author Mark Fisher + * @author Antoine Rey + * @author Vitaliy Fedoriv + */ +@Repository +@Profile("jdbc") +public class JdbcOwnerRepositoryImpl implements OwnerRepository { + + private NamedParameterJdbcTemplate namedParameterJdbcTemplate; + + private SimpleJdbcInsert insertOwner; + + @Autowired + public JdbcOwnerRepositoryImpl(DataSource dataSource) { + + this.insertOwner = new SimpleJdbcInsert(dataSource) + .withTableName("owners") + .usingGeneratedKeyColumns("id"); + + this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource); + + } + + + /** + * Loads {@link Owner Owners} from the data store by last name, returning all owners whose last name starts with + * the given name; also loads the {@link Pet Pets} and {@link Visit Visits} for the corresponding owners, if not + * already loaded. + */ + @Override + public Collection findByLastName(String lastName) throws DataAccessException { + Map params = new HashMap<>(); + params.put("lastName", lastName + "%"); + List owners = this.namedParameterJdbcTemplate.query( + "SELECT id, first_name, last_name, address, city, telephone FROM owners WHERE last_name like :lastName", + params, + BeanPropertyRowMapper.newInstance(Owner.class) + ); + loadOwnersPetsAndVisits(owners); + return owners; + } + + /** + * Loads the {@link Owner} with the supplied id; also loads the {@link Pet Pets} and {@link Visit Visits} + * for the corresponding owner, if not already loaded. + */ + @Override + public Owner findById(int id) throws DataAccessException { + Owner owner; + try { + Map params = new HashMap<>(); + params.put("id", id); + owner = this.namedParameterJdbcTemplate.queryForObject( + "SELECT id, first_name, last_name, address, city, telephone FROM owners WHERE id= :id", + params, + BeanPropertyRowMapper.newInstance(Owner.class) + ); + } catch (EmptyResultDataAccessException ex) { + throw new ObjectRetrievalFailureException(Owner.class, id); + } + loadPetsAndVisits(owner); + return owner; + } + + public void loadPetsAndVisits(final Owner owner) { + Map params = new HashMap<>(); + params.put("id", owner.getId()); + final List pets = this.namedParameterJdbcTemplate.query( + "SELECT pets.id as pets_id, name, birth_date, type_id, owner_id, visits.id as visit_id, visit_date, description, visits.pet_id as visits_pet_id FROM pets LEFT OUTER JOIN visits ON pets.id = visits.pet_id WHERE owner_id=:id ORDER BY pets.id", + params, + new JdbcPetVisitExtractor() + ); + Collection petTypes = getPetTypes(); + for (JdbcPet pet : pets) { + pet.setType(EntityUtils.getById(petTypes, PetType.class, pet.getTypeId())); + owner.addPet(pet); + } + } + + @Override + public void save(Owner owner) throws DataAccessException { + BeanPropertySqlParameterSource parameterSource = new BeanPropertySqlParameterSource(owner); + if (owner.isNew()) { + Number newKey = this.insertOwner.executeAndReturnKey(parameterSource); + owner.setId(newKey.intValue()); + } else { + this.namedParameterJdbcTemplate.update( + "UPDATE owners SET first_name=:firstName, last_name=:lastName, address=:address, " + + "city=:city, telephone=:telephone WHERE id=:id", + parameterSource); + } + } + + public Collection getPetTypes() throws DataAccessException { + return this.namedParameterJdbcTemplate.query( + "SELECT id, name FROM types ORDER BY name", new HashMap(), + BeanPropertyRowMapper.newInstance(PetType.class)); + } + + /** + * Loads the {@link Pet} and {@link Visit} data for the supplied {@link List} of {@link Owner Owners}. + * + * @param owners the list of owners for whom the pet and visit data should be loaded + * @see #loadPetsAndVisits(Owner) + */ + private void loadOwnersPetsAndVisits(List owners) { + for (Owner owner : owners) { + loadPetsAndVisits(owner); + } + } + + @Override + public Collection findAll() throws DataAccessException { + List owners = this.namedParameterJdbcTemplate.query( + "SELECT id, first_name, last_name, address, city, telephone FROM owners", + new HashMap(), + BeanPropertyRowMapper.newInstance(Owner.class)); + for (Owner owner : owners) { + loadPetsAndVisits(owner); + } + return owners; + } + + @Override + @Transactional + public void delete(Owner owner) throws DataAccessException { + Map owner_params = new HashMap<>(); + owner_params.put("id", owner.getId()); + List pets = owner.getPets(); + // cascade delete pets + for (Pet pet : pets){ + Map pet_params = new HashMap<>(); + pet_params.put("id", pet.getId()); + // cascade delete visits + List visits = pet.getVisits(); + for (Visit visit : visits){ + Map visit_params = new HashMap<>(); + visit_params.put("id", visit.getId()); + this.namedParameterJdbcTemplate.update("DELETE FROM visits WHERE id=:id", visit_params); + } + this.namedParameterJdbcTemplate.update("DELETE FROM pets WHERE id=:id", pet_params); + } + this.namedParameterJdbcTemplate.update("DELETE FROM owners WHERE id=:id", owner_params); + } + + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPet.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPet.java new file mode 100644 index 0000000..05d048d --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPet.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2013 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 + * + * http://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.repository.jdbc; + +import org.springframework.samples.petclinic.model.Pet; + +/** + * Subclass of Pet that carries temporary id properties which are only relevant for a JDBC implementation of the + * PetRepository. + * + * @author Juergen Hoeller + */ +public class JdbcPet extends Pet { + + private int typeId; + + private int ownerId; + + public int getTypeId() { + return this.typeId; + } + + public void setTypeId(int typeId) { + this.typeId = typeId; + } + + public int getOwnerId() { + return this.ownerId; + } + + public void setOwnerId(int ownerId) { + this.ownerId = ownerId; + } + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetRepositoryImpl.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetRepositoryImpl.java new file mode 100644 index 0000000..9911ff5 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetRepositoryImpl.java @@ -0,0 +1,169 @@ +/* + * Copyright 2002-2017 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 + * + * http://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.repository.jdbc; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.sql.DataSource; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Profile; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.BeanPropertyRowMapper; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.core.simple.SimpleJdbcInsert; +import org.springframework.orm.ObjectRetrievalFailureException; +import org.springframework.samples.petclinic.model.Owner; +import org.springframework.samples.petclinic.model.Pet; +import org.springframework.samples.petclinic.model.PetType; +import org.springframework.samples.petclinic.model.Visit; +import org.springframework.samples.petclinic.repository.OwnerRepository; +import org.springframework.samples.petclinic.repository.PetRepository; +import org.springframework.samples.petclinic.repository.VisitRepository; +import org.springframework.samples.petclinic.util.EntityUtils; +import org.springframework.stereotype.Repository; + +/** + * @author Ken Krebs + * @author Juergen Hoeller + * @author Rob Harrop + * @author Sam Brannen + * @author Thomas Risberg + * @author Mark Fisher + * @author Vitaliy Fedoriv + */ +@Repository +@Profile("jdbc") +public class JdbcPetRepositoryImpl implements PetRepository { + + private NamedParameterJdbcTemplate namedParameterJdbcTemplate; + + private SimpleJdbcInsert insertPet; + + private OwnerRepository ownerRepository; + + private VisitRepository visitRepository; + + + @Autowired + public JdbcPetRepositoryImpl(DataSource dataSource, + OwnerRepository ownerRepository, + VisitRepository visitRepository) { + this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource); + + this.insertPet = new SimpleJdbcInsert(dataSource) + .withTableName("pets") + .usingGeneratedKeyColumns("id"); + + this.ownerRepository = ownerRepository; + this.visitRepository = visitRepository; + } + + @Override + public List findPetTypes() throws DataAccessException { + Map params = new HashMap<>(); + return this.namedParameterJdbcTemplate.query( + "SELECT id, name FROM types ORDER BY name", + params, + BeanPropertyRowMapper.newInstance(PetType.class)); + } + + @Override + public Pet findById(int id) throws DataAccessException { + Integer ownerId; + try { + Map params = new HashMap<>(); + params.put("id", id); + ownerId = this.namedParameterJdbcTemplate.queryForObject("SELECT owner_id FROM pets WHERE id=:id", params, Integer.class); + } catch (EmptyResultDataAccessException ex) { + throw new ObjectRetrievalFailureException(Pet.class, id); + } + Owner owner = this.ownerRepository.findById(ownerId); + return EntityUtils.getById(owner.getPets(), Pet.class, id); + } + + @Override + public void save(Pet pet) throws DataAccessException { + if (pet.isNew()) { + Number newKey = this.insertPet.executeAndReturnKey( + createPetParameterSource(pet)); + pet.setId(newKey.intValue()); + } else { + this.namedParameterJdbcTemplate.update( + "UPDATE pets SET name=:name, birth_date=:birth_date, type_id=:type_id, " + + "owner_id=:owner_id WHERE id=:id", + createPetParameterSource(pet)); + } + } + + /** + * Creates a {@link MapSqlParameterSource} based on data values from the supplied {@link Pet} instance. + */ + private MapSqlParameterSource createPetParameterSource(Pet pet) { + return new MapSqlParameterSource() + .addValue("id", pet.getId()) + .addValue("name", pet.getName()) + .addValue("birth_date", pet.getBirthDate()) + .addValue("type_id", pet.getType().getId()) + .addValue("owner_id", pet.getOwner().getId()); + } + + @Override + public Collection findAll() throws DataAccessException { + Map params = new HashMap<>(); + Collection pets = new ArrayList(); + Collection jdbcPets = new ArrayList(); + jdbcPets = this.namedParameterJdbcTemplate + .query("SELECT pets.id as pets_id, name, birth_date, type_id, owner_id FROM pets", + params, + new JdbcPetRowMapper()); + Collection petTypes = this.namedParameterJdbcTemplate.query("SELECT id, name FROM types ORDER BY name", + new HashMap(), BeanPropertyRowMapper.newInstance(PetType.class)); + Collection owners = this.namedParameterJdbcTemplate.query( + "SELECT id, first_name, last_name, address, city, telephone FROM owners ORDER BY last_name", + new HashMap(), + BeanPropertyRowMapper.newInstance(Owner.class)); + for (JdbcPet jdbcPet : jdbcPets) { + jdbcPet.setType(EntityUtils.getById(petTypes, PetType.class, jdbcPet.getTypeId())); + jdbcPet.setOwner(EntityUtils.getById(owners, Owner.class, jdbcPet.getOwnerId())); + // TODO add visits + pets.add(jdbcPet); + } + return pets; + } + + @Override + public void delete(Pet pet) throws DataAccessException { + Map pet_params = new HashMap<>(); + pet_params.put("id", pet.getId()); + List visits = pet.getVisits(); + // cascade delete visits + for (Visit visit : visits) { + Map visit_params = new HashMap<>(); + visit_params.put("id", visit.getId()); + this.namedParameterJdbcTemplate.update("DELETE FROM visits WHERE id=:id", visit_params); + } + this.namedParameterJdbcTemplate.update("DELETE FROM pets WHERE id=:id", pet_params); + } + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetRowMapper.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetRowMapper.java new file mode 100644 index 0000000..0305263 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetRowMapper.java @@ -0,0 +1,41 @@ +/* + * Copyright 2002-2013 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 + * + * http://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.repository.jdbc; + +import org.springframework.jdbc.core.RowMapper; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.LocalDate; +import java.util.Date; + +/** + * {@link RowMapper} implementation mapping data from a {@link ResultSet} to the corresponding properties + * of the {@link JdbcPet} class. + */ +public class JdbcPetRowMapper implements RowMapper { + + @Override + public JdbcPet mapRow(ResultSet rs, int rownum) throws SQLException { + JdbcPet pet = new JdbcPet(); + pet.setId(rs.getInt("pets_id")); + pet.setName(rs.getString("name")); + pet.setBirthDate(rs.getObject("birth_date", LocalDate.class)); + pet.setTypeId(rs.getInt("type_id")); + pet.setOwnerId(rs.getInt("owner_id")); + return pet; + } +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetTypeRepositoryImpl.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetTypeRepositoryImpl.java new file mode 100644 index 0000000..1835cb7 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetTypeRepositoryImpl.java @@ -0,0 +1,145 @@ +/* + * Copyright 2016-2017 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 + * + * http://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.repository.jdbc; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.sql.DataSource; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Profile; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.BeanPropertyRowMapper; +import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.core.simple.SimpleJdbcInsert; +import org.springframework.orm.ObjectRetrievalFailureException; +import org.springframework.samples.petclinic.model.Pet; +import org.springframework.samples.petclinic.model.PetType; +import org.springframework.samples.petclinic.model.Visit; +import org.springframework.samples.petclinic.repository.PetTypeRepository; +import org.springframework.stereotype.Repository; + +/** + * @author Vitaliy Fedoriv + * + */ + +@Repository +@Profile("jdbc") +public class JdbcPetTypeRepositoryImpl implements PetTypeRepository { + + private NamedParameterJdbcTemplate namedParameterJdbcTemplate; + + private SimpleJdbcInsert insertPetType; + + @Autowired + public JdbcPetTypeRepositoryImpl(DataSource dataSource) { + this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource); + this.insertPetType = new SimpleJdbcInsert(dataSource) + .withTableName("types") + .usingGeneratedKeyColumns("id"); + } + + @Override + public PetType findById(int id) { + PetType petType; + try { + Map params = new HashMap<>(); + params.put("id", id); + petType = this.namedParameterJdbcTemplate.queryForObject( + "SELECT id, name FROM types WHERE id= :id", + params, + BeanPropertyRowMapper.newInstance(PetType.class)); + } catch (EmptyResultDataAccessException ex) { + throw new ObjectRetrievalFailureException(PetType.class, id); + } + return petType; + } + + @Override + public PetType findByName(String name) throws DataAccessException { + PetType petType; + try { + Map params = new HashMap<>(); + params.put("name", name); + petType = this.namedParameterJdbcTemplate.queryForObject( + "SELECT id, name FROM types WHERE name= :name", + params, + BeanPropertyRowMapper.newInstance(PetType.class)); + } catch (EmptyResultDataAccessException ex) { + throw new ObjectRetrievalFailureException(PetType.class, name); + } + return petType; + } + + @Override + public Collection findAll() throws DataAccessException { + Map params = new HashMap<>(); + return this.namedParameterJdbcTemplate.query( + "SELECT id, name FROM types", + params, + BeanPropertyRowMapper.newInstance(PetType.class)); + } + + @Override + public void save(PetType petType) throws DataAccessException { + BeanPropertySqlParameterSource parameterSource = new BeanPropertySqlParameterSource(petType); + if (petType.isNew()) { + Number newKey = this.insertPetType.executeAndReturnKey(parameterSource); + petType.setId(newKey.intValue()); + } else { + this.namedParameterJdbcTemplate.update("UPDATE types SET name=:name WHERE id=:id", + parameterSource); + } + } + + @Override + public void delete(PetType petType) throws DataAccessException { + Map pettype_params = new HashMap<>(); + pettype_params.put("id", petType.getId()); + List pets = new ArrayList(); + pets = this.namedParameterJdbcTemplate. + query("SELECT pets.id, name, birth_date, type_id, owner_id FROM pets WHERE type_id=:id", + pettype_params, + BeanPropertyRowMapper.newInstance(Pet.class)); + // cascade delete pets + for (Pet pet : pets){ + Map pet_params = new HashMap<>(); + pet_params.put("id", pet.getId()); + List visits = new ArrayList(); + visits = this.namedParameterJdbcTemplate.query( + "SELECT id, pet_id, visit_date, description FROM visits WHERE pet_id = :id", + pet_params, + BeanPropertyRowMapper.newInstance(Visit.class)); + // cascade delete visits + for (Visit visit : visits){ + Map visit_params = new HashMap<>(); + visit_params.put("id", visit.getId()); + this.namedParameterJdbcTemplate.update("DELETE FROM visits WHERE id=:id", visit_params); + } + this.namedParameterJdbcTemplate.update("DELETE FROM pets WHERE id=:id", pet_params); + } + this.namedParameterJdbcTemplate.update("DELETE FROM types WHERE id=:id", pettype_params); + } + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetVisitExtractor.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetVisitExtractor.java new file mode 100644 index 0000000..f6483e3 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetVisitExtractor.java @@ -0,0 +1,54 @@ +/* + * Copyright 2002-2015 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 + * + * http://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.repository.jdbc; + +import org.springframework.data.jdbc.core.OneToManyResultSetExtractor; +import org.springframework.jdbc.core.ResultSetExtractor; +import org.springframework.samples.petclinic.model.Visit; + +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * {@link ResultSetExtractor} implementation by using the + * {@link OneToManyResultSetExtractor} of Spring Data Core JDBC Extensions. + */ +public class JdbcPetVisitExtractor extends + OneToManyResultSetExtractor { + + public JdbcPetVisitExtractor() { + super(new JdbcPetRowMapper(), new JdbcVisitRowMapper()); + } + + @Override + protected Integer mapPrimaryKey(ResultSet rs) throws SQLException { + return rs.getInt("pets_id"); + } + + @Override + protected Integer mapForeignKey(ResultSet rs) throws SQLException { + if (rs.getObject("visits_pet_id") == null) { + return null; + } else { + return rs.getInt("visits_pet_id"); + } + } + + @Override + protected void addChild(JdbcPet root, Visit child) { + root.addVisit(child); + } +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcSpecialtyRepositoryImpl.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcSpecialtyRepositoryImpl.java new file mode 100644 index 0000000..3f125d9 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcSpecialtyRepositoryImpl.java @@ -0,0 +1,121 @@ +/* + * Copyright 2016-2017 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 + * + * http://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.repository.jdbc; + +import java.util.*; + +import javax.sql.DataSource; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Profile; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.BeanPropertyRowMapper; +import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.core.simple.SimpleJdbcInsert; +import org.springframework.orm.ObjectRetrievalFailureException; +import org.springframework.samples.petclinic.model.Specialty; +import org.springframework.samples.petclinic.repository.SpecialtyRepository; +import org.springframework.stereotype.Repository; + +/** + * @author Vitaliy Fedoriv + * + */ + +@Repository +@Profile("jdbc") +public class JdbcSpecialtyRepositoryImpl implements SpecialtyRepository { + + private NamedParameterJdbcTemplate namedParameterJdbcTemplate; + + private SimpleJdbcInsert insertSpecialty; + + @Autowired + public JdbcSpecialtyRepositoryImpl(DataSource dataSource) { + this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource); + this.insertSpecialty = new SimpleJdbcInsert(dataSource) + .withTableName("specialties") + .usingGeneratedKeyColumns("id"); + } + + @Override + public Specialty findById(int id) { + Specialty specialty; + try { + Map params = new HashMap<>(); + params.put("id", id); + specialty = this.namedParameterJdbcTemplate.queryForObject( + "SELECT id, name FROM specialties WHERE id= :id", + params, + BeanPropertyRowMapper.newInstance(Specialty.class)); + } catch (EmptyResultDataAccessException ex) { + throw new ObjectRetrievalFailureException(Specialty.class, id); + } + return specialty; + } + + @Override + public List findSpecialtiesByNameIn(Set names) { + List specialties; + try{ + String sql = "SELECT id, name FROM specialties WHERE specialties.name IN (:names)"; + Map params = new HashMap<>(); + params.put("names", names); + specialties = this.namedParameterJdbcTemplate.query( + sql, + params, + new BeanPropertyRowMapper<>(Specialty.class)); + } catch (EmptyResultDataAccessException ex){ + throw new ObjectRetrievalFailureException(Specialty.class, names); + } + + return specialties; + } + + @Override + public Collection findAll() throws DataAccessException { + Map params = new HashMap<>(); + return this.namedParameterJdbcTemplate.query( + "SELECT id, name FROM specialties", + params, + BeanPropertyRowMapper.newInstance(Specialty.class)); + } + + @Override + public void save(Specialty specialty) throws DataAccessException { + BeanPropertySqlParameterSource parameterSource = new BeanPropertySqlParameterSource(specialty); + if (specialty.isNew()) { + Number newKey = this.insertSpecialty.executeAndReturnKey(parameterSource); + specialty.setId(newKey.intValue()); + } else { + this.namedParameterJdbcTemplate.update("UPDATE specialties SET name=:name WHERE id=:id", + parameterSource); + } + + } + + @Override + public void delete(Specialty specialty) throws DataAccessException { + Map params = new HashMap<>(); + params.put("id", specialty.getId()); + this.namedParameterJdbcTemplate.update("DELETE FROM vet_specialties WHERE specialty_id=:id", params); + this.namedParameterJdbcTemplate.update("DELETE FROM specialties WHERE id=:id", params); + } + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcUserRepositoryImpl.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcUserRepositoryImpl.java new file mode 100644 index 0000000..7fc51ec --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcUserRepositoryImpl.java @@ -0,0 +1,68 @@ +package org.springframework.samples.petclinic.repository.jdbc; + +import java.util.HashMap; +import java.util.Map; + +import javax.sql.DataSource; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Profile; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.BeanPropertyRowMapper; +import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.core.simple.SimpleJdbcInsert; +import org.springframework.samples.petclinic.model.Role; +import org.springframework.samples.petclinic.model.User; +import org.springframework.samples.petclinic.repository.UserRepository; +import org.springframework.stereotype.Repository; + +@Repository +@Profile("jdbc") +public class JdbcUserRepositoryImpl implements UserRepository { + + private NamedParameterJdbcTemplate namedParameterJdbcTemplate; + private SimpleJdbcInsert insertUser; + + @Autowired + public JdbcUserRepositoryImpl(DataSource dataSource) { + this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource); + this.insertUser = new SimpleJdbcInsert(dataSource).withTableName("users"); + } + + @Override + public void save(User user) throws DataAccessException { + + BeanPropertySqlParameterSource parameterSource = new BeanPropertySqlParameterSource(user); + + try { + getByUsername(user.getUsername()); + this.namedParameterJdbcTemplate.update("UPDATE users SET password=:password, enabled=:enabled WHERE username=:username", parameterSource); + } catch (EmptyResultDataAccessException e) { + this.insertUser.execute(parameterSource); + } finally { + updateUserRoles(user); + } + } + + private User getByUsername(String username) { + + Map params = new HashMap<>(); + params.put("username", username); + return this.namedParameterJdbcTemplate.queryForObject("SELECT * FROM users WHERE username=:username", + params, BeanPropertyRowMapper.newInstance(User.class)); + } + + private void updateUserRoles(User user) { + Map params = new HashMap<>(); + params.put("username", user.getUsername()); + this.namedParameterJdbcTemplate.update("DELETE FROM roles WHERE username=:username", params); + for (Role role : user.getRoles()) { + params.put("role", role.getName()); + if (role.getName() != null) { + this.namedParameterJdbcTemplate.update("INSERT INTO roles(username, role) VALUES (:username, :role)", params); + } + } + } +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcVetRepositoryImpl.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcVetRepositoryImpl.java new file mode 100644 index 0000000..c5bc285 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcVetRepositoryImpl.java @@ -0,0 +1,174 @@ +/* + * Copyright 2002-2018 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 + * + * http://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.repository.jdbc; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.sql.DataSource; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Profile; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.BeanPropertyRowMapper; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.core.simple.SimpleJdbcInsert; +import org.springframework.orm.ObjectRetrievalFailureException; +import org.springframework.samples.petclinic.model.Specialty; +import org.springframework.samples.petclinic.model.Vet; +import org.springframework.samples.petclinic.repository.VetRepository; +import org.springframework.samples.petclinic.util.EntityUtils; +import org.springframework.stereotype.Repository; + +/** + * A simple JDBC-based implementation of the {@link VetRepository} interface. + * + * @author Ken Krebs + * @author Juergen Hoeller + * @author Rob Harrop + * @author Sam Brannen + * @author Thomas Risberg + * @author Mark Fisher + * @author Michael Isvy + * @author Vitaliy Fedoriv + */ +@Repository +@Profile("jdbc") +public class JdbcVetRepositoryImpl implements VetRepository { + + private JdbcTemplate jdbcTemplate; + private NamedParameterJdbcTemplate namedParameterJdbcTemplate; + private SimpleJdbcInsert insertVet; + + @Autowired + public JdbcVetRepositoryImpl(DataSource dataSource, JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + this.insertVet = new SimpleJdbcInsert(dataSource).withTableName("vets").usingGeneratedKeyColumns("id"); + this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource); + } + + /** + * Refresh the cache of Vets that the ClinicService is holding. + */ + @Override + public Collection findAll() throws DataAccessException { + List vets = new ArrayList<>(); + // Retrieve the list of all vets. + vets.addAll(this.jdbcTemplate.query( + "SELECT id, first_name, last_name FROM vets ORDER BY last_name,first_name", + BeanPropertyRowMapper.newInstance(Vet.class))); + + // Retrieve the list of all possible specialties. + final List specialties = this.jdbcTemplate.query( + "SELECT id, name FROM specialties", + BeanPropertyRowMapper.newInstance(Specialty.class)); + + // Build each vet's list of specialties. + for (Vet vet : vets) { + final List vetSpecialtiesIds = this.jdbcTemplate.query( + "SELECT specialty_id FROM vet_specialties WHERE vet_id=?", + new BeanPropertyRowMapper() { + @Override + public Integer mapRow(ResultSet rs, int row) throws SQLException { + return rs.getInt(1); + } + }, + vet.getId()); + for (int specialtyId : vetSpecialtiesIds) { + Specialty specialty = EntityUtils.getById(specialties, Specialty.class, specialtyId); + vet.addSpecialty(specialty); + } + } + return vets; + } + + @Override + public Vet findById(int id) throws DataAccessException { + Vet vet; + try { + Map vet_params = new HashMap<>(); + vet_params.put("id", id); + vet = this.namedParameterJdbcTemplate.queryForObject( + "SELECT id, first_name, last_name FROM vets WHERE id= :id", + vet_params, + BeanPropertyRowMapper.newInstance(Vet.class)); + + final List specialties = this.namedParameterJdbcTemplate.query( + "SELECT id, name FROM specialties", vet_params, BeanPropertyRowMapper.newInstance(Specialty.class)); + + final List vetSpecialtiesIds = this.namedParameterJdbcTemplate.query( + "SELECT specialty_id FROM vet_specialties WHERE vet_id=:id", + vet_params, + new BeanPropertyRowMapper() { + @Override + public Integer mapRow(ResultSet rs, int row) throws SQLException { + return rs.getInt(1); + } + }); + for (int specialtyId : vetSpecialtiesIds) { + Specialty specialty = EntityUtils.getById(specialties, Specialty.class, specialtyId); + vet.addSpecialty(specialty); + } + + } catch (EmptyResultDataAccessException ex) { + throw new ObjectRetrievalFailureException(Vet.class, id); + } + return vet; + } + + @Override + public void save(Vet vet) throws DataAccessException { + BeanPropertySqlParameterSource parameterSource = new BeanPropertySqlParameterSource(vet); + if (vet.isNew()) { + Number newKey = this.insertVet.executeAndReturnKey(parameterSource); + vet.setId(newKey.intValue()); + updateVetSpecialties(vet); + } else { + this.namedParameterJdbcTemplate + .update("UPDATE vets SET first_name=:firstName, last_name=:lastName WHERE id=:id", parameterSource); + updateVetSpecialties(vet); + } + } + + @Override + public void delete(Vet vet) throws DataAccessException { + Map params = new HashMap<>(); + params.put("id", vet.getId()); + this.namedParameterJdbcTemplate.update("DELETE FROM vet_specialties WHERE vet_id=:id", params); + this.namedParameterJdbcTemplate.update("DELETE FROM vets WHERE id=:id", params); + } + + private void updateVetSpecialties(Vet vet) throws DataAccessException { + Map params = new HashMap<>(); + params.put("id", vet.getId()); + this.namedParameterJdbcTemplate.update("DELETE FROM vet_specialties WHERE vet_id=:id", params); + for (Specialty spec : vet.getSpecialties()) { + params.put("spec_id", spec.getId()); + if(!(spec.getId() == null)) { + this.namedParameterJdbcTemplate.update("INSERT INTO vet_specialties VALUES (:id, :spec_id)", params); + } + } + } + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcVisitRepositoryImpl.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcVisitRepositoryImpl.java new file mode 100644 index 0000000..b8e6ea9 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcVisitRepositoryImpl.java @@ -0,0 +1,177 @@ +/* + * Copyright 2002-2017 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 + * + * http://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.repository.jdbc; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Profile; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.BeanPropertyRowMapper; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.core.simple.SimpleJdbcInsert; +import org.springframework.orm.ObjectRetrievalFailureException; +import org.springframework.samples.petclinic.model.Owner; +import org.springframework.samples.petclinic.model.PetType; +import org.springframework.samples.petclinic.model.Visit; +import org.springframework.samples.petclinic.repository.VisitRepository; +import org.springframework.stereotype.Repository; + +import javax.sql.DataSource; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.*; + +/** + * A simple JDBC-based implementation of the {@link VisitRepository} interface. + * + * @author Ken Krebs + * @author Juergen Hoeller + * @author Rob Harrop + * @author Sam Brannen + * @author Thomas Risberg + * @author Mark Fisher + * @author Michael Isvy + * @author Vitaliy Fedoriv + */ +@Repository +@Profile("jdbc") +public class JdbcVisitRepositoryImpl implements VisitRepository { + + protected SimpleJdbcInsert insertVisit; + private NamedParameterJdbcTemplate namedParameterJdbcTemplate; + + @Autowired + public JdbcVisitRepositoryImpl(DataSource dataSource) { + this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource); + + this.insertVisit = new SimpleJdbcInsert(dataSource) + .withTableName("visits") + .usingGeneratedKeyColumns("id"); + } + + + /** + * Creates a {@link MapSqlParameterSource} based on data values from the supplied {@link Visit} instance. + */ + protected MapSqlParameterSource createVisitParameterSource(Visit visit) { + return new MapSqlParameterSource() + .addValue("id", visit.getId()) + .addValue("visit_date", visit.getDate()) + .addValue("description", visit.getDescription()) + .addValue("pet_id", visit.getPet().getId()); + } + + @Override + public List findByPetId(Integer petId) { + Map params = new HashMap<>(); + params.put("id", petId); + JdbcPet pet = this.namedParameterJdbcTemplate.queryForObject( + "SELECT id as pets_id, name, birth_date, type_id, owner_id FROM pets WHERE id=:id", + params, + new JdbcPetRowMapper()); + + List visits = this.namedParameterJdbcTemplate.query( + "SELECT id as visit_id, visit_date, description FROM visits WHERE pet_id=:id", + params, new JdbcVisitRowMapper()); + + for (Visit visit : visits) { + visit.setPet(pet); + } + + return visits; + } + + @Override + public Visit findById(int id) throws DataAccessException { + Visit visit; + try { + Map params = new HashMap<>(); + params.put("id", id); + visit = this.namedParameterJdbcTemplate.queryForObject( + "SELECT id as visit_id, visits.pet_id as pets_id, visit_date, description FROM visits WHERE id= :id", + params, + new JdbcVisitRowMapperExt()); + } catch (EmptyResultDataAccessException ex) { + throw new ObjectRetrievalFailureException(Visit.class, id); + } + return visit; + } + + @Override + public Collection findAll() throws DataAccessException { + Map params = new HashMap<>(); + return this.namedParameterJdbcTemplate.query( + "SELECT id as visit_id, pets.id as pets_id, visit_date, description FROM visits LEFT JOIN pets ON visits.pet_id = pets.id", + params, new JdbcVisitRowMapperExt()); + } + + @Override + public void save(Visit visit) throws DataAccessException { + if (visit.isNew()) { + Number newKey = this.insertVisit.executeAndReturnKey(createVisitParameterSource(visit)); + visit.setId(newKey.intValue()); + } else { + this.namedParameterJdbcTemplate.update( + "UPDATE visits SET visit_date=:visit_date, description=:description, pet_id=:pet_id WHERE id=:id ", + createVisitParameterSource(visit)); + } + } + + @Override + public void delete(Visit visit) throws DataAccessException { + Map params = new HashMap<>(); + params.put("id", visit.getId()); + this.namedParameterJdbcTemplate.update("DELETE FROM visits WHERE id=:id", params); + } + + protected class JdbcVisitRowMapperExt implements RowMapper { + + @Override + public Visit mapRow(ResultSet rs, int rowNum) throws SQLException { + Visit visit = new Visit(); + JdbcPet pet = new JdbcPet(); + PetType petType = new PetType(); + Owner owner = new Owner(); + visit.setId(rs.getInt("visit_id")); + Date visitDate = rs.getDate("visit_date"); + visit.setDate(new java.sql.Date(visitDate.getTime()).toLocalDate()); + visit.setDescription(rs.getString("description")); + Map params = new HashMap<>(); + params.put("id", rs.getInt("pets_id")); + pet = JdbcVisitRepositoryImpl.this.namedParameterJdbcTemplate.queryForObject( + "SELECT pets.id as pets_id, name, birth_date, type_id, owner_id FROM pets WHERE pets.id=:id", + params, + new JdbcPetRowMapper()); + params.put("type_id", pet.getTypeId()); + petType = JdbcVisitRepositoryImpl.this.namedParameterJdbcTemplate.queryForObject( + "SELECT id, name FROM types WHERE id= :type_id", + params, + BeanPropertyRowMapper.newInstance(PetType.class)); + pet.setType(petType); + params.put("owner_id", pet.getOwnerId()); + owner = JdbcVisitRepositoryImpl.this.namedParameterJdbcTemplate.queryForObject( + "SELECT id, first_name, last_name, address, city, telephone FROM owners WHERE id= :owner_id", + params, + BeanPropertyRowMapper.newInstance(Owner.class)); + pet.setOwner(owner); + visit.setPet(pet); + return visit; + } + } + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcVisitRowMapper.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcVisitRowMapper.java new file mode 100644 index 0000000..9cab6c5 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcVisitRowMapper.java @@ -0,0 +1,41 @@ +/* + * Copyright 2002-2015 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 + * + * http://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.repository.jdbc; + + +import org.springframework.jdbc.core.RowMapper; +import org.springframework.samples.petclinic.model.Visit; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.LocalDate; +import java.util.Date; + +/** + * {@link RowMapper} implementation mapping data from a {@link ResultSet} to the corresponding properties + * of the {@link Visit} class. + */ +class JdbcVisitRowMapper implements RowMapper { + + @Override + public Visit mapRow(ResultSet rs, int row) throws SQLException { + Visit visit = new Visit(); + visit.setId(rs.getInt("visit_id")); + visit.setDate(rs.getObject("visit_date", LocalDate.class)); + visit.setDescription(rs.getString("description")); + return visit; + } +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/jdbc/package-info.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/jdbc/package-info.java new file mode 100644 index 0000000..d093503 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/jdbc/package-info.java @@ -0,0 +1,6 @@ +/** + * The classes in this package represent the JDBC implementation + * of PetClinic's persistence layer. + */ +package org.springframework.samples.petclinic.repository.jdbc; + diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaOwnerRepositoryImpl.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaOwnerRepositoryImpl.java new file mode 100644 index 0000000..3240d07 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaOwnerRepositoryImpl.java @@ -0,0 +1,96 @@ +/* + * Copyright 2002-2017 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 + * + * http://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.repository.jpa; + +import java.util.Collection; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.persistence.Query; + +import org.springframework.context.annotation.Profile; +import org.springframework.dao.DataAccessException; +import org.springframework.orm.hibernate5.support.OpenSessionInViewFilter; +import org.springframework.samples.petclinic.model.Owner; +import org.springframework.samples.petclinic.repository.OwnerRepository; +import org.springframework.stereotype.Repository; + +/** + * JPA implementation of the {@link OwnerRepository} interface. + * + * @author Mike Keith + * @author Rod Johnson + * @author Sam Brannen + * @author Michael Isvy + * @author Vitaliy Fedoriv + */ +@Repository +@Profile("jpa") +public class JpaOwnerRepositoryImpl implements OwnerRepository { + + @PersistenceContext + private EntityManager em; + + + /** + * Important: in the current version of this method, we load Owners with all their Pets and Visits while + * we do not need Visits at all and we only need one property from the Pet objects (the 'name' property). + * There are some ways to improve it such as: + * - creating a Ligtweight class (example here: https://community.jboss.org/wiki/LightweightClass) + * - Turning on lazy-loading and using {@link OpenSessionInViewFilter} + */ + @SuppressWarnings("unchecked") + public Collection findByLastName(String lastName) { + // using 'join fetch' because a single query should load both owners and pets + // using 'left join fetch' because it might happen that an owner does not have pets yet + Query query = this.em.createQuery("SELECT DISTINCT owner FROM Owner owner left join fetch owner.pets WHERE owner.lastName LIKE :lastName"); + query.setParameter("lastName", lastName + "%"); + return query.getResultList(); + } + + @Override + public Owner findById(int id) { + // using 'join fetch' because a single query should load both owners and pets + // using 'left join fetch' because it might happen that an owner does not have pets yet + Query query = this.em.createQuery("SELECT owner FROM Owner owner left join fetch owner.pets WHERE owner.id =:id"); + query.setParameter("id", id); + return (Owner) query.getSingleResult(); + } + + + @Override + public void save(Owner owner) { + if (owner.getId() == null) { + this.em.persist(owner); + } else { + this.em.merge(owner); + } + + } + + @SuppressWarnings("unchecked") + @Override + public Collection findAll() throws DataAccessException { + Query query = this.em.createQuery("SELECT owner FROM Owner owner"); + return query.getResultList(); + } + + @Override + public void delete(Owner owner) throws DataAccessException { + this.em.remove(this.em.contains(owner) ? owner : this.em.merge(owner)); + } + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaPetRepositoryImpl.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaPetRepositoryImpl.java new file mode 100644 index 0000000..8b04981 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaPetRepositoryImpl.java @@ -0,0 +1,84 @@ +/* + * Copyright 2002-2017 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 + * + * http://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.repository.jpa; + +import java.util.Collection; +import java.util.List; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; + +import org.springframework.context.annotation.Profile; +import org.springframework.dao.DataAccessException; +import org.springframework.samples.petclinic.model.Pet; +import org.springframework.samples.petclinic.model.PetType; +import org.springframework.samples.petclinic.repository.PetRepository; +import org.springframework.stereotype.Repository; + +/** + * JPA implementation of the {@link PetRepository} interface. + * + * @author Mike Keith + * @author Rod Johnson + * @author Sam Brannen + * @author Michael Isvy + * @author Vitaliy Fedoriv + */ +@Repository +@Profile("jpa") +public class JpaPetRepositoryImpl implements PetRepository { + + @PersistenceContext + private EntityManager em; + + @Override + @SuppressWarnings("unchecked") + public List findPetTypes() { + return this.em.createQuery("SELECT ptype FROM PetType ptype ORDER BY ptype.name").getResultList(); + } + + @Override + public Pet findById(int id) { + return this.em.find(Pet.class, id); + } + + @Override + public void save(Pet pet) { + if (pet.getId() == null) { + this.em.persist(pet); + } else { + this.em.merge(pet); + } + } + + @SuppressWarnings("unchecked") + @Override + public Collection findAll() throws DataAccessException { + return this.em.createQuery("SELECT pet FROM Pet pet").getResultList(); + } + + @Override + public void delete(Pet pet) throws DataAccessException { + //this.em.remove(this.em.contains(pet) ? pet : this.em.merge(pet)); + String petId = pet.getId().toString(); + this.em.createQuery("DELETE FROM Visit visit WHERE pet.id=" + petId).executeUpdate(); + this.em.createQuery("DELETE FROM Pet pet WHERE id=" + petId).executeUpdate(); + if (em.contains(pet)) { + em.remove(pet); + } + } + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaPetTypeRepositoryImpl.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaPetTypeRepositoryImpl.java new file mode 100644 index 0000000..184aa38 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaPetTypeRepositoryImpl.java @@ -0,0 +1,92 @@ +/* + * Copyright 2016-2017 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 + * + * http://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.repository.jpa; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; + +import org.springframework.context.annotation.Profile; +import org.springframework.dao.DataAccessException; +import org.springframework.samples.petclinic.model.Pet; +import org.springframework.samples.petclinic.model.PetType; +import org.springframework.samples.petclinic.model.Visit; +import org.springframework.samples.petclinic.repository.PetTypeRepository; +import org.springframework.stereotype.Repository; + +/** + * @author Vitaliy Fedoriv + * + */ + +@Repository +@Profile("jpa") +public class JpaPetTypeRepositoryImpl implements PetTypeRepository { + + @PersistenceContext + private EntityManager em; + + @Override + public PetType findById(int id) { + return this.em.find(PetType.class, id); + } + + @Override + public PetType findByName(String name) throws DataAccessException { + return this.em.createQuery("SELECT p FROM PetType p WHERE p.name = :name", PetType.class) + .setParameter("name", name) + .getSingleResult(); + } + + + @SuppressWarnings("unchecked") + @Override + public Collection findAll() throws DataAccessException { + return this.em.createQuery("SELECT ptype FROM PetType ptype").getResultList(); + } + + @Override + public void save(PetType petType) throws DataAccessException { + if (petType.getId() == null) { + this.em.persist(petType); + } else { + this.em.merge(petType); + } + + } + + @SuppressWarnings("unchecked") + @Override + public void delete(PetType petType) throws DataAccessException { + this.em.remove(this.em.contains(petType) ? petType : this.em.merge(petType)); + Integer petTypeId = petType.getId(); + + List pets = this.em.createQuery("SELECT pet FROM Pet pet WHERE type.id=" + petTypeId).getResultList(); + for (Pet pet : pets){ + List visits = pet.getVisits(); + for (Visit visit : visits){ + this.em.createQuery("DELETE FROM Visit visit WHERE id=" + visit.getId()).executeUpdate(); + } + this.em.createQuery("DELETE FROM Pet pet WHERE id=" + pet.getId()).executeUpdate(); + } + this.em.createQuery("DELETE FROM PetType pettype WHERE id=" + petTypeId).executeUpdate(); + } + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaSpecialtyRepositoryImpl.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaSpecialtyRepositoryImpl.java new file mode 100644 index 0000000..3c95587 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaSpecialtyRepositoryImpl.java @@ -0,0 +1,80 @@ +/* + * Copyright 2016-2017 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 + * + * http://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.repository.jpa; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; + +import org.springframework.context.annotation.Profile; +import org.springframework.dao.DataAccessException; +import org.springframework.samples.petclinic.model.Specialty; +import org.springframework.samples.petclinic.repository.SpecialtyRepository; +import org.springframework.stereotype.Repository; + +/** + * @author Vitaliy Fedoriv + * + */ + +@Repository +@Profile("jpa") +public class JpaSpecialtyRepositoryImpl implements SpecialtyRepository { + + @PersistenceContext + private EntityManager em; + + @Override + public Specialty findById(int id) { + return this.em.find(Specialty.class, id); + } + + @Override + public List findSpecialtiesByNameIn(Set names) { + final String jpql = "SELECT s FROM Specialty s WHERE s.name IN :names"; + return em.createQuery(jpql, Specialty.class) + .setParameter("names", names) + .getResultList(); + } + + @SuppressWarnings("unchecked") + @Override + public Collection findAll() throws DataAccessException { + return this.em.createQuery("SELECT s FROM Specialty s").getResultList(); + } + + @Override + public void save(Specialty specialty) throws DataAccessException { + if (specialty.getId() == null) { + this.em.persist(specialty); + } else { + this.em.merge(specialty); + } + } + + @Override + public void delete(Specialty specialty) throws DataAccessException { + this.em.remove(this.em.contains(specialty) ? specialty : this.em.merge(specialty)); + Integer specId = specialty.getId(); + this.em.createNativeQuery("DELETE FROM vet_specialties WHERE specialty_id=" + specId).executeUpdate(); + this.em.createQuery("DELETE FROM Specialty specialty WHERE id=" + specId).executeUpdate(); + } + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaUserRepositoryImpl.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaUserRepositoryImpl.java new file mode 100644 index 0000000..de3fd4e --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaUserRepositoryImpl.java @@ -0,0 +1,27 @@ +package org.springframework.samples.petclinic.repository.jpa; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; + +import org.springframework.context.annotation.Profile; +import org.springframework.dao.DataAccessException; +import org.springframework.samples.petclinic.model.User; +import org.springframework.samples.petclinic.repository.UserRepository; +import org.springframework.stereotype.Repository; + +@Repository +@Profile("jpa") +public class JpaUserRepositoryImpl implements UserRepository { + + @PersistenceContext + private EntityManager em; + + @Override + public void save(User user) throws DataAccessException { + if (this.em.find(User.class, user.getUsername()) == null) { + this.em.persist(user); + } else { + this.em.merge(user); + } + } +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaVetRepositoryImpl.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaVetRepositoryImpl.java new file mode 100644 index 0000000..eed4dbb --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaVetRepositoryImpl.java @@ -0,0 +1,71 @@ +/* + * Copyright 2002-2017 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 + * + * http://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.repository.jpa; + +import org.springframework.context.annotation.Profile; +import org.springframework.dao.DataAccessException; +import org.springframework.samples.petclinic.model.Vet; +import org.springframework.samples.petclinic.repository.VetRepository; +import org.springframework.stereotype.Repository; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import java.util.Collection; + +/** + * JPA implementation of the {@link VetRepository} interface. + * + * @author Mike Keith + * @author Rod Johnson + * @author Sam Brannen + * @author Michael Isvy + * @author Vitaliy Fedoriv + */ +@Repository +@Profile("jpa") +public class JpaVetRepositoryImpl implements VetRepository { + + @PersistenceContext + private EntityManager em; + + + @Override + public Vet findById(int id) throws DataAccessException { + return this.em.find(Vet.class, id); + } + + @SuppressWarnings("unchecked") + @Override + public Collection findAll() throws DataAccessException { + return this.em.createQuery("SELECT vet FROM Vet vet").getResultList(); + } + + @Override + public void save(Vet vet) throws DataAccessException { + if (vet.getId() == null) { + this.em.persist(vet); + } else { + this.em.merge(vet); + } + } + + @Override + public void delete(Vet vet) throws DataAccessException { + this.em.remove(this.em.contains(vet) ? vet : this.em.merge(vet)); + } + + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaVisitRepositoryImpl.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaVisitRepositoryImpl.java new file mode 100644 index 0000000..79d0325 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaVisitRepositoryImpl.java @@ -0,0 +1,84 @@ +/* + * Copyright 2002-2017 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 + * + * http://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.repository.jpa; + +import java.util.Collection; +import java.util.List; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.persistence.Query; + +import org.springframework.context.annotation.Profile; +import org.springframework.dao.DataAccessException; +import org.springframework.samples.petclinic.model.Visit; +import org.springframework.samples.petclinic.repository.VisitRepository; +import org.springframework.stereotype.Repository; + +/** + * JPA implementation of the ClinicService interface using EntityManager. + *

+ *

The mappings are defined in "orm.xml" located in the META-INF directory. + * + * @author Mike Keith + * @author Rod Johnson + * @author Sam Brannen + * @author Michael Isvy + * @author Vitaliy Fedoriv + */ +@Repository +@Profile("jpa") +public class JpaVisitRepositoryImpl implements VisitRepository { + + @PersistenceContext + private EntityManager em; + + + @Override + public void save(Visit visit) { + if (visit.getId() == null) { + this.em.persist(visit); + } else { + this.em.merge(visit); + } + } + + + @Override + @SuppressWarnings("unchecked") + public List findByPetId(Integer petId) { + Query query = this.em.createQuery("SELECT v FROM Visit v where v.pet.id= :id"); + query.setParameter("id", petId); + return query.getResultList(); + } + + @Override + public Visit findById(int id) throws DataAccessException { + return this.em.find(Visit.class, id); + } + + @SuppressWarnings("unchecked") + @Override + public Collection findAll() throws DataAccessException { + return this.em.createQuery("SELECT v FROM Visit v").getResultList(); + } + + @Override + public void delete(Visit visit) throws DataAccessException { + this.em.remove(this.em.contains(visit) ? visit : this.em.merge(visit)); + } + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/jpa/package-info.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/jpa/package-info.java new file mode 100644 index 0000000..04fd89b --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/jpa/package-info.java @@ -0,0 +1,6 @@ +/** + * The classes in this package represent the JPA implementation + * of PetClinic's persistence layer. + */ +package org.springframework.samples.petclinic.repository.jpa; + diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/PetRepositoryOverride.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/PetRepositoryOverride.java new file mode 100644 index 0000000..78c02b0 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/PetRepositoryOverride.java @@ -0,0 +1,32 @@ +/* + * Copyright 2016-2017 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 + * + * http://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.repository.springdatajpa; + +import org.springframework.context.annotation.Profile; +import org.springframework.samples.petclinic.model.Pet; + +/** + * @author Vitaliy Fedoriv + * + */ + +@Profile("spring-data-jpa") +public interface PetRepositoryOverride { + + void delete(Pet pet); + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/PetTypeRepositoryOverride.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/PetTypeRepositoryOverride.java new file mode 100644 index 0000000..a3b19e9 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/PetTypeRepositoryOverride.java @@ -0,0 +1,32 @@ +/* + * Copyright 2016-2017 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 + * + * http://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.repository.springdatajpa; + +import org.springframework.context.annotation.Profile; +import org.springframework.samples.petclinic.model.PetType; + +/** + * @author Vitaliy Fedoriv + * + */ + +@Profile("spring-data-jpa") +public interface PetTypeRepositoryOverride { + + void delete(PetType petType); + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpecialtyRepositoryOverride.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpecialtyRepositoryOverride.java new file mode 100644 index 0000000..656d8ef --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpecialtyRepositoryOverride.java @@ -0,0 +1,32 @@ +/* + * Copyright 2016-2017 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 + * + * http://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.repository.springdatajpa; + +import org.springframework.context.annotation.Profile; +import org.springframework.samples.petclinic.model.Specialty; + +/** + * @author Vitaliy Fedoriv + * + */ + +@Profile("spring-data-jpa") +public interface SpecialtyRepositoryOverride { + + void delete(Specialty specialty); + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataOwnerRepository.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataOwnerRepository.java new file mode 100644 index 0000000..78c0093 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataOwnerRepository.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2013 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 + * + * http://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.repository.springdatajpa; + +import java.util.Collection; + +import org.springframework.context.annotation.Profile; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.Repository; +import org.springframework.data.repository.query.Param; +import org.springframework.samples.petclinic.model.Owner; +import org.springframework.samples.petclinic.repository.OwnerRepository; + +/** + * Spring Data JPA specialization of the {@link OwnerRepository} interface + * + * @author Michael Isvy + * @since 15.1.2013 + */ + +@Profile("spring-data-jpa") +public interface SpringDataOwnerRepository extends OwnerRepository, Repository { + + @Override + @Query("SELECT DISTINCT owner FROM Owner owner left join fetch owner.pets WHERE owner.lastName LIKE :lastName%") + Collection findByLastName(@Param("lastName") String lastName); + + @Override + @Query("SELECT owner FROM Owner owner left join fetch owner.pets WHERE owner.id =:id") + Owner findById(@Param("id") int id); +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataPetRepository.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataPetRepository.java new file mode 100644 index 0000000..501d398 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataPetRepository.java @@ -0,0 +1,41 @@ +/* + * Copyright 2002-2017 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 + * + * http://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.repository.springdatajpa; + +import java.util.List; + +import org.springframework.context.annotation.Profile; +import org.springframework.dao.DataAccessException; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.Repository; +import org.springframework.samples.petclinic.model.Pet; +import org.springframework.samples.petclinic.model.PetType; +import org.springframework.samples.petclinic.repository.PetRepository; + +/** + * Spring Data JPA specialization of the {@link PetRepository} interface + * + * @author Michael Isvy + * @author Vitaliy Fedoriv + */ + +@Profile("spring-data-jpa") +public interface SpringDataPetRepository extends PetRepository, Repository, PetRepositoryOverride { + + @Override + @Query("SELECT ptype FROM PetType ptype ORDER BY ptype.name") + List findPetTypes() throws DataAccessException; +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataPetRepositoryImpl.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataPetRepositoryImpl.java new file mode 100644 index 0000000..4299505 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataPetRepositoryImpl.java @@ -0,0 +1,46 @@ +/* + * Copyright 2016-2017 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 + * + * http://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.repository.springdatajpa; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; + +import org.springframework.context.annotation.Profile; +import org.springframework.samples.petclinic.model.Pet; + +/** + * @author Vitaliy Fedoriv + * + */ + +@Profile("spring-data-jpa") +public class SpringDataPetRepositoryImpl implements PetRepositoryOverride { + + @PersistenceContext + private EntityManager em; + + @Override + public void delete(Pet pet) { + String petId = pet.getId().toString(); + this.em.createQuery("DELETE FROM Visit visit WHERE pet.id=" + petId).executeUpdate(); + this.em.createQuery("DELETE FROM Pet pet WHERE id=" + petId).executeUpdate(); + if (em.contains(pet)) { + em.remove(pet); + } + } + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataPetTypeRepository.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataPetTypeRepository.java new file mode 100644 index 0000000..18a6979 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataPetTypeRepository.java @@ -0,0 +1,32 @@ +/* + * Copyright 2016-2017 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 + * + * http://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.repository.springdatajpa; + +import org.springframework.context.annotation.Profile; +import org.springframework.data.repository.Repository; +import org.springframework.samples.petclinic.model.PetType; +import org.springframework.samples.petclinic.repository.PetTypeRepository; + +/** + * @author Vitaliy Fedoriv + * + */ + +@Profile("spring-data-jpa") +public interface SpringDataPetTypeRepository extends PetTypeRepository, Repository, PetTypeRepositoryOverride { + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataPetTypeRepositoryImpl.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataPetTypeRepositoryImpl.java new file mode 100644 index 0000000..5350c4e --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataPetTypeRepositoryImpl.java @@ -0,0 +1,56 @@ +/* + * Copyright 2016-2017 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 + * + * http://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.repository.springdatajpa; + +import org.springframework.context.annotation.Profile; +import org.springframework.samples.petclinic.model.Pet; +import org.springframework.samples.petclinic.model.PetType; +import org.springframework.samples.petclinic.model.Visit; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import java.util.List; + +/** + * @author Vitaliy Fedoriv + * + */ + +@Profile("spring-data-jpa") +public class SpringDataPetTypeRepositoryImpl implements PetTypeRepositoryOverride { + + @PersistenceContext + private EntityManager em; + + @SuppressWarnings("unchecked") + @Override + public void delete(PetType petType) { + this.em.remove(this.em.contains(petType) ? petType : this.em.merge(petType)); + Integer petTypeId = petType.getId(); + + List pets = this.em.createQuery("SELECT pet FROM Pet pet WHERE type.id=" + petTypeId).getResultList(); + for (Pet pet : pets){ + List visits = pet.getVisits(); + for (Visit visit : visits){ + this.em.createQuery("DELETE FROM Visit visit WHERE id=" + visit.getId()).executeUpdate(); + } + this.em.createQuery("DELETE FROM Pet pet WHERE id=" + pet.getId()).executeUpdate(); + } + this.em.createQuery("DELETE FROM PetType pettype WHERE id=" + petTypeId).executeUpdate(); + } + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataSpecialtyRepository.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataSpecialtyRepository.java new file mode 100644 index 0000000..5ec7364 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataSpecialtyRepository.java @@ -0,0 +1,33 @@ +/* + * Copyright 2016-2017 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 + * + * http://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.repository.springdatajpa; + +import org.springframework.context.annotation.Profile; +import org.springframework.data.repository.Repository; +import org.springframework.samples.petclinic.model.Specialty; +import org.springframework.samples.petclinic.repository.SpecialtyRepository; + + +/** + * @author Vitaliy Fedoriv + * + */ + +@Profile("spring-data-jpa") +public interface SpringDataSpecialtyRepository extends SpecialtyRepository, Repository, SpecialtyRepositoryOverride { + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataSpecialtyRepositoryImpl.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataSpecialtyRepositoryImpl.java new file mode 100644 index 0000000..940625f --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataSpecialtyRepositoryImpl.java @@ -0,0 +1,44 @@ +/* + * Copyright 2016-2017 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 + * + * http://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.repository.springdatajpa; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; + +import org.springframework.context.annotation.Profile; +import org.springframework.samples.petclinic.model.Specialty; + +/** + * @author Vitaliy Fedoriv + * + */ + +@Profile("spring-data-jpa") +public class SpringDataSpecialtyRepositoryImpl implements SpecialtyRepositoryOverride { + + @PersistenceContext + private EntityManager em; + + @Override + public void delete(Specialty specialty) { + this.em.remove(this.em.contains(specialty) ? specialty : this.em.merge(specialty)); + Integer specId = specialty.getId(); + this.em.createNativeQuery("DELETE FROM vet_specialties WHERE specialty_id=" + specId).executeUpdate(); + this.em.createQuery("DELETE FROM Specialty specialty WHERE id=" + specId).executeUpdate(); + } + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataUserRepository.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataUserRepository.java new file mode 100644 index 0000000..a698c82 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataUserRepository.java @@ -0,0 +1,11 @@ +package org.springframework.samples.petclinic.repository.springdatajpa; + +import org.springframework.context.annotation.Profile; +import org.springframework.data.repository.Repository; +import org.springframework.samples.petclinic.model.User; +import org.springframework.samples.petclinic.repository.UserRepository; + +@Profile("spring-data-jpa") +public interface SpringDataUserRepository extends UserRepository, Repository { + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataVetRepository.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataVetRepository.java new file mode 100644 index 0000000..c52e440 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataVetRepository.java @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2013 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 + * + * http://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.repository.springdatajpa; + +import org.springframework.context.annotation.Profile; +import org.springframework.data.repository.Repository; +import org.springframework.samples.petclinic.model.Vet; +import org.springframework.samples.petclinic.repository.VetRepository; + +/** + * Spring Data JPA specialization of the {@link VetRepository} interface + * + * @author Michael Isvy + * @since 15.1.2013 + */ + +@Profile("spring-data-jpa") +public interface SpringDataVetRepository extends VetRepository, Repository { +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataVisitRepository.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataVisitRepository.java new file mode 100644 index 0000000..1b3c6c3 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataVisitRepository.java @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2017 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 + * + * http://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.repository.springdatajpa; + +import org.springframework.context.annotation.Profile; +import org.springframework.data.repository.Repository; +import org.springframework.samples.petclinic.model.Visit; +import org.springframework.samples.petclinic.repository.VisitRepository; + +/** + * Spring Data JPA specialization of the {@link VisitRepository} interface + * + * @author Michael Isvy + * @author Vitaliy Fedoriv + */ + +@Profile("spring-data-jpa") +public interface SpringDataVisitRepository extends VisitRepository, Repository, VisitRepositoryOverride { +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataVisitRepositoryImpl.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataVisitRepositoryImpl.java new file mode 100644 index 0000000..4c2fcc0 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/SpringDataVisitRepositoryImpl.java @@ -0,0 +1,47 @@ +/* + * Copyright 2016-2017 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 + * + * http://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.repository.springdatajpa; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; + +import org.springframework.context.annotation.Profile; +import org.springframework.dao.DataAccessException; +import org.springframework.samples.petclinic.model.Visit; + +/** + * @author Vitaliy Fedoriv + * + */ + +@Profile("spring-data-jpa") +public class SpringDataVisitRepositoryImpl implements VisitRepositoryOverride { + + @PersistenceContext + private EntityManager em; + + @Override + public void delete(Visit visit) throws DataAccessException { + String visitId = visit.getId().toString(); + this.em.createQuery("DELETE FROM Visit visit WHERE id=" + visitId).executeUpdate(); + if (em.contains(visit)) { + em.remove(visit); + } + } + + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/VisitRepositoryOverride.java b/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/VisitRepositoryOverride.java new file mode 100644 index 0000000..7a622f3 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/repository/springdatajpa/VisitRepositoryOverride.java @@ -0,0 +1,32 @@ +/* + * Copyright 2016-2017 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 + * + * http://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.repository.springdatajpa; + +import org.springframework.context.annotation.Profile; +import org.springframework.samples.petclinic.model.Visit; + +/** + * @author Vitaliy Fedoriv + * + */ + +@Profile("spring-data-jpa") +public interface VisitRepositoryOverride { + + void delete(Visit visit); + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/rest/advice/ExceptionControllerAdvice.java b/backend/src/main/java/org/springframework/samples/petclinic/rest/advice/ExceptionControllerAdvice.java new file mode 100644 index 0000000..a01b1eb --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/rest/advice/ExceptionControllerAdvice.java @@ -0,0 +1,109 @@ +/* + * Copyright 2016 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 + * + * http://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.rest.advice; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.samples.petclinic.rest.controller.BindingErrorsResponse; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.context.request.WebRequest; + +import static org.springframework.http.HttpStatus.BAD_REQUEST; + +/** + * Global Exception handler for REST controllers. + *

+ * This class handles exceptions thrown by REST controllers and returns + * appropriate HTTP responses to the client. + * + * @author Vitaliy Fedoriv + * @author Alexander Dudkin + */ +@ControllerAdvice +public class ExceptionControllerAdvice { + + /** + * Record for storing error information. + *

+ * This record encapsulates the class name and message of the exception. + * + * @param className The name of the exception class + * @param exMessage The message of the exception + */ + private record ErrorInfo(String className, String exMessage) { + public ErrorInfo(Exception ex) { + this(ex.getClass().getName(), ex.getLocalizedMessage()); + } + } + + /** + * Handles all general exceptions by returning a 500 Internal Server Error status with error details. + * + * @param e The exception to be handled + * @return A {@link ResponseEntity} containing the error information and a 500 Internal Server Error status + */ + @ExceptionHandler(Exception.class) + public ResponseEntity handleGeneralException(Exception e) { + ErrorInfo info = new ErrorInfo(e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(info); + } + + /** + * Handles {@link DataIntegrityViolationException} which typically indicates database constraint violations. + * This method returns a 404 Not Found status if an entity does not exist. + * + * @param ex The {@link DataIntegrityViolationException} to be handled + * @return A {@link ResponseEntity} containing the error information and a 404 Not Found status + */ + @ExceptionHandler(DataIntegrityViolationException.class) + @ResponseStatus(code = HttpStatus.NOT_FOUND) + @ResponseBody + public ResponseEntity handleDataIntegrityViolationException(DataIntegrityViolationException ex) { + ErrorInfo errorInfo = new ErrorInfo(ex); + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorInfo); + } + + /** + * Handles exception thrown by Bean Validation on controller methods parameters + * + * @param ex The thrown exception + * + * @return an empty response entity + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(BAD_REQUEST) + @ResponseBody + public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) { + BindingErrorsResponse errors = new BindingErrorsResponse(); + BindingResult bindingResult = ex.getBindingResult(); + if (bindingResult.hasErrors()) { + errors.addAllErrors(bindingResult); + return ResponseEntity.badRequest().body(new ErrorInfo("MethodArgumentNotValidException", "Validation failed")); + } + return ResponseEntity.badRequest().build(); + } + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/rest/controller/BindingErrorsResponse.java b/backend/src/main/java/org/springframework/samples/petclinic/rest/controller/BindingErrorsResponse.java new file mode 100644 index 0000000..0c77284 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/rest/controller/BindingErrorsResponse.java @@ -0,0 +1,137 @@ +/* + * Copyright 2016 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 + * + * http://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.rest.controller; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.validation.BindingResult; +import org.springframework.validation.FieldError; + +import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * @author Vitaliy Fedoriv + * + */ + +public class BindingErrorsResponse { + + public BindingErrorsResponse() { + this(null); + } + + public BindingErrorsResponse(Integer id) { + this(null, id); + } + + public BindingErrorsResponse(Integer pathId, Integer bodyId) { + boolean onlyBodyIdSpecified = pathId == null && bodyId != null; + if (onlyBodyIdSpecified) { + addBodyIdError(bodyId, "must not be specified"); + } + boolean bothIdsSpecified = pathId != null && bodyId != null; + if (bothIdsSpecified && !pathId.equals(bodyId)) { + addBodyIdError(bodyId, String.format("does not match pathId: %d", pathId)); + } + } + + private void addBodyIdError(Integer bodyId, String message) { + BindingError error = new BindingError(); + error.setObjectName("body"); + error.setFieldName("id"); + error.setFieldValue(bodyId.toString()); + error.setErrorMessage(message); + addError(error); + } + + private final List bindingErrors = new ArrayList(); + + public void addError(BindingError bindingError) { + this.bindingErrors.add(bindingError); + } + + public void addAllErrors(BindingResult bindingResult) { + for (FieldError fieldError : bindingResult.getFieldErrors()) { + BindingError error = new BindingError(); + error.setObjectName(fieldError.getObjectName()); + error.setFieldName(fieldError.getField()); + error.setFieldValue(String.valueOf(fieldError.getRejectedValue())); + error.setErrorMessage(fieldError.getDefaultMessage()); + addError(error); + } + } + + public String toJSON() { + ObjectMapper mapper = new ObjectMapper(); + mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY); + String errorsAsJSON = ""; + try { + errorsAsJSON = mapper.writeValueAsString(bindingErrors); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + return errorsAsJSON; + } + + @Override + public String toString() { + return "BindingErrorsResponse [bindingErrors=" + bindingErrors + "]"; + } + + protected static class BindingError { + + private String objectName; + private String fieldName; + private String fieldValue; + private String errorMessage; + + public BindingError() { + this.objectName = ""; + this.fieldName = ""; + this.fieldValue = ""; + this.errorMessage = ""; + } + + protected void setObjectName(String objectName) { + this.objectName = objectName; + } + + protected void setFieldName(String fieldName) { + this.fieldName = fieldName; + } + + protected void setFieldValue(String fieldValue) { + this.fieldValue = fieldValue; + } + + protected void setErrorMessage(String error_message) { + this.errorMessage = error_message; + } + + @Override + public String toString() { + return "BindingError [objectName=" + objectName + ", fieldName=" + fieldName + ", fieldValue=" + fieldValue + + ", errorMessage=" + errorMessage + "]"; + } + + } + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/rest/controller/OwnerRestController.java b/backend/src/main/java/org/springframework/samples/petclinic/rest/controller/OwnerRestController.java new file mode 100644 index 0000000..906f8f5 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/rest/controller/OwnerRestController.java @@ -0,0 +1,177 @@ +/* + * Copyright 2016-2017 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 + * + * http://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.rest.controller; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.samples.petclinic.mapper.OwnerMapper; +import org.springframework.samples.petclinic.mapper.PetMapper; +import org.springframework.samples.petclinic.mapper.VisitMapper; +import org.springframework.samples.petclinic.model.Owner; +import org.springframework.samples.petclinic.model.Pet; +import org.springframework.samples.petclinic.model.Visit; +import org.springframework.samples.petclinic.rest.api.OwnersApi; +import org.springframework.samples.petclinic.rest.dto.*; +import org.springframework.samples.petclinic.service.ClinicService; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.util.UriComponentsBuilder; + +import jakarta.transaction.Transactional; + +import java.util.Collection; +import java.util.List; + +/** + * @author Vitaliy Fedoriv + */ + +@RestController +@CrossOrigin(exposedHeaders = "errors, content-type") +@RequestMapping("/api") +public class OwnerRestController implements OwnersApi { + + private final ClinicService clinicService; + + private final OwnerMapper ownerMapper; + + private final PetMapper petMapper; + + private final VisitMapper visitMapper; + + public OwnerRestController(ClinicService clinicService, + OwnerMapper ownerMapper, + PetMapper petMapper, + VisitMapper visitMapper) { + this.clinicService = clinicService; + this.ownerMapper = ownerMapper; + this.petMapper = petMapper; + this.visitMapper = visitMapper; + } + + @PreAuthorize("hasRole(@roles.OWNER_ADMIN)") + @Override + public ResponseEntity> listOwners(String lastName) { + Collection owners; + if (lastName != null) { + owners = this.clinicService.findOwnerByLastName(lastName); + } else { + owners = this.clinicService.findAllOwners(); + } + if (owners.isEmpty()) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + return new ResponseEntity<>(ownerMapper.toOwnerDtoCollection(owners), HttpStatus.OK); + } + + @PreAuthorize("hasRole(@roles.OWNER_ADMIN)") + @Override + public ResponseEntity getOwner(Integer ownerId) { + Owner owner = this.clinicService.findOwnerById(ownerId); + if (owner == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + return new ResponseEntity<>(ownerMapper.toOwnerDto(owner), HttpStatus.OK); + } + + @PreAuthorize("hasRole(@roles.OWNER_ADMIN)") + @Override + public ResponseEntity addOwner(OwnerFieldsDto ownerFieldsDto) { + HttpHeaders headers = new HttpHeaders(); + Owner owner = ownerMapper.toOwner(ownerFieldsDto); + this.clinicService.saveOwner(owner); + OwnerDto ownerDto = ownerMapper.toOwnerDto(owner); + headers.setLocation(UriComponentsBuilder.newInstance() + .path("/api/owners/{id}").buildAndExpand(owner.getId()).toUri()); + return new ResponseEntity<>(ownerDto, headers, HttpStatus.CREATED); + } + + @PreAuthorize("hasRole(@roles.OWNER_ADMIN)") + @Override + public ResponseEntity updateOwner(Integer ownerId, OwnerFieldsDto ownerFieldsDto) { + Owner currentOwner = this.clinicService.findOwnerById(ownerId); + if (currentOwner == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + currentOwner.setAddress(ownerFieldsDto.getAddress()); + currentOwner.setCity(ownerFieldsDto.getCity()); + currentOwner.setFirstName(ownerFieldsDto.getFirstName()); + currentOwner.setLastName(ownerFieldsDto.getLastName()); + currentOwner.setTelephone(ownerFieldsDto.getTelephone()); + this.clinicService.saveOwner(currentOwner); + return new ResponseEntity<>(ownerMapper.toOwnerDto(currentOwner), HttpStatus.NO_CONTENT); + } + + @PreAuthorize("hasRole(@roles.OWNER_ADMIN)") + @Transactional + @Override + public ResponseEntity deleteOwner(Integer ownerId) { + Owner owner = this.clinicService.findOwnerById(ownerId); + if (owner == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + this.clinicService.deleteOwner(owner); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + + @PreAuthorize("hasRole(@roles.OWNER_ADMIN)") + @Override + public ResponseEntity addPetToOwner(Integer ownerId, PetFieldsDto petFieldsDto) { + HttpHeaders headers = new HttpHeaders(); + Pet pet = petMapper.toPet(petFieldsDto); + Owner owner = new Owner(); + owner.setId(ownerId); + pet.setOwner(owner); + this.clinicService.savePet(pet); + PetDto petDto = petMapper.toPetDto(pet); + headers.setLocation(UriComponentsBuilder.newInstance().path("/api/pets/{id}") + .buildAndExpand(pet.getId()).toUri()); + return new ResponseEntity<>(petDto, headers, HttpStatus.CREATED); + } + + @PreAuthorize("hasRole(@roles.OWNER_ADMIN)") + @Override + public ResponseEntity addVisitToOwner(Integer ownerId, Integer petId, VisitFieldsDto visitFieldsDto) { + HttpHeaders headers = new HttpHeaders(); + Visit visit = visitMapper.toVisit(visitFieldsDto); + Pet pet = new Pet(); + pet.setId(petId); + visit.setPet(pet); + this.clinicService.saveVisit(visit); + VisitDto visitDto = visitMapper.toVisitDto(visit); + headers.setLocation(UriComponentsBuilder.newInstance().path("/api/visits/{id}") + .buildAndExpand(visit.getId()).toUri()); + return new ResponseEntity<>(visitDto, headers, HttpStatus.CREATED); + } + + + @PreAuthorize("hasRole(@roles.OWNER_ADMIN)") + @Override + public ResponseEntity getOwnersPet(Integer ownerId, Integer petId) { + Owner owner = this.clinicService.findOwnerById(ownerId); + if (owner != null) { + Pet pet = owner.getPet(petId); + if (pet != null) { + return new ResponseEntity<>(petMapper.toPetDto(pet), HttpStatus.OK); + } + } + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/rest/controller/PetRestController.java b/backend/src/main/java/org/springframework/samples/petclinic/rest/controller/PetRestController.java new file mode 100644 index 0000000..e5727fd --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/rest/controller/PetRestController.java @@ -0,0 +1,109 @@ +/* + * Copyright 2016-2017 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 + * + * http://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.rest.controller; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.samples.petclinic.mapper.PetMapper; +import org.springframework.samples.petclinic.model.Pet; +import org.springframework.samples.petclinic.rest.api.PetsApi; +import org.springframework.samples.petclinic.rest.dto.PetDto; +import org.springframework.samples.petclinic.service.ClinicService; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.util.UriComponentsBuilder; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Vitaliy Fedoriv + */ + +@RestController +@CrossOrigin(exposedHeaders = "errors, content-type") +@RequestMapping("api") +public class PetRestController implements PetsApi { + + private final ClinicService clinicService; + + private final PetMapper petMapper; + + public PetRestController(ClinicService clinicService, PetMapper petMapper) { + this.clinicService = clinicService; + this.petMapper = petMapper; + } + + @PreAuthorize("hasRole(@roles.OWNER_ADMIN)") + @Override + public ResponseEntity getPet(Integer petId) { + PetDto pet = petMapper.toPetDto(this.clinicService.findPetById(petId)); + if (pet == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + return new ResponseEntity<>(pet, HttpStatus.OK); + } + + @PreAuthorize("hasRole(@roles.OWNER_ADMIN)") + @Override + public ResponseEntity> listPets() { + List pets = new ArrayList<>(petMapper.toPetsDto(this.clinicService.findAllPets())); + if (pets.isEmpty()) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + return new ResponseEntity<>(pets, HttpStatus.OK); + } + + + @PreAuthorize("hasRole(@roles.OWNER_ADMIN)") + @Override + public ResponseEntity updatePet(Integer petId, PetDto petDto) { + Pet currentPet = this.clinicService.findPetById(petId); + if (currentPet == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + currentPet.setBirthDate(petDto.getBirthDate()); + currentPet.setName(petDto.getName()); + currentPet.setType(petMapper.toPetType(petDto.getType())); + this.clinicService.savePet(currentPet); + return new ResponseEntity<>(petMapper.toPetDto(currentPet), HttpStatus.NO_CONTENT); + } + + @PreAuthorize("hasRole(@roles.OWNER_ADMIN)") + @Override + public ResponseEntity deletePet(Integer petId) { + Pet pet = this.clinicService.findPetById(petId); + if (pet == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + this.clinicService.deletePet(pet); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + + @PreAuthorize("hasRole(@roles.OWNER_ADMIN)") + @Override + public ResponseEntity addPet(PetDto petDto) { + HttpHeaders headers = new HttpHeaders(); + Pet pet = petMapper.toPet(petDto); + this.clinicService.savePet(pet); + headers.setLocation(UriComponentsBuilder.newInstance().path("/api/pets/{id}").buildAndExpand(pet.getId()).toUri()); + return new ResponseEntity<>(petMapper.toPetDto(pet), headers, HttpStatus.CREATED); + } +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/rest/controller/PetTypeRestController.java b/backend/src/main/java/org/springframework/samples/petclinic/rest/controller/PetTypeRestController.java new file mode 100644 index 0000000..3d0f423 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/rest/controller/PetTypeRestController.java @@ -0,0 +1,105 @@ +/* + * Copyright 2016-2017 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 + * + * http://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.rest.controller; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.samples.petclinic.mapper.PetTypeMapper; +import org.springframework.samples.petclinic.model.PetType; +import org.springframework.samples.petclinic.rest.api.PettypesApi; +import org.springframework.samples.petclinic.rest.dto.PetTypeDto; +import org.springframework.samples.petclinic.rest.dto.PetTypeFieldsDto; +import org.springframework.samples.petclinic.service.ClinicService; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.util.UriComponentsBuilder; + +import jakarta.transaction.Transactional; + +import java.util.ArrayList; +import java.util.List; + +@RestController +@CrossOrigin(exposedHeaders = "errors, content-type") +@RequestMapping("api") +public class PetTypeRestController implements PettypesApi { + + private final ClinicService clinicService; + private final PetTypeMapper petTypeMapper; + + + public PetTypeRestController(ClinicService clinicService, PetTypeMapper petTypeMapper) { + this.clinicService = clinicService; + this.petTypeMapper = petTypeMapper; + } + + @PreAuthorize("hasAnyRole(@roles.OWNER_ADMIN, @roles.VET_ADMIN)") + @Override + public ResponseEntity> listPetTypes() { + List petTypes = new ArrayList<>(this.clinicService.findAllPetTypes()); + if (petTypes.isEmpty()) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + return new ResponseEntity<>(petTypeMapper.toPetTypeDtos(petTypes), HttpStatus.OK); + } + + @PreAuthorize("hasAnyRole(@roles.OWNER_ADMIN, @roles.VET_ADMIN)") + @Override + public ResponseEntity getPetType(Integer petTypeId) { + PetType petType = this.clinicService.findPetTypeById(petTypeId); + if (petType == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + return new ResponseEntity<>(petTypeMapper.toPetTypeDto(petType), HttpStatus.OK); + } + + @PreAuthorize("hasRole(@roles.VET_ADMIN)") + @Override + public ResponseEntity addPetType(PetTypeFieldsDto petTypeFieldsDto) { + HttpHeaders headers = new HttpHeaders(); + final PetType type = petTypeMapper.toPetType(petTypeFieldsDto); + this.clinicService.savePetType(type); + headers.setLocation(UriComponentsBuilder.newInstance().path("/api/pettypes/{id}").buildAndExpand(type.getId()).toUri()); + return new ResponseEntity<>(petTypeMapper.toPetTypeDto(type), headers, HttpStatus.CREATED); + } + + @PreAuthorize("hasRole(@roles.VET_ADMIN)") + @Override + public ResponseEntity updatePetType(Integer petTypeId, PetTypeDto petTypeDto) { + PetType currentPetType = this.clinicService.findPetTypeById(petTypeId); + if (currentPetType == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + currentPetType.setName(petTypeDto.getName()); + this.clinicService.savePetType(currentPetType); + return new ResponseEntity<>(petTypeMapper.toPetTypeDto(currentPetType), HttpStatus.NO_CONTENT); + } + + @PreAuthorize("hasRole(@roles.VET_ADMIN)") + @Transactional + @Override + public ResponseEntity deletePetType(Integer petTypeId) { + PetType petType = this.clinicService.findPetTypeById(petTypeId); + if (petType == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + this.clinicService.deletePetType(petType); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/rest/controller/RootRestController.java b/backend/src/main/java/org/springframework/samples/petclinic/rest/controller/RootRestController.java new file mode 100644 index 0000000..7984f66 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/rest/controller/RootRestController.java @@ -0,0 +1,47 @@ +/* + * Copyright 2016-2018 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 + * + * http://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.rest.controller; + +import java.io.IOException; + +import jakarta.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author Vitaliy Fedoriv + * + */ + +@RestController +@CrossOrigin(exposedHeaders = "errors, content-type") +@RequestMapping("/") +public class RootRestController { + + @Value("#{servletContext.contextPath}") + private String servletContextPath; + + @RequestMapping(value = "/") + public void redirectToSwagger(HttpServletResponse response) throws IOException { + response.sendRedirect(this.servletContextPath + "/swagger-ui/index.html"); + } + +} + diff --git a/backend/src/main/java/org/springframework/samples/petclinic/rest/controller/SpecialtyRestController.java b/backend/src/main/java/org/springframework/samples/petclinic/rest/controller/SpecialtyRestController.java new file mode 100644 index 0000000..0b82e81 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/rest/controller/SpecialtyRestController.java @@ -0,0 +1,108 @@ +/* + * Copyright 2016-2017 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 + * + * http://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.rest.controller; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.samples.petclinic.mapper.SpecialtyMapper; +import org.springframework.samples.petclinic.model.Specialty; +import org.springframework.samples.petclinic.rest.api.SpecialtiesApi; +import org.springframework.samples.petclinic.rest.dto.SpecialtyDto; +import org.springframework.samples.petclinic.service.ClinicService; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.util.UriComponentsBuilder; + +import jakarta.transaction.Transactional; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Vitaliy Fedoriv + */ + +@RestController +@CrossOrigin(exposedHeaders = "errors, content-type") +@RequestMapping("api") +public class SpecialtyRestController implements SpecialtiesApi { + + private final ClinicService clinicService; + + private final SpecialtyMapper specialtyMapper; + + public SpecialtyRestController(ClinicService clinicService, SpecialtyMapper specialtyMapper) { + this.clinicService = clinicService; + this.specialtyMapper = specialtyMapper; + } + + @PreAuthorize("hasRole(@roles.VET_ADMIN)") + @Override + public ResponseEntity> listSpecialties() { + List specialties = new ArrayList<>(); + specialties.addAll(specialtyMapper.toSpecialtyDtos(this.clinicService.findAllSpecialties())); + if (specialties.isEmpty()) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + return new ResponseEntity<>(specialties, HttpStatus.OK); + } + + @PreAuthorize("hasRole(@roles.VET_ADMIN)") + @Override + public ResponseEntity getSpecialty(Integer specialtyId) { + Specialty specialty = this.clinicService.findSpecialtyById(specialtyId); + if (specialty == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + return new ResponseEntity<>(specialtyMapper.toSpecialtyDto(specialty), HttpStatus.OK); + } + + @PreAuthorize("hasRole(@roles.VET_ADMIN)") + @Override + public ResponseEntity addSpecialty(SpecialtyDto specialtyDto) { + HttpHeaders headers = new HttpHeaders(); + Specialty specialty = specialtyMapper.toSpecialty(specialtyDto); + this.clinicService.saveSpecialty(specialty); + headers.setLocation(UriComponentsBuilder.newInstance().path("/api/specialties/{id}").buildAndExpand(specialty.getId()).toUri()); + return new ResponseEntity<>(specialtyMapper.toSpecialtyDto(specialty), headers, HttpStatus.CREATED); + } + + @PreAuthorize("hasRole(@roles.VET_ADMIN)") + @Override + public ResponseEntity updateSpecialty(Integer specialtyId, SpecialtyDto specialtyDto) { + Specialty currentSpecialty = this.clinicService.findSpecialtyById(specialtyId); + if (currentSpecialty == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + currentSpecialty.setName(specialtyDto.getName()); + this.clinicService.saveSpecialty(currentSpecialty); + return new ResponseEntity<>(specialtyMapper.toSpecialtyDto(currentSpecialty), HttpStatus.NO_CONTENT); + } + + @PreAuthorize("hasRole(@roles.VET_ADMIN)") + @Transactional + @Override + public ResponseEntity deleteSpecialty(Integer specialtyId) { + Specialty specialty = this.clinicService.findSpecialtyById(specialtyId); + if (specialty == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + this.clinicService.deleteSpecialty(specialty); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/rest/controller/UserRestController.java b/backend/src/main/java/org/springframework/samples/petclinic/rest/controller/UserRestController.java new file mode 100644 index 0000000..ce1d843 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/rest/controller/UserRestController.java @@ -0,0 +1,57 @@ +/* + * Copyright 2016-2017 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 + * + * http://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.rest.controller; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.samples.petclinic.mapper.UserMapper; +import org.springframework.samples.petclinic.model.User; +import org.springframework.samples.petclinic.rest.api.UsersApi; +import org.springframework.samples.petclinic.rest.dto.UserDto; +import org.springframework.samples.petclinic.service.UserService; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.validation.Valid; + +@RestController +@CrossOrigin(exposedHeaders = "errors, content-type") +@RequestMapping("api") +public class UserRestController implements UsersApi { + + private final UserService userService; + private final UserMapper userMapper; + + public UserRestController(UserService userService, UserMapper userMapper) { + this.userService = userService; + this.userMapper = userMapper; + } + + + @PreAuthorize( "hasRole(@roles.ADMIN)" ) + @Override + public ResponseEntity addUser(UserDto userDto) { + HttpHeaders headers = new HttpHeaders(); + User user = userMapper.toUser(userDto); + this.userService.saveUser(user); + return new ResponseEntity<>(userMapper.toUserDto(user), headers, HttpStatus.CREATED); + } +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/rest/controller/VetRestController.java b/backend/src/main/java/org/springframework/samples/petclinic/rest/controller/VetRestController.java new file mode 100644 index 0000000..531dede --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/rest/controller/VetRestController.java @@ -0,0 +1,123 @@ +/* + * Copyright 2016-2018 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 + * + * http://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.rest.controller; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.samples.petclinic.mapper.SpecialtyMapper; +import org.springframework.samples.petclinic.mapper.VetMapper; +import org.springframework.samples.petclinic.model.Specialty; +import org.springframework.samples.petclinic.model.Vet; +import org.springframework.samples.petclinic.rest.api.VetsApi; +import org.springframework.samples.petclinic.rest.dto.VetDto; +import org.springframework.samples.petclinic.service.ClinicService; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.util.UriComponentsBuilder; + +import jakarta.transaction.Transactional; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author Vitaliy Fedoriv + */ + +@RestController +@CrossOrigin(exposedHeaders = "errors, content-type") +@RequestMapping("api") +public class VetRestController implements VetsApi { + + private final ClinicService clinicService; + private final VetMapper vetMapper; + private final SpecialtyMapper specialtyMapper; + + public VetRestController(ClinicService clinicService, VetMapper vetMapper, SpecialtyMapper specialtyMapper) { + this.clinicService = clinicService; + this.vetMapper = vetMapper; + this.specialtyMapper = specialtyMapper; + } + + @PreAuthorize("hasRole(@roles.VET_ADMIN)") + @Override + public ResponseEntity> listVets() { + List vets = new ArrayList<>(); + vets.addAll(vetMapper.toVetDtos(this.clinicService.findAllVets())); + if (vets.isEmpty()) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + return new ResponseEntity<>(vets, HttpStatus.OK); + } + + @PreAuthorize("hasRole(@roles.VET_ADMIN)") + @Override + public ResponseEntity getVet(Integer vetId) { + Vet vet = this.clinicService.findVetById(vetId); + if (vet == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + return new ResponseEntity<>(vetMapper.toVetDto(vet), HttpStatus.OK); + } + + @PreAuthorize("hasRole(@roles.VET_ADMIN)") + @Override + public ResponseEntity addVet(VetDto vetDto) { + HttpHeaders headers = new HttpHeaders(); + Vet vet = vetMapper.toVet(vetDto); + if(vet.getNrOfSpecialties() > 0){ + List vetSpecialities = this.clinicService.findSpecialtiesByNameIn(vet.getSpecialties().stream().map(Specialty::getName).collect(Collectors.toSet())); + vet.setSpecialties(vetSpecialities); + } + this.clinicService.saveVet(vet); + headers.setLocation(UriComponentsBuilder.newInstance().path("/api/vets/{id}").buildAndExpand(vet.getId()).toUri()); + return new ResponseEntity<>(vetMapper.toVetDto(vet), headers, HttpStatus.CREATED); + } + + @PreAuthorize("hasRole(@roles.VET_ADMIN)") + @Override + public ResponseEntity updateVet(Integer vetId,VetDto vetDto) { + Vet currentVet = this.clinicService.findVetById(vetId); + if (currentVet == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + currentVet.setFirstName(vetDto.getFirstName()); + currentVet.setLastName(vetDto.getLastName()); + currentVet.clearSpecialties(); + for (Specialty spec : specialtyMapper.toSpecialtys(vetDto.getSpecialties())) { + currentVet.addSpecialty(spec); + } + if(currentVet.getNrOfSpecialties() > 0){ + List vetSpecialities = this.clinicService.findSpecialtiesByNameIn(currentVet.getSpecialties().stream().map(Specialty::getName).collect(Collectors.toSet())); + currentVet.setSpecialties(vetSpecialities); + } + this.clinicService.saveVet(currentVet); + return new ResponseEntity<>(vetMapper.toVetDto(currentVet), HttpStatus.NO_CONTENT); + } + + @PreAuthorize("hasRole(@roles.VET_ADMIN)") + @Transactional + @Override + public ResponseEntity deleteVet(Integer vetId) { + Vet vet = this.clinicService.findVetById(vetId); + if (vet == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + this.clinicService.deleteVet(vet); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/rest/controller/VisitRestController.java b/backend/src/main/java/org/springframework/samples/petclinic/rest/controller/VisitRestController.java new file mode 100644 index 0000000..138f040 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/rest/controller/VisitRestController.java @@ -0,0 +1,110 @@ +/* + * Copyright 2016-2017 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 + * + * http://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.rest.controller; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.samples.petclinic.mapper.VisitMapper; +import org.springframework.samples.petclinic.model.Visit; +import org.springframework.samples.petclinic.rest.api.VisitsApi; +import org.springframework.samples.petclinic.rest.dto.VisitDto; +import org.springframework.samples.petclinic.service.ClinicService; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.util.UriComponentsBuilder; + +import jakarta.transaction.Transactional; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Vitaliy Fedoriv + */ + +@RestController +@CrossOrigin(exposedHeaders = "errors, content-type") +@RequestMapping("api") +public class VisitRestController implements VisitsApi { + + private final ClinicService clinicService; + + private final VisitMapper visitMapper; + + public VisitRestController(ClinicService clinicService, VisitMapper visitMapper) { + this.clinicService = clinicService; + this.visitMapper = visitMapper; + } + + + @PreAuthorize("hasRole(@roles.OWNER_ADMIN)") + @Override + public ResponseEntity> listVisits() { + List visits = new ArrayList<>(this.clinicService.findAllVisits()); + if (visits.isEmpty()) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + return new ResponseEntity<>(new ArrayList<>(visitMapper.toVisitsDto(visits)), HttpStatus.OK); + } + + @PreAuthorize("hasRole(@roles.OWNER_ADMIN)") + @Override + public ResponseEntity getVisit( Integer visitId) { + Visit visit = this.clinicService.findVisitById(visitId); + if (visit == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + return new ResponseEntity<>(visitMapper.toVisitDto(visit), HttpStatus.OK); + } + + @PreAuthorize("hasRole(@roles.OWNER_ADMIN)") + @Override + public ResponseEntity addVisit(VisitDto visitDto) { + HttpHeaders headers = new HttpHeaders(); + Visit visit = visitMapper.toVisit(visitDto); + this.clinicService.saveVisit(visit); + visitDto = visitMapper.toVisitDto(visit); + headers.setLocation(UriComponentsBuilder.newInstance().path("/api/visits/{id}").buildAndExpand(visit.getId()).toUri()); + return new ResponseEntity<>(visitDto, headers, HttpStatus.CREATED); + } + + @PreAuthorize("hasRole(@roles.OWNER_ADMIN)") + @Override + public ResponseEntity updateVisit(Integer visitId, VisitDto visitDto) { + Visit currentVisit = this.clinicService.findVisitById(visitId); + if (currentVisit == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + currentVisit.setDate(visitDto.getDate()); + currentVisit.setDescription(visitDto.getDescription()); + this.clinicService.saveVisit(currentVisit); + return new ResponseEntity<>(visitMapper.toVisitDto(currentVisit), HttpStatus.NO_CONTENT); + } + + @PreAuthorize("hasRole(@roles.OWNER_ADMIN)") + @Transactional + @Override + public ResponseEntity deleteVisit(Integer visitId) { + Visit visit = this.clinicService.findVisitById(visitId); + if (visit == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + this.clinicService.deleteVisit(visit); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/rest/package-info.java b/backend/src/main/java/org/springframework/samples/petclinic/rest/package-info.java new file mode 100644 index 0000000..dfae7ea --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/rest/package-info.java @@ -0,0 +1,5 @@ +/** + * The classes in this package represent PetClinic's REST API. + */ + +package org.springframework.samples.petclinic.rest; \ No newline at end of file diff --git a/backend/src/main/java/org/springframework/samples/petclinic/security/BasicAuthenticationConfig.java b/backend/src/main/java/org/springframework/samples/petclinic/security/BasicAuthenticationConfig.java new file mode 100644 index 0000000..b91e1e4 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/security/BasicAuthenticationConfig.java @@ -0,0 +1,58 @@ +package org.springframework.samples.petclinic.security; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.crypto.password.DelegatingPasswordEncoder; +import org.springframework.security.crypto.password.NoOpPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; + +import javax.sql.DataSource; +import java.util.Map; + +@Configuration +@EnableMethodSecurity(prePostEnabled = true) // Enable @PreAuthorize method-level security +@ConditionalOnProperty(name = "petclinic.security.enable", havingValue = "true") +public class BasicAuthenticationConfig { + + @Autowired + private DataSource dataSource; + + @Bean + public PasswordEncoder passwordEncoder() { + var encoders = Map.of("noop", NoOpPasswordEncoder.getInstance()); + var passwordEncoder = new DelegatingPasswordEncoder("noop", encoders); + passwordEncoder.setDefaultPasswordEncoderForMatches(NoOpPasswordEncoder.getInstance()); + return passwordEncoder; + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + // @formatter:off + http + .csrf(AbstractHttpConfigurer::disable) + .authorizeHttpRequests((authz) -> authz + .anyRequest().authenticated()) + .httpBasic(Customizer.withDefaults()); + // @formatter:on + return http.build(); + } + + @Autowired + public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { + // @formatter:off + auth + .jdbcAuthentication() + .dataSource(dataSource) + .usersByUsernameQuery("select username,password,enabled from users where username=?") + .authoritiesByUsernameQuery("select username,role from roles where username=?"); + // @formatter:on + } +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/security/DisableSecurityConfig.java b/backend/src/main/java/org/springframework/samples/petclinic/security/DisableSecurityConfig.java new file mode 100644 index 0000000..2075fcb --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/security/DisableSecurityConfig.java @@ -0,0 +1,29 @@ +package org.springframework.samples.petclinic.security; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.web.SecurityFilterChain; + +/** + * Starting from Spring Boot 2, if Spring Security is present, endpoints are secured by default + * using Spring Security’s content-negotiation strategy. + */ +@Configuration +@ConditionalOnProperty(name = "petclinic.security.enable", havingValue = "false") +public class DisableSecurityConfig { + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + // @formatter:off + http + .csrf(AbstractHttpConfigurer::disable) + .authorizeHttpRequests((authz) -> authz + .anyRequest().permitAll() + ); + // @formatter:on + return http.build(); + } +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/security/Roles.java b/backend/src/main/java/org/springframework/samples/petclinic/security/Roles.java new file mode 100644 index 0000000..d60f049 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/security/Roles.java @@ -0,0 +1,11 @@ +package org.springframework.samples.petclinic.security; + +import org.springframework.stereotype.Component; + +@Component +public class Roles { + + public final String OWNER_ADMIN = "ROLE_OWNER_ADMIN"; + public final String VET_ADMIN = "ROLE_VET_ADMIN"; + public final String ADMIN = "ROLE_ADMIN"; +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/service/ClinicService.java b/backend/src/main/java/org/springframework/samples/petclinic/service/ClinicService.java new file mode 100644 index 0000000..32d0975 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/service/ClinicService.java @@ -0,0 +1,70 @@ +/* + * Copyright 2002-2017 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 + * + * http://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 java.util.Collection; +import java.util.List; +import java.util.Set; + +import org.springframework.dao.DataAccessException; +import org.springframework.samples.petclinic.model.Owner; +import org.springframework.samples.petclinic.model.Pet; +import org.springframework.samples.petclinic.model.PetType; +import org.springframework.samples.petclinic.model.Specialty; +import org.springframework.samples.petclinic.model.Vet; +import org.springframework.samples.petclinic.model.Visit; + +/** + * Mostly used as a facade so all controllers have a single point of entry + * + * @author Michael Isvy + * @author Vitaliy Fedoriv + */ +public interface ClinicService { + + Pet findPetById(int id) throws DataAccessException; + Collection findAllPets() throws DataAccessException; + void savePet(Pet pet) throws DataAccessException; + void deletePet(Pet pet) throws DataAccessException; + + Collection findVisitsByPetId(int petId); + Visit findVisitById(int visitId) throws DataAccessException; + Collection findAllVisits() throws DataAccessException; + void saveVisit(Visit visit) throws DataAccessException; + void deleteVisit(Visit visit) throws DataAccessException; + Vet findVetById(int id) throws DataAccessException; + Collection findVets() throws DataAccessException; + Collection findAllVets() throws DataAccessException; + void saveVet(Vet vet) throws DataAccessException; + void deleteVet(Vet vet) throws DataAccessException; + Owner findOwnerById(int id) throws DataAccessException; + Collection findAllOwners() throws DataAccessException; + void saveOwner(Owner owner) throws DataAccessException; + void deleteOwner(Owner owner) throws DataAccessException; + Collection findOwnerByLastName(String lastName) throws DataAccessException; + + PetType findPetTypeById(int petTypeId); + Collection findAllPetTypes() throws DataAccessException; + Collection findPetTypes() throws DataAccessException; + void savePetType(PetType petType) throws DataAccessException; + void deletePetType(PetType petType) throws DataAccessException; + Specialty findSpecialtyById(int specialtyId); + Collection findAllSpecialties() throws DataAccessException; + void saveSpecialty(Specialty specialty) throws DataAccessException; + void deleteSpecialty(Specialty specialty) throws DataAccessException; + + List findSpecialtiesByNameIn(Set names) throws DataAccessException; +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/service/ClinicServiceImpl.java b/backend/src/main/java/org/springframework/samples/petclinic/service/ClinicServiceImpl.java new file mode 100644 index 0000000..baf8118 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/service/ClinicServiceImpl.java @@ -0,0 +1,250 @@ +/* + * Copyright 2002-2017 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 + * + * http://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 org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.orm.ObjectRetrievalFailureException; +import org.springframework.samples.petclinic.model.*; +import org.springframework.samples.petclinic.repository.*; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.function.Supplier; + +/** + * Mostly used as a facade for all Petclinic controllers + * Also a placeholder for @Transactional and @Cacheable annotations + * + * @author Michael Isvy + * @author Vitaliy Fedoriv + */ +@Service +public class ClinicServiceImpl implements ClinicService { + + private final PetRepository petRepository; + private final VetRepository vetRepository; + private final OwnerRepository ownerRepository; + private final VisitRepository visitRepository; + private final SpecialtyRepository specialtyRepository; + private final PetTypeRepository petTypeRepository; + + @Autowired + public ClinicServiceImpl( + PetRepository petRepository, + VetRepository vetRepository, + OwnerRepository ownerRepository, + VisitRepository visitRepository, + SpecialtyRepository specialtyRepository, + PetTypeRepository petTypeRepository) { + this.petRepository = petRepository; + this.vetRepository = vetRepository; + this.ownerRepository = ownerRepository; + this.visitRepository = visitRepository; + this.specialtyRepository = specialtyRepository; + this.petTypeRepository = petTypeRepository; + } + + @Override + @Transactional(readOnly = true) + public Collection findAllPets() throws DataAccessException { + return petRepository.findAll(); + } + + @Override + @Transactional + public void deletePet(Pet pet) throws DataAccessException { + petRepository.delete(pet); + } + + @Override + @Transactional(readOnly = true) + public Visit findVisitById(int visitId) throws DataAccessException { + return findEntityById(() -> visitRepository.findById(visitId)); + } + + @Override + @Transactional(readOnly = true) + public Collection findAllVisits() throws DataAccessException { + return visitRepository.findAll(); + } + + @Override + @Transactional + public void deleteVisit(Visit visit) throws DataAccessException { + visitRepository.delete(visit); + } + + @Override + @Transactional(readOnly = true) + public Vet findVetById(int id) throws DataAccessException { + return findEntityById(() -> vetRepository.findById(id)); + } + + @Override + @Transactional(readOnly = true) + public Collection findAllVets() throws DataAccessException { + return vetRepository.findAll(); + } + + @Override + @Transactional + public void saveVet(Vet vet) throws DataAccessException { + vetRepository.save(vet); + } + + @Override + @Transactional + public void deleteVet(Vet vet) throws DataAccessException { + vetRepository.delete(vet); + } + + @Override + @Transactional(readOnly = true) + public Collection findAllOwners() throws DataAccessException { + return ownerRepository.findAll(); + } + + @Override + @Transactional + public void deleteOwner(Owner owner) throws DataAccessException { + ownerRepository.delete(owner); + } + + @Override + @Transactional(readOnly = true) + public PetType findPetTypeById(int petTypeId) { + return findEntityById(() -> petTypeRepository.findById(petTypeId)); + } + + @Override + @Transactional(readOnly = true) + public Collection findAllPetTypes() throws DataAccessException { + return petTypeRepository.findAll(); + } + + @Override + @Transactional + public void savePetType(PetType petType) throws DataAccessException { + petTypeRepository.save(petType); + } + + @Override + @Transactional + public void deletePetType(PetType petType) throws DataAccessException { + petTypeRepository.delete(petType); + } + + @Override + @Transactional(readOnly = true) + public Specialty findSpecialtyById(int specialtyId) { + return findEntityById(() -> specialtyRepository.findById(specialtyId)); + } + + @Override + @Transactional(readOnly = true) + public Collection findAllSpecialties() throws DataAccessException { + return specialtyRepository.findAll(); + } + + @Override + @Transactional + public void saveSpecialty(Specialty specialty) throws DataAccessException { + specialtyRepository.save(specialty); + } + + @Override + @Transactional + public void deleteSpecialty(Specialty specialty) throws DataAccessException { + specialtyRepository.delete(specialty); + } + + @Override + @Transactional(readOnly = true) + public Collection findPetTypes() throws DataAccessException { + return petRepository.findPetTypes(); + } + + @Override + @Transactional(readOnly = true) + public Owner findOwnerById(int id) throws DataAccessException { + return findEntityById(() -> ownerRepository.findById(id)); + } + + @Override + @Transactional(readOnly = true) + public Pet findPetById(int id) throws DataAccessException { + return findEntityById(() -> petRepository.findById(id)); + } + + @Override + @Transactional + public void savePet(Pet pet) throws DataAccessException { + petRepository.save(pet); + } + + @Override + @Transactional + public void saveVisit(Visit visit) throws DataAccessException { + visitRepository.save(visit); + + } + + @Override + @Transactional(readOnly = true) + public Collection findVets() throws DataAccessException { + return vetRepository.findAll(); + } + + @Override + @Transactional + public void saveOwner(Owner owner) throws DataAccessException { + ownerRepository.save(owner); + + } + + @Override + @Transactional(readOnly = true) + public Collection findOwnerByLastName(String lastName) throws DataAccessException { + return ownerRepository.findByLastName(lastName); + } + + @Override + @Transactional(readOnly = true) + public Collection findVisitsByPetId(int petId) { + return visitRepository.findByPetId(petId); + } + + @Override + @Transactional(readOnly = true) + public List findSpecialtiesByNameIn(Set names) { + return findEntityById(() -> specialtyRepository.findSpecialtiesByNameIn(names)); + } + + private T findEntityById(Supplier supplier) { + try { + return supplier.get(); + } catch (ObjectRetrievalFailureException | EmptyResultDataAccessException e) { + // Just ignore not found exceptions for Jdbc/Jpa realization + return null; + } + } + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/service/UserService.java b/backend/src/main/java/org/springframework/samples/petclinic/service/UserService.java new file mode 100644 index 0000000..352605a --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/service/UserService.java @@ -0,0 +1,8 @@ +package org.springframework.samples.petclinic.service; + +import org.springframework.samples.petclinic.model.User; + +public interface UserService { + + void saveUser(User user) ; +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/service/UserServiceImpl.java b/backend/src/main/java/org/springframework/samples/petclinic/service/UserServiceImpl.java new file mode 100644 index 0000000..fb099e1 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/service/UserServiceImpl.java @@ -0,0 +1,36 @@ +package org.springframework.samples.petclinic.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.samples.petclinic.model.User; +import org.springframework.samples.petclinic.model.Role; +import org.springframework.samples.petclinic.repository.UserRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class UserServiceImpl implements UserService { + + @Autowired + private UserRepository userRepository; + + @Override + @Transactional + public void saveUser(User user) { + + if(user.getRoles() == null || user.getRoles().isEmpty()) { + throw new IllegalArgumentException("User must have at least a role set!"); + } + + for (Role role : user.getRoles()) { + if(!role.getName().startsWith("ROLE_")) { + role.setName("ROLE_" + role.getName()); + } + + if(role.getUser() == null) { + role.setUser(user); + } + } + + userRepository.save(user); + } +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/util/CallMonitoringAspect.java b/backend/src/main/java/org/springframework/samples/petclinic/util/CallMonitoringAspect.java new file mode 100644 index 0000000..fddf17d --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/util/CallMonitoringAspect.java @@ -0,0 +1,97 @@ +/* + * Copyright 2002-2013 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 + * + * http://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.util; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.jmx.export.annotation.ManagedAttribute; +import org.springframework.jmx.export.annotation.ManagedOperation; +import org.springframework.jmx.export.annotation.ManagedResource; +import org.springframework.util.StopWatch; + +/** + * Simple aspect that monitors call count and call invocation time. It uses JMX annotations and therefore can be + * monitored using any JMX console such as the jConsole + *

+ * This is only useful if you use JPA or JDBC. Spring-data-jpa doesn't have any correctly annotated classes to join on + * + * @author Rob Harrop + * @author Juergen Hoeller + * @author Michael Isvy + * @since 2.5 + */ +@ManagedResource("petclinic:type=CallMonitor") +@Aspect +public class CallMonitoringAspect { + + private boolean enabled = true; + + private int callCount = 0; + + private long accumulatedCallTime = 0; + + @ManagedAttribute + public boolean isEnabled() { + return enabled; + } + + @ManagedAttribute + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + @ManagedOperation + public void reset() { + this.callCount = 0; + this.accumulatedCallTime = 0; + } + + @ManagedAttribute + public int getCallCount() { + return callCount; + } + + @ManagedAttribute + public long getCallTime() { + if (this.callCount > 0) + return this.accumulatedCallTime / this.callCount; + else + return 0; + } + + + @Around("within(@org.springframework.stereotype.Repository *)") + public Object invoke(ProceedingJoinPoint joinPoint) throws Throwable { + if (this.enabled) { + StopWatch sw = new StopWatch(joinPoint.toShortString()); + + sw.start("invoke"); + try { + return joinPoint.proceed(); + } finally { + sw.stop(); + synchronized (this) { + this.callCount++; + this.accumulatedCallTime += sw.getTotalTimeMillis(); + } + } + } else { + return joinPoint.proceed(); + } + } + +} diff --git a/backend/src/main/java/org/springframework/samples/petclinic/util/EntityUtils.java b/backend/src/main/java/org/springframework/samples/petclinic/util/EntityUtils.java new file mode 100644 index 0000000..eee3906 --- /dev/null +++ b/backend/src/main/java/org/springframework/samples/petclinic/util/EntityUtils.java @@ -0,0 +1,54 @@ +/* + * Copyright 2002-2013 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 + * + * http://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.util; + +import java.util.Collection; + +import org.springframework.orm.ObjectRetrievalFailureException; +import org.springframework.samples.petclinic.model.BaseEntity; + +/** + * Utility methods for handling entities. Separate from the BaseEntity class mainly because of dependency on the + * ORM-associated ObjectRetrievalFailureException. + * + * @author Juergen Hoeller + * @author Sam Brannen + * @see org.springframework.samples.petclinic.model.BaseEntity + * @since 29.10.2003 + */ +public abstract class EntityUtils { + + /** + * Look up the entity of the given class with the given id in the given collection. + * + * @param entities the collection to search + * @param entityClass the entity class to look up + * @param entityId the entity id to look up + * @return the found entity + * @throws ObjectRetrievalFailureException if the entity was not found + */ + public static T getById(Collection entities, Class entityClass, int entityId) + throws ObjectRetrievalFailureException { + for (T entity : entities) { + if (entity.getId() == entityId && entityClass.isInstance(entity)) { + return entity; + } + } + throw new ObjectRetrievalFailureException(entityClass, entityId); + } + +} diff --git a/backend/src/main/resources/application-hsqldb.properties b/backend/src/main/resources/application-hsqldb.properties new file mode 100644 index 0000000..d65ff9f --- /dev/null +++ b/backend/src/main/resources/application-hsqldb.properties @@ -0,0 +1,8 @@ +# HSQLDB config start +#---------------------------------------------------------------- +spring.datasource.url=jdbc:hsqldb:mem:petclinic +spring.datasource.username=sa +spring.datasource.password= +spring.jpa.hibernate.ddl-auto=none +#---------------------------------------------------------------- +# HSQLDB config end diff --git a/backend/src/main/resources/application-mysql.properties b/backend/src/main/resources/application-mysql.properties new file mode 100644 index 0000000..e23dfa6 --- /dev/null +++ b/backend/src/main/resources/application-mysql.properties @@ -0,0 +1,7 @@ +# database init, supports mysql too +database=mysql +spring.datasource.url=${MYSQL_URL:jdbc:mysql://localhost/petclinic} +spring.datasource.username=${MYSQL_USER:petclinic} +spring.datasource.password=${MYSQL_PASS:petclinic} +# SQL is written to be idempotent so this is safe +spring.sql.init.mode=always diff --git a/backend/src/main/resources/application-postgres.properties b/backend/src/main/resources/application-postgres.properties new file mode 100644 index 0000000..60889b4 --- /dev/null +++ b/backend/src/main/resources/application-postgres.properties @@ -0,0 +1,6 @@ +database=postgres +spring.datasource.url=${POSTGRES_URL:jdbc:postgresql://localhost/petclinic} +spring.datasource.username=${POSTGRES_USER:petclinic} +spring.datasource.password=${POSTGRES_PASS:petclinic} +# SQL is written to be idempotent so this is safe +spring.sql.init.mode=always diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties new file mode 100644 index 0000000..460f991 --- /dev/null +++ b/backend/src/main/resources/application.properties @@ -0,0 +1,44 @@ +# active profiles config +# +# application use two active profiles +# +# one - for select database +# ------------------------------------------------ +# When using HSQL, use: hsqldb +# When using MySQL, use: mysql +# When using PostgeSQL, use: postgres +# ------------------------------------------------ +# +# one for select repository layer +# ------------------------------------------------ +# When using Spring jpa, use: jpa +# When using Spring JDBC, use: jdbc +# When using Spring Data JPA, use: spring-data-jpa +# ------------------------------------------------ + +spring.profiles.active=hsqldb,spring-data-jpa + +# ------------------------------------------------ + +server.port=9966 +server.servlet.context-path=/petclinic/ + +# database init, supports mysql and postgres too +database=hsqldb +spring.sql.init.schema-locations=classpath*:db/${database}/schema.sql +spring.sql.init.data-locations=classpath*:db/${database}/data.sql + + +spring.messages.basename=messages/messages +spring.jpa.open-in-view=false + +logging.level.org.springframework=INFO +#logging.level.org.springframework=DEBUG + +#logging.level.org.hibernate.SQL=DEBUG +#logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE + +# enable the desired authentication type +# by default the authentication is disabled +petclinic.security.enable=false + diff --git a/backend/src/main/resources/db/hsqldb/data.sql b/backend/src/main/resources/db/hsqldb/data.sql new file mode 100644 index 0000000..e5ee7b9 --- /dev/null +++ b/backend/src/main/resources/db/hsqldb/data.sql @@ -0,0 +1,59 @@ +INSERT INTO vets VALUES (1, 'James', 'Carter'); +INSERT INTO vets VALUES (2, 'Helen', 'Leary'); +INSERT INTO vets VALUES (3, 'Linda', 'Douglas'); +INSERT INTO vets VALUES (4, 'Rafael', 'Ortega'); +INSERT INTO vets VALUES (5, 'Henry', 'Stevens'); +INSERT INTO vets VALUES (6, 'Sharon', 'Jenkins'); + +INSERT INTO specialties VALUES (1, 'radiology'); +INSERT INTO specialties VALUES (2, 'surgery'); +INSERT INTO specialties VALUES (3, 'dentistry'); + +INSERT INTO vet_specialties VALUES (2, 1); +INSERT INTO vet_specialties VALUES (3, 2); +INSERT INTO vet_specialties VALUES (3, 3); +INSERT INTO vet_specialties VALUES (4, 2); +INSERT INTO vet_specialties VALUES (5, 1); + +INSERT INTO types VALUES (1, 'cat'); +INSERT INTO types VALUES (2, 'dog'); +INSERT INTO types VALUES (3, 'lizard'); +INSERT INTO types VALUES (4, 'snake'); +INSERT INTO types VALUES (5, 'bird'); +INSERT INTO types VALUES (6, 'hamster'); + +INSERT INTO owners VALUES (1, 'George', 'Franklin', '110 W. Liberty St.', 'Madison', '6085551023'); +INSERT INTO owners VALUES (2, 'Betty', 'Davis', '638 Cardinal Ave.', 'Sun Prairie', '6085551749'); +INSERT INTO owners VALUES (3, 'Eduardo', 'Rodriquez', '2693 Commerce St.', 'McFarland', '6085558763'); +INSERT INTO owners VALUES (4, 'Harold', 'Davis', '563 Friendly St.', 'Windsor', '6085553198'); +INSERT INTO owners VALUES (5, 'Peter', 'McTavish', '2387 S. Fair Way', 'Madison', '6085552765'); +INSERT INTO owners VALUES (6, 'Jean', 'Coleman', '105 N. Lake St.', 'Monona', '6085552654'); +INSERT INTO owners VALUES (7, 'Jeff', 'Black', '1450 Oak Blvd.', 'Monona', '6085555387'); +INSERT INTO owners VALUES (8, 'Maria', 'Escobito', '345 Maple St.', 'Madison', '6085557683'); +INSERT INTO owners VALUES (9, 'David', 'Schroeder', '2749 Blackhawk Trail', 'Madison', '6085559435'); +INSERT INTO owners VALUES (10, 'Carlos', 'Estaban', '2335 Independence La.', 'Waunakee', '6085555487'); + +INSERT INTO pets VALUES (1, 'Leo', '2010-09-07', 1, 1); +INSERT INTO pets VALUES (2, 'Basil', '2012-08-06', 6, 2); +INSERT INTO pets VALUES (3, 'Rosy', '2011-04-17', 2, 3); +INSERT INTO pets VALUES (4, 'Jewel', '2010-03-07', 2, 3); +INSERT INTO pets VALUES (5, 'Iggy', '2010-11-30', 3, 4); +INSERT INTO pets VALUES (6, 'George', '2010-01-20', 4, 5); +INSERT INTO pets VALUES (7, 'Samantha', '2012-09-04', 1, 6); +INSERT INTO pets VALUES (8, 'Max', '2012-09-04', 1, 6); +INSERT INTO pets VALUES (9, 'Lucky', '2011-08-06', 5, 7); +INSERT INTO pets VALUES (10, 'Mulligan', '2007-02-24', 2, 8); +INSERT INTO pets VALUES (11, 'Freddy', '2010-03-09', 5, 9); +INSERT INTO pets VALUES (12, 'Lucky', '2010-06-24', 2, 10); +INSERT INTO pets VALUES (13, 'Sly', '2012-06-08', 1, 10); + +INSERT INTO visits VALUES (1, 7, '2013-01-01', 'rabies shot'); +INSERT INTO visits VALUES (2, 8, '2013-01-02', 'rabies shot'); +INSERT INTO visits VALUES (3, 8, '2013-01-03', 'neutered'); +INSERT INTO visits VALUES (4, 7, '2013-01-04', 'spayed'); + +INSERT INTO users(username,password,enabled) VALUES ('admin','{noop}admin', true); + +INSERT INTO roles (username, role) VALUES ('admin', 'ROLE_OWNER_ADMIN'); +INSERT INTO roles (username, role) VALUES ('admin', 'ROLE_VET_ADMIN'); +INSERT INTO roles (username, role) VALUES ('admin', 'ROLE_ADMIN'); diff --git a/backend/src/main/resources/db/hsqldb/schema.sql b/backend/src/main/resources/db/hsqldb/schema.sql new file mode 100644 index 0000000..d14ecf3 --- /dev/null +++ b/backend/src/main/resources/db/hsqldb/schema.sql @@ -0,0 +1,82 @@ +DROP TABLE vet_specialties IF EXISTS; +DROP TABLE vets IF EXISTS; +DROP TABLE specialties IF EXISTS; +DROP TABLE visits IF EXISTS; +DROP TABLE pets IF EXISTS; +DROP TABLE types IF EXISTS; +DROP TABLE owners IF EXISTS; +DROP TABLE roles IF EXISTS; +DROP TABLE users IF EXISTS; + + +CREATE TABLE vets ( + id INTEGER IDENTITY PRIMARY KEY, + first_name VARCHAR(30), + last_name VARCHAR(30) +); +CREATE INDEX vets_last_name ON vets (last_name); + +CREATE TABLE specialties ( + id INTEGER IDENTITY PRIMARY KEY, + name VARCHAR(80) +); +CREATE INDEX specialties_name ON specialties (name); + +CREATE TABLE vet_specialties ( + vet_id INTEGER NOT NULL, + specialty_id INTEGER NOT NULL +); +ALTER TABLE vet_specialties ADD CONSTRAINT fk_vet_specialties_vets FOREIGN KEY (vet_id) REFERENCES vets (id); +ALTER TABLE vet_specialties ADD CONSTRAINT fk_vet_specialties_specialties FOREIGN KEY (specialty_id) REFERENCES specialties (id); + +CREATE TABLE types ( + id INTEGER IDENTITY PRIMARY KEY, + name VARCHAR(80) +); +CREATE INDEX types_name ON types (name); + +CREATE TABLE owners ( + id INTEGER IDENTITY PRIMARY KEY, + first_name VARCHAR(30), + last_name VARCHAR_IGNORECASE(30), + address VARCHAR(255), + city VARCHAR(80), + telephone VARCHAR(20) +); +CREATE INDEX owners_last_name ON owners (last_name); + +CREATE TABLE pets ( + id INTEGER IDENTITY PRIMARY KEY, + name VARCHAR(30), + birth_date DATE, + type_id INTEGER NOT NULL, + owner_id INTEGER NOT NULL +); +ALTER TABLE pets ADD CONSTRAINT fk_pets_owners FOREIGN KEY (owner_id) REFERENCES owners (id); +ALTER TABLE pets ADD CONSTRAINT fk_pets_types FOREIGN KEY (type_id) REFERENCES types (id); +CREATE INDEX pets_name ON pets (name); + +CREATE TABLE visits ( + id INTEGER IDENTITY PRIMARY KEY, + pet_id INTEGER NOT NULL, + visit_date DATE, + description VARCHAR(255) +); +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 users ( + username VARCHAR(20) NOT NULL , + password VARCHAR(20) NOT NULL , + enabled BOOLEAN DEFAULT TRUE NOT NULL , + PRIMARY KEY (username) +); + +CREATE TABLE roles ( + id INTEGER IDENTITY PRIMARY KEY, + username VARCHAR(20) NOT NULL, + role VARCHAR(20) NOT NULL +); +ALTER TABLE roles ADD CONSTRAINT fk_username FOREIGN KEY (username) REFERENCES users (username); +CREATE INDEX fk_username_idx ON roles (username); + diff --git a/backend/src/main/resources/db/mysql/data.sql b/backend/src/main/resources/db/mysql/data.sql new file mode 100644 index 0000000..0bbcf4a --- /dev/null +++ b/backend/src/main/resources/db/mysql/data.sql @@ -0,0 +1,59 @@ +INSERT IGNORE INTO vets VALUES (1, 'James', 'Carter'); +INSERT IGNORE INTO vets VALUES (2, 'Helen', 'Leary'); +INSERT IGNORE INTO vets VALUES (3, 'Linda', 'Douglas'); +INSERT IGNORE INTO vets VALUES (4, 'Rafael', 'Ortega'); +INSERT IGNORE INTO vets VALUES (5, 'Henry', 'Stevens'); +INSERT IGNORE INTO vets VALUES (6, 'Sharon', 'Jenkins'); + +INSERT IGNORE INTO specialties VALUES (1, 'radiology'); +INSERT IGNORE INTO specialties VALUES (2, 'surgery'); +INSERT IGNORE INTO specialties VALUES (3, 'dentistry'); + +INSERT IGNORE INTO vet_specialties VALUES (2, 1); +INSERT IGNORE INTO vet_specialties VALUES (3, 2); +INSERT IGNORE INTO vet_specialties VALUES (3, 3); +INSERT IGNORE INTO vet_specialties VALUES (4, 2); +INSERT IGNORE INTO vet_specialties VALUES (5, 1); + +INSERT IGNORE INTO types VALUES (1, 'cat'); +INSERT IGNORE INTO types VALUES (2, 'dog'); +INSERT IGNORE INTO types VALUES (3, 'lizard'); +INSERT IGNORE INTO types VALUES (4, 'snake'); +INSERT IGNORE INTO types VALUES (5, 'bird'); +INSERT IGNORE INTO types VALUES (6, 'hamster'); + +INSERT IGNORE INTO owners VALUES (1, 'George', 'Franklin', '110 W. Liberty St.', 'Madison', '6085551023'); +INSERT IGNORE INTO owners VALUES (2, 'Betty', 'Davis', '638 Cardinal Ave.', 'Sun Prairie', '6085551749'); +INSERT IGNORE INTO owners VALUES (3, 'Eduardo', 'Rodriquez', '2693 Commerce St.', 'McFarland', '6085558763'); +INSERT IGNORE INTO owners VALUES (4, 'Harold', 'Davis', '563 Friendly St.', 'Windsor', '6085553198'); +INSERT IGNORE INTO owners VALUES (5, 'Peter', 'McTavish', '2387 S. Fair Way', 'Madison', '6085552765'); +INSERT IGNORE INTO owners VALUES (6, 'Jean', 'Coleman', '105 N. Lake St.', 'Monona', '6085552654'); +INSERT IGNORE INTO owners VALUES (7, 'Jeff', 'Black', '1450 Oak Blvd.', 'Monona', '6085555387'); +INSERT IGNORE INTO owners VALUES (8, 'Maria', 'Escobito', '345 Maple St.', 'Madison', '6085557683'); +INSERT IGNORE INTO owners VALUES (9, 'David', 'Schroeder', '2749 Blackhawk Trail', 'Madison', '6085559435'); +INSERT IGNORE INTO owners VALUES (10, 'Carlos', 'Estaban', '2335 Independence La.', 'Waunakee', '6085555487'); + +INSERT IGNORE INTO pets VALUES (1, 'Leo', '2000-09-07', 1, 1); +INSERT IGNORE INTO pets VALUES (2, 'Basil', '2002-08-06', 6, 2); +INSERT IGNORE INTO pets VALUES (3, 'Rosy', '2001-04-17', 2, 3); +INSERT IGNORE INTO pets VALUES (4, 'Jewel', '2000-03-07', 2, 3); +INSERT IGNORE INTO pets VALUES (5, 'Iggy', '2000-11-30', 3, 4); +INSERT IGNORE INTO pets VALUES (6, 'George', '2000-01-20', 4, 5); +INSERT IGNORE INTO pets VALUES (7, 'Samantha', '1995-09-04', 1, 6); +INSERT IGNORE INTO pets VALUES (8, 'Max', '1995-09-04', 1, 6); +INSERT IGNORE INTO pets VALUES (9, 'Lucky', '1999-08-06', 5, 7); +INSERT IGNORE INTO pets VALUES (10, 'Mulligan', '1997-02-24', 2, 8); +INSERT IGNORE INTO pets VALUES (11, 'Freddy', '2000-03-09', 5, 9); +INSERT IGNORE INTO pets VALUES (12, 'Lucky', '2000-06-24', 2, 10); +INSERT IGNORE INTO pets VALUES (13, 'Sly', '2002-06-08', 1, 10); + +INSERT IGNORE INTO visits VALUES (1, 7, '2010-03-04', 'rabies shot'); +INSERT IGNORE INTO visits VALUES (2, 8, '2011-03-04', 'rabies shot'); +INSERT IGNORE INTO visits VALUES (3, 8, '2009-06-04', 'neutered'); +INSERT IGNORE INTO visits VALUES (4, 7, '2008-09-04', 'spayed'); + +INSERT IGNORE INTO users(username,password,enabled) VALUES ('admin','{noop}admin', true); + +INSERT IGNORE INTO roles (username, role) VALUES ('admin', 'ROLE_OWNER_ADMIN'); +INSERT IGNORE INTO roles (username, role) VALUES ('admin', 'ROLE_VET_ADMIN'); +INSERT IGNORE INTO roles (username, role) VALUES ('admin', 'ROLE_ADMIN'); diff --git a/backend/src/main/resources/db/mysql/petclinic_db_setup_mysql.txt b/backend/src/main/resources/db/mysql/petclinic_db_setup_mysql.txt new file mode 100644 index 0000000..21213b4 --- /dev/null +++ b/backend/src/main/resources/db/mysql/petclinic_db_setup_mysql.txt @@ -0,0 +1,36 @@ +================================================================================ +=== Spring PetClinic sample application - MySQL Configuration === +================================================================================ + +@author Sam Brannen +@author Costin Leau +@author Dave Syer + +-------------------------------------------------------------------------------- + +1) Download and install the MySQL database (e.g., MySQL Community Server 5.1.x), + which can be found here: https://dev.mysql.com/downloads/. Or run the + "docker-compose.yml" from the root of the project (if you have docker installed + locally): + + $ docker-compose up + ... + mysql_1_eedb4818d817 | MySQL init process done. Ready for start up. + ... + +2) (Once only) create the PetClinic database and user by executing the "db/mysql/user.sql" + scripts. You can connect to the database running in the docker container using + `mysql -u root -h localhost --protocol tcp`, but you don't need to run the script there + because the petclinic user is already set up if you use the provided `docker-compose.yml`. + +3) Run the app with `spring.profiles.active=mysql` (e.g. as a System property via the command + line, but any way that sets that property in a Spring Boot app should work). For example use + + mvn spring-boot:run -Dspring-boot.run.profiles=mysql + + To activate the profile on the command line. + +N.B. the "petclinic" database has to exist for the app to work with the JDBC URL value +as it is configured by default. This condition is taken care of automatically by the +docker-compose configuration provided, or by the `user.sql` script if you run that as +root. diff --git a/backend/src/main/resources/db/mysql/schema.sql b/backend/src/main/resources/db/mysql/schema.sql new file mode 100644 index 0000000..109d566 --- /dev/null +++ b/backend/src/main/resources/db/mysql/schema.sql @@ -0,0 +1,72 @@ +CREATE TABLE IF NOT EXISTS vets ( + id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + first_name VARCHAR(30), + last_name VARCHAR(30), + INDEX(last_name) +) engine=InnoDB; + +CREATE TABLE IF NOT EXISTS specialties ( + id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(80), + INDEX(name) +) engine=InnoDB; + +CREATE TABLE IF NOT EXISTS vet_specialties ( + vet_id INT(4) UNSIGNED NOT NULL, + specialty_id INT(4) UNSIGNED NOT NULL, + FOREIGN KEY (vet_id) REFERENCES vets(id), + FOREIGN KEY (specialty_id) REFERENCES specialties(id), + UNIQUE (vet_id,specialty_id) +) engine=InnoDB; + +CREATE TABLE IF NOT EXISTS types ( + id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(80), + INDEX(name) +) engine=InnoDB; + +CREATE TABLE IF NOT EXISTS owners ( + id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + first_name VARCHAR(30), + last_name VARCHAR(30), + address VARCHAR(255), + city VARCHAR(80), + telephone VARCHAR(20), + INDEX(last_name) +) engine=InnoDB; + +CREATE TABLE IF NOT EXISTS pets ( + id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(30), + birth_date DATE, + type_id INT(4) UNSIGNED NOT NULL, + owner_id INT(4) UNSIGNED NOT NULL, + INDEX(name), + FOREIGN KEY (owner_id) REFERENCES owners(id), + FOREIGN KEY (type_id) REFERENCES types(id) +) engine=InnoDB; + +CREATE TABLE IF NOT EXISTS visits ( + id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + pet_id INT(4) UNSIGNED NOT NULL, + visit_date DATE, + description VARCHAR(255), + FOREIGN KEY (pet_id) REFERENCES pets(id) +) engine=InnoDB; + +CREATE TABLE IF NOT EXISTS users ( + username VARCHAR(20) NOT NULL , + password VARCHAR(20) NOT NULL , + enabled TINYINT NOT NULL DEFAULT 1 , + PRIMARY KEY (username) +) engine=InnoDB; + +CREATE TABLE IF NOT EXISTS roles ( + id int(11) NOT NULL AUTO_INCREMENT, + username varchar(20) NOT NULL, + role varchar(20) NOT NULL, + PRIMARY KEY (id), + UNIQUE KEY uni_username_role (role,username), + KEY fk_username_idx (username), + CONSTRAINT fk_username FOREIGN KEY (username) REFERENCES users (username) +) engine=InnoDB; diff --git a/backend/src/main/resources/db/postgres/data.sql b/backend/src/main/resources/db/postgres/data.sql new file mode 100644 index 0000000..b1dfda8 --- /dev/null +++ b/backend/src/main/resources/db/postgres/data.sql @@ -0,0 +1,59 @@ +INSERT INTO vets VALUES (1, 'James', 'Carter') ON CONFLICT DO NOTHING; +INSERT INTO vets VALUES (2, 'Helen', 'Leary') ON CONFLICT DO NOTHING; +INSERT INTO vets VALUES (3, 'Linda', 'Douglas') ON CONFLICT DO NOTHING; +INSERT INTO vets VALUES (4, 'Rafael', 'Ortega') ON CONFLICT DO NOTHING; +INSERT INTO vets VALUES (5, 'Henry', 'Stevens') ON CONFLICT DO NOTHING; +INSERT INTO vets VALUES (6, 'Sharon', 'Jenkins') ON CONFLICT DO NOTHING; + +INSERT INTO specialties VALUES (1, 'radiology') ON CONFLICT DO NOTHING; +INSERT INTO specialties VALUES (2, 'surgery') ON CONFLICT DO NOTHING; +INSERT INTO specialties VALUES (3, 'dentistry') ON CONFLICT DO NOTHING; + +INSERT INTO vet_specialties VALUES (2, 1) ON CONFLICT DO NOTHING; +INSERT INTO vet_specialties VALUES (3, 2) ON CONFLICT DO NOTHING; +INSERT INTO vet_specialties VALUES (3, 3) ON CONFLICT DO NOTHING; +INSERT INTO vet_specialties VALUES (4, 2) ON CONFLICT DO NOTHING; +INSERT INTO vet_specialties VALUES (5, 1) ON CONFLICT DO NOTHING; + +INSERT INTO types VALUES (1, 'cat') ON CONFLICT DO NOTHING; +INSERT INTO types VALUES (2, 'dog') ON CONFLICT DO NOTHING; +INSERT INTO types VALUES (3, 'lizard') ON CONFLICT DO NOTHING; +INSERT INTO types VALUES (4, 'snake') ON CONFLICT DO NOTHING; +INSERT INTO types VALUES (5, 'bird') ON CONFLICT DO NOTHING; +INSERT INTO types VALUES (6, 'hamster') ON CONFLICT DO NOTHING; + +INSERT INTO owners VALUES (1, 'George', 'Franklin', '110 W. Liberty St.', 'Madison', '6085551023') ON CONFLICT DO NOTHING; +INSERT INTO owners VALUES (2, 'Betty', 'Davis', '638 Cardinal Ave.', 'Sun Prairie', '6085551749') ON CONFLICT DO NOTHING; +INSERT INTO owners VALUES (3, 'Eduardo', 'Rodriquez', '2693 Commerce St.', 'McFarland', '6085558763') ON CONFLICT DO NOTHING; +INSERT INTO owners VALUES (4, 'Harold', 'Davis', '563 Friendly St.', 'Windsor', '6085553198') ON CONFLICT DO NOTHING; +INSERT INTO owners VALUES (5, 'Peter', 'McTavish', '2387 S. Fair Way', 'Madison', '6085552765') ON CONFLICT DO NOTHING; +INSERT INTO owners VALUES (6, 'Jean', 'Coleman', '105 N. Lake St.', 'Monona', '6085552654') ON CONFLICT DO NOTHING; +INSERT INTO owners VALUES (7, 'Jeff', 'Black', '1450 Oak Blvd.', 'Monona', '6085555387') ON CONFLICT DO NOTHING; +INSERT INTO owners VALUES (8, 'Maria', 'Escobito', '345 Maple St.', 'Madison', '6085557683') ON CONFLICT DO NOTHING; +INSERT INTO owners VALUES (9, 'David', 'Schroeder', '2749 Blackhawk Trail', 'Madison', '6085559435') ON CONFLICT DO NOTHING; +INSERT INTO owners VALUES (10, 'Carlos', 'Estaban', '2335 Independence La.', 'Waunakee', '6085555487') ON CONFLICT DO NOTHING; + +INSERT INTO pets VALUES (1, 'Leo', '2000-09-07', 1, 1) ON CONFLICT DO NOTHING; +INSERT INTO pets VALUES (2, 'Basil', '2002-08-06', 6, 2) ON CONFLICT DO NOTHING; +INSERT INTO pets VALUES (3, 'Rosy', '2001-04-17', 2, 3) ON CONFLICT DO NOTHING; +INSERT INTO pets VALUES (4, 'Jewel', '2000-03-07', 2, 3) ON CONFLICT DO NOTHING; +INSERT INTO pets VALUES (5, 'Iggy', '2000-11-30', 3, 4) ON CONFLICT DO NOTHING; +INSERT INTO pets VALUES (6, 'George', '2000-01-20', 4, 5) ON CONFLICT DO NOTHING; +INSERT INTO pets VALUES (7, 'Samantha', '1995-09-04', 1, 6) ON CONFLICT DO NOTHING; +INSERT INTO pets VALUES (8, 'Max', '1995-09-04', 1, 6) ON CONFLICT DO NOTHING; +INSERT INTO pets VALUES (9, 'Lucky', '1999-08-06', 5, 7) ON CONFLICT DO NOTHING; +INSERT INTO pets VALUES (10, 'Mulligan', '1997-02-24', 2, 8) ON CONFLICT DO NOTHING; +INSERT INTO pets VALUES (11, 'Freddy', '2000-03-09', 5, 9) ON CONFLICT DO NOTHING; +INSERT INTO pets VALUES (12, 'Lucky', '2000-06-24', 2, 10) ON CONFLICT DO NOTHING; +INSERT INTO pets VALUES (13, 'Sly', '2002-06-08', 1, 10) ON CONFLICT DO NOTHING; + +INSERT INTO visits VALUES (1, 7, '2010-03-04', 'rabies shot') ON CONFLICT DO NOTHING; +INSERT INTO visits VALUES (2, 8, '2011-03-04', 'rabies shot') ON CONFLICT DO NOTHING; +INSERT INTO visits VALUES (3, 8, '2009-06-04', 'neutered') ON CONFLICT DO NOTHING; +INSERT INTO visits VALUES (4, 7, '2008-09-04', 'spayed') ON CONFLICT DO NOTHING; + +INSERT INTO users(username,password,enabled) VALUES ('admin','{noop}admin', true) ON CONFLICT DO NOTHING; + +INSERT INTO roles (username, role) VALUES ('admin', 'ROLE_OWNER_ADMIN') ON CONFLICT DO NOTHING; +INSERT INTO roles (username, role) VALUES ('admin', 'ROLE_VET_ADMIN') ON CONFLICT DO NOTHING; +INSERT INTO roles (username, role) VALUES ('admin', 'ROLE_ADMIN') ON CONFLICT DO NOTHING; diff --git a/backend/src/main/resources/db/postgres/petclinic_db_setup_postgres.txt b/backend/src/main/resources/db/postgres/petclinic_db_setup_postgres.txt new file mode 100644 index 0000000..981e2b0 --- /dev/null +++ b/backend/src/main/resources/db/postgres/petclinic_db_setup_postgres.txt @@ -0,0 +1,22 @@ +================================================================================ +=== Spring PetClinic sample application - PostgreSQL Configuration === +================================================================================ + +@author Vitaliy Fedoriv +@autor Antoine Rey + +-------------------------------------------------------------------------------- + +1) Run the "docker-compose.yml" from the root of the project: + + $ docker-compose up + ... + spring-petclinic-postgres-1 | The files belonging to this database system will be owned by user "postgres". + ... + +2) Run the app with `spring.profiles.active=postgres` (e.g. as a System property via the command + line, but any way that sets that property in a Spring Boot app should work). For example use + + mvn spring-boot:run -Dspring-boot.run.profiles=postgres + + To activate the profile on the command line. diff --git a/backend/src/main/resources/db/postgres/schema.sql b/backend/src/main/resources/db/postgres/schema.sql new file mode 100644 index 0000000..49f997b --- /dev/null +++ b/backend/src/main/resources/db/postgres/schema.sql @@ -0,0 +1,102 @@ +CREATE TABLE IF NOT EXISTS vets ( + id SERIAL, + first_name VARCHAR(30), + last_name VARCHAR(30), + CONSTRAINT pk_vets PRIMARY KEY (id) +); + +CREATE INDEX IF NOT EXISTS idx_vets_last_name ON vets (last_name); + +ALTER SEQUENCE vets_id_seq RESTART WITH 100; + + +CREATE TABLE IF NOT EXISTS specialties ( + id SERIAL, + name VARCHAR(80), + CONSTRAINT pk_specialties PRIMARY KEY (id) +); + +CREATE INDEX IF NOT EXISTS idx_specialties_name ON specialties (name); + +ALTER SEQUENCE specialties_id_seq RESTART WITH 100; + + +CREATE TABLE IF NOT EXISTS vet_specialties ( + vet_id INT NOT NULL, + specialty_id INT NOT NULL, + FOREIGN KEY (vet_id) REFERENCES vets(id), + FOREIGN KEY (specialty_id) REFERENCES specialties(id), + CONSTRAINT unique_ids UNIQUE (vet_id,specialty_id) +); + + + +CREATE TABLE IF NOT EXISTS types ( + id SERIAL, + name VARCHAR(80), + CONSTRAINT pk_types PRIMARY KEY (id) +); + +CREATE INDEX IF NOT EXISTS idx_types_name ON types (name); + +ALTER SEQUENCE types_id_seq RESTART WITH 100; + +CREATE TABLE IF NOT EXISTS owners ( + id SERIAL, + first_name VARCHAR(30), + last_name VARCHAR(30), + address VARCHAR(255), + city VARCHAR(80), + telephone VARCHAR(20), + CONSTRAINT pk_owners PRIMARY KEY (id) +); + +CREATE INDEX IF NOT EXISTS idx_owners_last_name ON owners (last_name); + +ALTER SEQUENCE owners_id_seq RESTART WITH 100; + + +CREATE TABLE IF NOT EXISTS pets ( + id SERIAL, + name VARCHAR(30), + birth_date DATE, + type_id INT NOT NULL, + owner_id INT NOT NULL, + FOREIGN KEY (owner_id) REFERENCES owners(id), + FOREIGN KEY (type_id) REFERENCES types(id), + CONSTRAINT pk_pets PRIMARY KEY (id) +); + +CREATE INDEX IF NOT EXISTS idx_pets_name ON pets (name); + +ALTER SEQUENCE pets_id_seq RESTART WITH 100; + + +CREATE TABLE IF NOT EXISTS visits ( + id SERIAL, + pet_id INT NOT NULL, + visit_date DATE, + description VARCHAR(255), + FOREIGN KEY (pet_id) REFERENCES pets(id), + CONSTRAINT pk_visits PRIMARY KEY (id) +); + +ALTER SEQUENCE visits_id_seq RESTART WITH 100; + +CREATE TABLE IF NOT EXISTS users ( + username VARCHAR(20) NOT NULL , + password VARCHAR(20) NOT NULL , + enabled boolean NOT NULL DEFAULT true , + CONSTRAINT pk_users PRIMARY KEY (username) +); + +CREATE TABLE IF NOT EXISTS roles ( + id SERIAL, + username varchar(20) NOT NULL, + role varchar(20) NOT NULL, + CONSTRAINT pk_roles PRIMARY KEY (id), + FOREIGN KEY (username) REFERENCES users (username) +); + +ALTER TABLE roles ADD CONSTRAINT uni_username_role UNIQUE (role,username); +ALTER SEQUENCE roles_id_seq RESTART WITH 100; diff --git a/backend/src/main/resources/logback.xml b/backend/src/main/resources/logback.xml new file mode 100644 index 0000000..54bfd5f --- /dev/null +++ b/backend/src/main/resources/logback.xml @@ -0,0 +1,22 @@ + + + + + + + true + + + + + %-5level %logger{0} - %msg%n + + + + + + + + + + diff --git a/backend/src/main/resources/messages/messages.properties b/backend/src/main/resources/messages/messages.properties new file mode 100644 index 0000000..173417a --- /dev/null +++ b/backend/src/main/resources/messages/messages.properties @@ -0,0 +1,8 @@ +welcome=Welcome +required=is required +notFound=has not been found +duplicate=is already in use +nonNumeric=must be all numeric +duplicateFormSubmission=Duplicate form submission is not allowed +typeMismatch.date=invalid date +typeMismatch.birthDate=invalid date diff --git a/backend/src/main/resources/messages/messages_de.properties b/backend/src/main/resources/messages/messages_de.properties new file mode 100644 index 0000000..124bee4 --- /dev/null +++ b/backend/src/main/resources/messages/messages_de.properties @@ -0,0 +1,8 @@ +welcome=Willkommen +required=muss angegeben werden +notFound=wurde nicht gefunden +duplicate=ist bereits vergeben +nonNumeric=darf nur numerisch sein +duplicateFormSubmission=Wiederholtes Absenden des Formulars ist nicht erlaubt +typeMismatch.date=ungültiges Datum +typeMismatch.birthDate=ungültiges Datum diff --git a/backend/src/main/resources/messages/messages_en.properties b/backend/src/main/resources/messages/messages_en.properties new file mode 100644 index 0000000..05d519b --- /dev/null +++ b/backend/src/main/resources/messages/messages_en.properties @@ -0,0 +1 @@ +# This file is intentionally empty. Message look-ups will fall back to the default "messages.properties" file. \ No newline at end of file diff --git a/backend/src/main/resources/openapi.yml b/backend/src/main/resources/openapi.yml new file mode 100755 index 0000000..792cfa0 --- /dev/null +++ b/backend/src/main/resources/openapi.yml @@ -0,0 +1,2220 @@ +openapi: 3.0.1 +info: + title: Spring PetClinic + description: Spring PetClinic Sample Application. + license: + name: Apache 2.0 + url: http://www.apache.org/licenses/LICENSE-2.0 + version: '1.0' +servers: + - url: http://localhost:9966/petclinic/api +tags: + - name: failing + description: Endpoint which always returns an error. + - name: owner + description: Endpoints related to pet owners. + - name: user + description: Endpoints related to users. + - name: pet + description: Endpoints related to pets. + - name: vet + description: Endpoints related to vets. + - name: visit + description: Endpoints related to vet visits. + - name: pettypes + description: Endpoints related to pet types. + - name: specialty + description: Endpoints related to vet specialties. +paths: + /oops: + get: + tags: + - failing + operationId: failingRequest + summary: Always fails + description: Produces sample error response. + responses: + 200: + description: Never returned. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + content: + text/plain: + schema: + type: string + 304: + description: Not modified. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + 400: + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + /owners: + post: + tags: + - owner + operationId: addOwner + summary: Adds a pet owner + description: Records the details of a new pet owner. + requestBody: + description: The pet owner + content: + application/json: + schema: + $ref: '#/components/schemas/OwnerFields' + required: true + responses: + 201: + description: The pet owner was successfully added. + content: + application/json: + schema: + $ref: '#/components/schemas/Owner' + 400: + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 500: + description: Server error. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + get: + tags: + - owner + operationId: listOwners + summary: Lists pet owners + description: Returns an array of pet owners. + parameters: + - name: lastName + in: query + description: Last name. + required: false + schema: + type: string + example: Davis + responses: + 200: + description: Owner details found and returned. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Owner' + 304: + description: Not modified. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + 500: + description: Server error. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + /owners/{ownerId}: + get: + tags: + - owner + operationId: getOwner + summary: Get a pet owner by ID + description: Returns the pet owner or a 404 error. + parameters: + - name: ownerId + in: path + description: The ID of the pet owner. + required: true + schema: + type: integer + format: int32 + minimum: 0 + example: 1 + responses: + 200: + description: Owner details found and returned. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/Owner' + 304: + description: Not modified. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + 400: + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 404: + description: Owner not found. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 500: + description: Server error. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + put: + tags: + - owner + operationId: updateOwner + summary: Update a pet owner's details + description: Updates the pet owner record with the specified details. + parameters: + - name: ownerId + in: path + description: The ID of the pet owner. + required: true + schema: + type: integer + format: int32 + minimum: 0 + example: 1 + requestBody: + description: The pet owner details to use for the update. + content: + application/json: + schema: + $ref: '#/components/schemas/OwnerFields' + required: true + responses: + 200: + description: Update successful. + content: + application/json: + schema: + $ref: '#/components/schemas/Owner' + 400: + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 404: + description: Owner not found. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 500: + description: Server error. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + + delete: + tags: + - owner + operationId: deleteOwner + summary: Delete an owner by ID + description: Returns the owner or a 404 error. + parameters: + - name: ownerId + in: path + description: The ID of the owner. + required: true + schema: + type: integer + format: int32 + minimum: 0 + example: 1 + responses: + 200: + description: Owner details found and returned. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/Owner' + 304: + description: Not modified. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + 400: + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 404: + description: Owner not found. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 500: + description: Server error. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + /owners/{ownerId}/pets: + post: + tags: + - pet + operationId: addPetToOwner + summary: Adds a pet to an owner + description: Records the details of a new pet. + parameters: + - name: ownerId + in: path + description: The ID of the pet owner. + required: true + schema: + type: integer + format: int32 + minimum: 0 + example: 1 + requestBody: + description: The details of the new pet. + content: + application/json: + schema: + $ref: '#/components/schemas/PetFields' + required: true + responses: + 201: + description: The pet was successfully added. + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + 400: + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 404: + description: Pet or Owner not found. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 500: + description: Server error. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + /owners/{ownerId}/pets/{petId}: + get: + tags: + - pet + operationId: getOwnersPet + summary: Get a pet by ID + description: Returns the pet or a 404 error. + parameters: + - name: ownerId + in: path + description: The ID of the pet owner. + required: true + schema: + type: integer + format: int32 + minimum: 0 + example: 1 + - name: petId + in: path + description: The ID of the pet. + required: true + schema: + type: integer + format: int32 + minimum: 0 + example: 1 + responses: + 200: + description: Pet details found and returned. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + 304: + description: Not modified. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + 400: + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 404: + description: Owner or pet not found. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 500: + description: Server error. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + put: + tags: + - pet + operationId: updateOwnersPet + + summary: Update a pet's details + description: Updates the pet record with the specified details. + parameters: + - name: ownerId + in: path + description: The ID of the pet owner. + required: true + schema: + type: integer + format: int32 + minimum: 0 + example: 1 + - name: petId + in: path + description: The ID of the pet. + required: true + schema: + type: integer + format: int32 + minimum: 0 + example: 1 + requestBody: + description: The pet details to use for the update. + content: + application/json: + schema: + $ref: '#/components/schemas/PetFields' + required: true + responses: + 204: + description: Update successful. + 400: + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 404: + description: Pet not found for this owner. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 500: + description: Server error. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + /owners/{ownerId}/pets/{petId}/visits: + post: + tags: + - visit + operationId: addVisitToOwner + summary: Adds a vet visit + description: Records the details of a new vet visit. + parameters: + - name: ownerId + in: path + description: The ID of the pet owner. + required: true + schema: + type: integer + format: int32 + minimum: 0 + example: 1 + - name: petId + in: path + description: The ID of the pet. + required: true + schema: + type: integer + format: int32 + minimum: 0 + example: 1 + requestBody: + description: The details of the new vet visit. + content: + application/json: + schema: + $ref: '#/components/schemas/VisitFields' + required: true + responses: + 201: + description: The vet visit was successfully added. + content: + application/json: + schema: + $ref: '#/components/schemas/Visit' + 400: + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 404: + description: Pet not found for this owner. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 500: + description: Server error. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + /pettypes: + get: + tags: + - pettypes + operationId: listPetTypes + summary: Lists pet types + description: Returns an array of pet types. + responses: + 200: + description: Pet types found and returned. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PetType' + 304: + description: Not modified. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + 500: + description: Server error. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + post: + tags: + - pettypes + operationId: addPetType + summary: Create a pet type + description: Creates a pet type . + requestBody: + description: The pet type + content: + application/json: + schema: + $ref: '#/components/schemas/PetTypeFields' + required: true + responses: + 200: + description: Pet type created successfully. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/PetType' + 304: + description: Not modified. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + 400: + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 404: + description: Pet Type not found. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 500: + description: Server error. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + /pettypes/{petTypeId}: + get: + tags: + - pettypes + operationId: getPetType + summary: Get a pet type by ID + description: Returns the pet type or a 404 error. + parameters: + - name: petTypeId + in: path + description: The ID of the pet type. + required: true + schema: + type: integer + format: int32 + minimum: 0 + example: 1 + responses: + 200: + description: Pet type details found and returned. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/PetType' + 304: + description: Not modified. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + 400: + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 404: + description: Pet Type not found. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 500: + description: Server error. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + put: + tags: + - pettypes + operationId: updatePetType + summary: Update a pet type by ID + description: Returns the pet type or a 404 error. + parameters: + - name: petTypeId + in: path + description: The ID of the pet type. + required: true + schema: + type: integer + format: int32 + minimum: 0 + example: 1 + requestBody: + description: The pet type + content: + application/json: + schema: + $ref: '#/components/schemas/PetType' + required: true + responses: + 200: + description: Pet type details found and returned. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/PetType' + 304: + description: Not modified. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + 400: + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 404: + description: Pet Type not found. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 500: + description: Server error. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + delete: + tags: + - pettypes + operationId: deletePetType + summary: Delete a pet type by ID + description: Returns the pet type or a 404 error. + parameters: + - name: petTypeId + in: path + description: The ID of the pet type. + required: true + schema: + type: integer + format: int32 + minimum: 0 + example: 1 + responses: + 200: + description: Pet type details found and returned. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/PetType' + 304: + description: Not modified. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + 400: + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 404: + description: Pet type not found. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 500: + description: Server error. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + + /pets: + get: + tags: + - pet + operationId: listPets + summary: Lists pet + description: Returns an array of pet . + responses: + 200: + description: Pet types found and returned. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + 304: + description: Not modified. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + 500: + description: Server error. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + post: + tags: + - pet + operationId: addPet + summary: Create a pet + description: Creates a pet . + requestBody: + description: The pet + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + required: true + responses: + 201: + description: Pet type created successfully. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + 304: + description: Not modified. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + 400: + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 404: + description: Pet not found. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 500: + description: Server error. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + /pets/{petId}: + get: + tags: + - pet + operationId: getPet + summary: Get a pet by ID + description: Returns the pet or a 404 error. + parameters: + - name: petId + in: path + description: The ID of the pet. + required: true + schema: + type: integer + format: int32 + minimum: 0 + example: 1 + responses: + 200: + description: Pet details found and returned. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + 304: + description: Not modified. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + 400: + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 404: + description: Pet not found. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 500: + description: Server error. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + put: + tags: + - pet + operationId: updatePet + summary: Update a pet by ID + description: Returns the pet or a 404 error. + parameters: + - name: petId + in: path + description: The ID of the pet. + required: true + schema: + type: integer + format: int32 + minimum: 0 + example: 1 + requestBody: + description: The pet + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + required: true + responses: + 200: + description: Pet details found and returned. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + 304: + description: Not modified. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + 400: + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 404: + description: Pet not found. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 500: + description: Server error. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + delete: + tags: + - pet + operationId: deletePet + summary: Delete a pet by ID + description: Returns the pet or a 404 error. + parameters: + - name: petId + in: path + description: The ID of the pet. + required: true + schema: + type: integer + format: int32 + minimum: 0 + example: 1 + responses: + 200: + description: Pet details found and returned. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + 304: + description: Not modified. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + 400: + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 404: + description: Pet not found. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 500: + description: Server error. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + /visits: + get: + tags: + - visit + operationId: listVisits + summary: Lists visits + description: Returns an array of visit . + responses: + 200: + description: visits found and returned. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Visit' + 304: + description: Not modified. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + 500: + description: Server error. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + post: + tags: + - visit + operationId: addVisit + summary: Create a visit + description: Creates a visit. + requestBody: + description: The visit + content: + application/json: + schema: + $ref: '#/components/schemas/Visit' + required: true + responses: + 200: + description: visit created successfully. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/Visit' + 304: + description: Not modified. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + 400: + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 404: + description: Visit not found. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 500: + description: Server error. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + /visits/{visitId}: + get: + tags: + - visit + operationId: getVisit + summary: Get a visit by ID + description: Returns the visit or a 404 error. + parameters: + - name: visitId + in: path + description: The ID of the visit. + required: true + schema: + type: integer + format: int32 + minimum: 0 + example: 1 + responses: + 200: + description: Visit details found and returned. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/Visit' + 304: + description: Not modified. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + 400: + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 404: + description: Visit not found. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 500: + description: Server error. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + put: + tags: + - visit + operationId: updateVisit + summary: Update a visit by ID + description: Returns the visit or a 404 error. + parameters: + - name: visitId + in: path + description: The ID of the visit. + required: true + schema: + type: integer + format: int32 + minimum: 0 + example: 1 + requestBody: + description: The visit + content: + application/json: + schema: + $ref: '#/components/schemas/Visit' + required: true + responses: + 200: + description: Visit details found and returned. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/Visit' + 304: + description: Not modified. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + 400: + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 404: + description: Visit not found. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 500: + description: Server error. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + delete: + tags: + - visit + operationId: deleteVisit + summary: Delete a visit by ID + description: Returns the visit or a 404 error. + parameters: + - name: visitId + in: path + description: The ID of the visit. + required: true + schema: + type: integer + format: int32 + minimum: 0 + example: 1 + responses: + 200: + description: Visit details found and returned. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/Visit' + 304: + description: Not modified. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + 400: + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 404: + description: Visit not found. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 500: + description: Server error. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + /specialties: + get: + tags: + - specialty + operationId: listSpecialties + summary: Lists specialties + description: Returns an array of specialty . + responses: + 200: + description: Specialties found and returned. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Specialty' + 304: + description: Not modified. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + 500: + description: Server error. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + post: + tags: + - specialty + operationId: addSpecialty + summary: Create a specialty + description: Creates a specialty . + requestBody: + description: The specialty + content: + application/json: + schema: + $ref: '#/components/schemas/Specialty' + required: true + responses: + 200: + description: Specialty created successfully. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/Specialty' + 304: + description: Not modified. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + 400: + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 404: + description: Specialty not found. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 500: + description: Server error. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + /specialties/{specialtyId}: + get: + tags: + - specialty + operationId: getSpecialty + summary: Get a specialty by ID + description: Returns the specialty or a 404 error. + parameters: + - name: specialtyId + in: path + description: The ID of the speciality. + required: true + schema: + type: integer + format: int32 + minimum: 0 + example: 1 + responses: + 200: + description: Specialty details found and returned. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/Specialty' + 304: + description: Not modified. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + 400: + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 404: + description: Specialty not found. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 500: + description: Server error. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + put: + tags: + - specialty + operationId: updateSpecialty + summary: Update a specialty by ID + description: Returns the specialty or a 404 error. + parameters: + - name: specialtyId + in: path + description: The ID of the specialty. + required: true + schema: + type: integer + format: int32 + minimum: 0 + example: 1 + requestBody: + description: The pet + content: + application/json: + schema: + $ref: '#/components/schemas/Specialty' + required: true + responses: + 200: + description: Specialty details found and returned. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/Specialty' + 304: + description: Not modified. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + 400: + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 404: + description: Specialty not found. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 500: + description: Server error. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + delete: + tags: + - specialty + operationId: deleteSpecialty + summary: Delete a specialty by ID + description: Returns the specialty or a 404 error. + parameters: + - name: specialtyId + in: path + description: The ID of the specialty. + required: true + schema: + type: integer + format: int32 + minimum: 0 + example: 1 + responses: + 200: + description: Specialty details found and returned. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/Specialty' + 304: + description: Not modified. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + 400: + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 404: + description: Specialty not found. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 500: + description: Server error. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + /vets: + get: + tags: + - vet + operationId: listVets + summary: Lists vets + description: Returns an array of vets. + responses: + 200: + description: Vets found and returned. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Vet' + 304: + description: Not modified. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + 500: + description: Server error. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + + post: + tags: + - vet + operationId: addVet + summary: Create a Vet + description: Creates a vet . + requestBody: + description: The vet + content: + application/json: + schema: + $ref: '#/components/schemas/Vet' + required: true + responses: + 200: + description: Vet created successfully. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/Vet' + 304: + description: Not modified. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + 400: + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 404: + description: Vet not found. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 500: + description: Server error. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + /vets/{vetId}: + get: + tags: + - vet + operationId: getVet + summary: Get a vet by ID + description: Returns the vet or a 404 error. + parameters: + - name: vetId + in: path + description: The ID of the vet. + required: true + schema: + type: integer + format: int32 + minimum: 0 + example: 1 + responses: + 200: + description: Vet details found and returned. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/Vet' + 304: + description: Not modified. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + 400: + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 404: + description: Vet not found. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 500: + description: Server error. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + put: + tags: + - vet + operationId: updateVet + summary: Update a vet by ID + description: Returns the vet or a 404 error. + parameters: + - name: vetId + in: path + description: The ID of the vet. + required: true + schema: + type: integer + format: int32 + minimum: 0 + example: 1 + requestBody: + description: The vet + content: + application/json: + schema: + $ref: '#/components/schemas/Vet' + required: true + responses: + 200: + description: Pet type details found and returned. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/Vet' + 304: + description: Not modified. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + 400: + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 404: + description: Vet not found. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 500: + description: Server error. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + delete: + tags: + - vet + operationId: deleteVet + summary: Delete a vet by ID + description: Returns the vet or a 404 error. + parameters: + - name: vetId + in: path + description: The ID of the vet. + required: true + schema: + type: integer + format: int32 + minimum: 0 + example: 1 + responses: + 200: + description: Vet details found and returned. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/Vet' + 304: + description: Not modified. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + 400: + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 404: + description: Vet not found. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 500: + description: Server error. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + /users: + post: + tags: + - user + operationId: addUser + summary: Create a user + description: Creates a user. + requestBody: + description: The user + content: + application/json: + schema: + $ref: '#/components/schemas/User' + required: true + responses: + 200: + description: User created successfully. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/User' + 304: + description: Not modified. + headers: + ETag: + description: An ID for this version of the response. + schema: + type: string + 400: + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 404: + description: User not found. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' + 500: + description: Server error. + content: + application/json: + schema: + $ref: '#/components/schemas/RestError' +components: + schemas: + RestError: + title: REST Error + description: The schema for all error responses. + type: object + properties: + status: + title: Status + description: The HTTP status code. + type: integer + format: int32 + example: 400 + readOnly: true + error: + title: Error + description: The short error message. + type: string + example: Bad Request + readOnly: true + path: + title: Path + description: The path of the URL for this request. + type: string + format: uri + example: '/api/owners' + readOnly: true + timestamp: + title: Timestamp + description: The time the error occurred. + type: string + format: date-time + example: '2019-08-21T21:41:46.158+0000' + readOnly: true + message: + title: Message + description: The long error message. + type: string + example: 'Request failed schema validation' + readOnly: true + schemaValidationErrors: + title: Schema validation errors + description: Validation errors against the OpenAPI schema. + type: array + items: + $ref: '#/components/schemas/ValidationMessage' + trace: + title: Trace + description: The stacktrace for this error. + type: string + example: 'com.atlassian.oai.validator.springmvc.InvalidRequestException: ...' + readOnly: true + required: + - status + - error + - path + - timestamp + - message + - schemaValidationErrors + ValidationMessage: + title: Validation message + description: Messages describing a validation error. + type: object + properties: + message: + title: Message + description: The validation message. + type: string + example: "[Path '/lastName'] Instance type (null) does not match any allowed primitive type (allowed: ['string'])" + readOnly: true + required: + - message + additionalProperties: true + Specialty: + title: Specialty + description: Fields of specialty of vets. + type: object + properties: + id: + title: ID + description: The ID of the specialty. + type: integer + format: int32 + minimum: 0 + example: 1 + readOnly: true + name: + title: Name + description: The name of the specialty. + type: string + maxLength: 80 + minLength: 1 + example: radiology + required: + - id + - name + OwnerFields: + title: Owner fields + description: Editable fields of a pet owner. + type: object + properties: + firstName: + title: First name + description: The first name of the pet owner. + type: string + minLength: 1 + maxLength: 30 + pattern: '^[a-zA-Z]*$' + example: George + lastName: + title: Last name + description: The last name of the pet owner. + type: string + minLength: 1 + maxLength: 30 + pattern: '^[a-zA-Z]*$' + example: Franklin + address: + title: Address + description: The postal address of the pet owner. + type: string + minLength: 1 + maxLength: 255 + example: '110 W. Liberty St.' + city: + title: City + description: The city of the pet owner. + type: string + minLength: 1 + maxLength: 80 + example: Madison + telephone: + title: Telephone number + description: The telephone number of the pet owner. + type: string + minLength: 1 + maxLength: 20 + pattern: '^[0-9]*$' + example: '6085551023' + required: + - firstName + - lastName + - address + - city + - telephone + Owner: + title: Owner + description: A pet owner. + allOf: + - $ref: '#/components/schemas/OwnerFields' + - type: object + properties: + id: + title: ID + description: The ID of the pet owner. + type: integer + format: int32 + minimum: 0 + example: 1 + readOnly: true + pets: + title: Pets + description: The pets owned by this individual including any booked vet visits. + type: array + items: + $ref: '#/components/schemas/Pet' + readOnly: true + required: + - pets + PetFields: + title: Pet fields + description: Editable fields of a pet. + type: object + properties: + name: + title: Name + description: The name of the pet. + type: string + maxLength: 30 + example: Leo + birthDate: + title: Birth date + description: The date of birth of the pet. + type: string + format: date + example: '2010-09-07' + type: + $ref: '#/components/schemas/PetType' + required: + - name + - birthDate + - type + Pet: + title: Pet + description: A pet. + allOf: + - $ref: '#/components/schemas/PetFields' + - type: object + properties: + id: + title: ID + description: The ID of the pet. + type: integer + format: int32 + minimum: 0 + example: 1 + readOnly: true + ownerId: + title: Owner ID + description: The ID of the pet's owner. + type: integer + format: int32 + minimum: 0 + example: 1 + readOnly: true + visits: + title: Visits + description: Vet visit bookings for this pet. + type: array + items: + $ref: '#/components/schemas/Visit' + readOnly: true + required: + - id + - type + - visits + VetFields: + title: VetFields + description: Editable fields of a veterinarian. + type: object + properties: + firstName: + title: First name + description: The first name of the vet. + type: string + minLength: 1 + maxLength: 30 + pattern: '^[a-zA-Z]*$' + example: 'James' + lastName: + title: Last name + description: The last name of the vet. + type: string + minLength: 1 + maxLength: 30 + pattern: '^[a-zA-Z]*$' + example: 'Carter' + specialties: + title: Specialties + description: The specialties of the vet. + type: array + items: + $ref: '#/components/schemas/Specialty' + required: + - firstName + - lastName + - specialties + Vet: + title: Vet + description: A veterinarian. + allOf: + - $ref: '#/components/schemas/VetFields' + - type: object + properties: + id: + title: ID + description: The ID of the vet. + type: integer + format: int32 + minimum: 0 + example: 1 + readOnly: true + required: + - id + - firstName + - lastName + - specialties + VisitFields: + title: Visit fields + description: Editable fields of a vet visit. + type: object + properties: + date: + title: Date + description: The date of the visit. + type: string + format: date + example: '2013-01-01' + description: + title: Description + description: The description for the visit. + type: string + minLength: 1 + maxLength: 255 + example: 'rabies shot' + required: + - description + Visit: + title: Visit + description: A booking for a vet visit. + allOf: + - $ref: '#/components/schemas/VisitFields' + - type: object + properties: + id: + title: ID + description: The ID of the visit. + type: integer + format: int32 + minimum: 0 + example: 1 + readOnly: true + petId: + title: Pet ID + description: The ID of the pet. + type: integer + format: int32 + minimum: 0 + example: 1 + readOnly: true + required: + - id + PetTypeFields: + title: PetType fields + description: Editable fields of a pet type. + type: object + properties: + name: + title: Name + description: The name of the pet type. + type: string + maxLength: 80 + minLength: 1 + example: cat + required: + - name + PetType: + title: Pet type + description: A pet type. + allOf: + - $ref: '#/components/schemas/PetTypeFields' + - type: object + properties: + id: + title: ID + description: The ID of the pet type. + type: integer + format: int32 + minimum: 0 + example: 1 + required: + - id + User: + title: User + description: An user. + type: object + properties: + username: + title: username + description: The username + type: string + maxLength: 80 + minLength: 1 + example: john.doe + password: + title: Password + description: The password + type: string + maxLength: 80 + minLength: 1 + example: 1234abc + enabled: + title: enabled + description: Indicates if the user is enabled + type: boolean + example: true + roles: + title: Roles + description: The roles of an user + type: array + items: + $ref: '#/components/schemas/Role' + required: + - username + Role: + title: Role + description: A role. + type: object + properties: + name: + title: name + description: The role's name + type: string + maxLength: 80 + minLength: 1 + example: admin + required: + - name diff --git a/backend/src/test/java/org/springframework/samples/petclinic/SpringConfigTests.java b/backend/src/test/java/org/springframework/samples/petclinic/SpringConfigTests.java new file mode 100644 index 0000000..e33631f --- /dev/null +++ b/backend/src/test/java/org/springframework/samples/petclinic/SpringConfigTests.java @@ -0,0 +1,13 @@ +package org.springframework.samples.petclinic; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest(classes = PetClinicApplication.class) +class SpringConfigTests { + + @Test + void contextLoads() { + // Test the Spring configuration + } +} diff --git a/backend/src/test/java/org/springframework/samples/petclinic/model/ValidatorTests.java b/backend/src/test/java/org/springframework/samples/petclinic/model/ValidatorTests.java new file mode 100644 index 0000000..a441303 --- /dev/null +++ b/backend/src/test/java/org/springframework/samples/petclinic/model/ValidatorTests.java @@ -0,0 +1,45 @@ +package org.springframework.samples.petclinic.model; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Locale; +import java.util.Set; + +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Validator; + +import org.junit.jupiter.api.Test; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; + +/** + * @author Michael Isvy + * Simple test to make sure that Bean Validation is working + * (useful when upgrading to a new version of Hibernate Validator/ Bean Validation) + */ +class ValidatorTests { + + private Validator createValidator() { + LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean(); + localValidatorFactoryBean.afterPropertiesSet(); + return localValidatorFactoryBean; + } + + @Test + void shouldNotValidateWhenFirstNameEmpty() { + + LocaleContextHolder.setLocale(Locale.ENGLISH); + Person person = new Person(); + person.setFirstName(""); + person.setLastName("smith"); + + Validator validator = createValidator(); + Set> constraintViolations = validator.validate(person); + + assertThat(constraintViolations.size()).isEqualTo(1); + ConstraintViolation violation = constraintViolations.iterator().next(); + assertThat(violation.getPropertyPath().toString()).isEqualTo("firstName"); + assertThat(violation.getMessage()).isEqualTo("must not be empty"); + } + +} diff --git a/backend/src/test/java/org/springframework/samples/petclinic/rest/controller/OwnerRestControllerTests.java b/backend/src/test/java/org/springframework/samples/petclinic/rest/controller/OwnerRestControllerTests.java new file mode 100644 index 0000000..c456128 --- /dev/null +++ b/backend/src/test/java/org/springframework/samples/petclinic/rest/controller/OwnerRestControllerTests.java @@ -0,0 +1,433 @@ +/* + * Copyright 2016-2017 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 + * + * http://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.rest.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +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.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.samples.petclinic.mapper.OwnerMapper; +import org.springframework.samples.petclinic.mapper.PetMapper; +import org.springframework.samples.petclinic.mapper.VisitMapper; +import org.springframework.samples.petclinic.model.Owner; +import org.springframework.samples.petclinic.rest.advice.ExceptionControllerAdvice; +import org.springframework.samples.petclinic.rest.dto.OwnerDto; +import org.springframework.samples.petclinic.rest.dto.PetDto; +import org.springframework.samples.petclinic.rest.dto.PetTypeDto; +import org.springframework.samples.petclinic.rest.dto.VisitDto; +import org.springframework.samples.petclinic.service.ClinicService; +import org.springframework.samples.petclinic.service.clinicService.ApplicationTestConfig; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + + +/** + * Test class for {@link OwnerRestController} + * + * @author Vitaliy Fedoriv + */ +@SpringBootTest +@ContextConfiguration(classes = ApplicationTestConfig.class) +@WebAppConfiguration +class OwnerRestControllerTests { + + @Autowired + private OwnerRestController ownerRestController; + + @Autowired + private OwnerMapper ownerMapper; + + @Autowired + private PetMapper petMapper; + + @Autowired + private VisitMapper visitMapper; + + @MockBean + private ClinicService clinicService; + + private MockMvc mockMvc; + + private List owners; + + private List pets; + + private List visits; + + @BeforeEach + void initOwners() { + this.mockMvc = MockMvcBuilders.standaloneSetup(ownerRestController) + .setControllerAdvice(new ExceptionControllerAdvice()) + .build(); + owners = new ArrayList<>(); + + OwnerDto ownerWithPet = new OwnerDto(); + owners.add(ownerWithPet.id(1).firstName("George").lastName("Franklin").address("110 W. Liberty St.").city("Madison").telephone("6085551023").addPetsItem(getTestPetWithIdAndName(ownerWithPet, 1, "Rosy"))); + OwnerDto owner = new OwnerDto(); + owners.add(owner.id(2).firstName("Betty").lastName("Davis").address("638 Cardinal Ave.").city("Sun Prairie").telephone("6085551749")); + owner = new OwnerDto(); + owners.add(owner.id(3).firstName("Eduardo").lastName("Rodriquez").address("2693 Commerce St.").city("McFarland").telephone("6085558763")); + owner = new OwnerDto(); + owners.add(owner.id(4).firstName("Harold").lastName("Davis").address("563 Friendly St.").city("Windsor").telephone("6085553198")); + + PetTypeDto petType = new PetTypeDto(); + petType.id(2) + .name("dog"); + + pets = new ArrayList<>(); + PetDto pet = new PetDto(); + pets.add(pet.id(3) + .name("Rosy") + .birthDate(LocalDate.now()) + .type(petType)); + + pet = new PetDto(); + pets.add(pet.id(4) + .name("Jewel") + .birthDate(LocalDate.now()) + .type(petType)); + + visits = new ArrayList<>(); + VisitDto visit = new VisitDto(); + visit.setId(2); + visit.setPetId(pet.getId()); + visit.setDate(LocalDate.now()); + visit.setDescription("rabies shot"); + visits.add(visit); + + visit = new VisitDto(); + visit.setId(3); + visit.setPetId(pet.getId()); + visit.setDate(LocalDate.now()); + visit.setDescription("neutered"); + visits.add(visit); + } + + private PetDto getTestPetWithIdAndName(final OwnerDto owner, final int id, final String name) { + PetTypeDto petType = new PetTypeDto(); + PetDto pet = new PetDto(); + pet.id(id).name(name).birthDate(LocalDate.now()).type(petType.id(2).name("dog")).addVisitsItem(getTestVisitForPet(pet, 1)); + return pet; + } + + private VisitDto getTestVisitForPet(final PetDto pet, final int id) { + VisitDto visit = new VisitDto(); + return visit.id(id).date(LocalDate.now()).description("test" + id); + } + + @Test + @WithMockUser(roles = "OWNER_ADMIN") + void testGetOwnerSuccess() throws Exception { + given(this.clinicService.findOwnerById(1)).willReturn(ownerMapper.toOwner(owners.get(0))); + this.mockMvc.perform(get("/api/owners/1") + .accept(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/json")) + .andExpect(jsonPath("$.id").value(1)) + .andExpect(jsonPath("$.firstName").value("George")); + } + + @Test + @WithMockUser(roles = "OWNER_ADMIN") + void testGetOwnerNotFound() throws Exception { + given(this.clinicService.findOwnerById(2)).willReturn(null); + this.mockMvc.perform(get("/api/owners/2") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + @WithMockUser(roles = "OWNER_ADMIN") + void testGetOwnersListSuccess() throws Exception { + owners.remove(0); + owners.remove(1); + given(this.clinicService.findOwnerByLastName("Davis")).willReturn(ownerMapper.toOwners(owners)); + this.mockMvc.perform(get("/api/owners?lastName=Davis") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/json")) + .andExpect(jsonPath("$.[0].id").value(2)) + .andExpect(jsonPath("$.[0].firstName").value("Betty")) + .andExpect(jsonPath("$.[1].id").value(4)) + .andExpect(jsonPath("$.[1].firstName").value("Harold")); + } + + @Test + @WithMockUser(roles = "OWNER_ADMIN") + void testGetOwnersListNotFound() throws Exception { + owners.clear(); + given(this.clinicService.findOwnerByLastName("0")).willReturn(ownerMapper.toOwners(owners)); + this.mockMvc.perform(get("/api/owners/?lastName=0") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + @WithMockUser(roles = "OWNER_ADMIN") + void testGetAllOwnersSuccess() throws Exception { + owners.remove(0); + owners.remove(1); + given(this.clinicService.findAllOwners()).willReturn(ownerMapper.toOwners(owners)); + this.mockMvc.perform(get("/api/owners/") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/json")) + .andExpect(jsonPath("$.[0].id").value(2)) + .andExpect(jsonPath("$.[0].firstName").value("Betty")) + .andExpect(jsonPath("$.[1].id").value(4)) + .andExpect(jsonPath("$.[1].firstName").value("Harold")); + } + + @Test + @WithMockUser(roles = "OWNER_ADMIN") + void testGetAllOwnersNotFound() throws Exception { + owners.clear(); + given(this.clinicService.findAllOwners()).willReturn(ownerMapper.toOwners(owners)); + this.mockMvc.perform(get("/api/owners/") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + @WithMockUser(roles = "OWNER_ADMIN") + void testCreateOwnerSuccess() throws Exception { + OwnerDto newOwnerDto = owners.get(0); + newOwnerDto.setId(null); + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + String newOwnerAsJSON = mapper.writeValueAsString(newOwnerDto); + this.mockMvc.perform(post("/api/owners/") + .content(newOwnerAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isCreated()); + } + + @Test + @WithMockUser(roles = "OWNER_ADMIN") + void testCreateOwnerError() throws Exception { + OwnerDto newOwnerDto = owners.get(0); + newOwnerDto.setId(null); + newOwnerDto.setFirstName(null); + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + String newOwnerAsJSON = mapper.writeValueAsString(newOwnerDto); + this.mockMvc.perform(post("/api/owners/") + .content(newOwnerAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isBadRequest()); + } + + @Test + @WithMockUser(roles = "OWNER_ADMIN") + void testUpdateOwnerSuccess() throws Exception { + given(this.clinicService.findOwnerById(1)).willReturn(ownerMapper.toOwner(owners.get(0))); + int ownerId = owners.get(0).getId(); + OwnerDto updatedOwnerDto = new OwnerDto(); + // body.id = ownerId which is used in url path + updatedOwnerDto.setId(ownerId); + updatedOwnerDto.setFirstName("GeorgeI"); + updatedOwnerDto.setLastName("Franklin"); + updatedOwnerDto.setAddress("110 W. Liberty St."); + updatedOwnerDto.setCity("Madison"); + updatedOwnerDto.setTelephone("6085551023"); + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + String newOwnerAsJSON = mapper.writeValueAsString(updatedOwnerDto); + this.mockMvc.perform(put("/api/owners/" + ownerId) + .content(newOwnerAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(content().contentType("application/json")) + .andExpect(status().isNoContent()); + + this.mockMvc.perform(get("/api/owners/" + ownerId) + .accept(MediaType.APPLICATION_JSON).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/json")) + .andExpect(jsonPath("$.id").value(ownerId)) + .andExpect(jsonPath("$.firstName").value("GeorgeI")); + + } + + @Test + @WithMockUser(roles = "OWNER_ADMIN") + void testUpdateOwnerSuccessNoBodyId() throws Exception { + given(this.clinicService.findOwnerById(1)).willReturn(ownerMapper.toOwner(owners.get(0))); + int ownerId = owners.get(0).getId(); + OwnerDto updatedOwnerDto = new OwnerDto(); + updatedOwnerDto.setFirstName("GeorgeI"); + updatedOwnerDto.setLastName("Franklin"); + updatedOwnerDto.setAddress("110 W. Liberty St."); + updatedOwnerDto.setCity("Madison"); + + updatedOwnerDto.setTelephone("6085551023"); + ObjectMapper mapper = new ObjectMapper(); + String newOwnerAsJSON = mapper.writeValueAsString(updatedOwnerDto); + this.mockMvc.perform(put("/api/owners/" + ownerId) + .content(newOwnerAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(content().contentType("application/json")) + .andExpect(status().isNoContent()); + + this.mockMvc.perform(get("/api/owners/" + ownerId) + .accept(MediaType.APPLICATION_JSON).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/json")) + .andExpect(jsonPath("$.id").value(ownerId)) + .andExpect(jsonPath("$.firstName").value("GeorgeI")); + + } + + @Test + @WithMockUser(roles = "OWNER_ADMIN") + void testUpdateOwnerError() throws Exception { + OwnerDto newOwnerDto = owners.get(0); + newOwnerDto.setFirstName(""); + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + String newOwnerAsJSON = mapper.writeValueAsString(newOwnerDto); + this.mockMvc.perform(put("/api/owners/1") + .content(newOwnerAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isBadRequest()); + } + + @Test + @WithMockUser(roles = "OWNER_ADMIN") + void testDeleteOwnerSuccess() throws Exception { + OwnerDto newOwnerDto = owners.get(0); + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + String newOwnerAsJSON = mapper.writeValueAsString(newOwnerDto); + final Owner owner = ownerMapper.toOwner(owners.get(0)); + given(this.clinicService.findOwnerById(1)).willReturn(owner); + this.mockMvc.perform(delete("/api/owners/1") + .content(newOwnerAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isNoContent()); + } + + @Test + @WithMockUser(roles = "OWNER_ADMIN") + void testDeleteOwnerError() throws Exception { + OwnerDto newOwnerDto = owners.get(0); + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + String newOwnerAsJSON = mapper.writeValueAsString(newOwnerDto); + given(this.clinicService.findOwnerById(999)).willReturn(null); + this.mockMvc.perform(delete("/api/owners/999") + .content(newOwnerAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isNotFound()); + } + + @Test + @WithMockUser(roles = "OWNER_ADMIN") + void testCreatePetSuccess() throws Exception { + PetDto newPet = pets.get(0); + newPet.setId(999); + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd")); + mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + String newPetAsJSON = mapper.writeValueAsString(newPet); + System.err.println("--> newPetAsJSON=" + newPetAsJSON); + this.mockMvc.perform(post("/api/owners/1/pets/") + .content(newPetAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isCreated()); + } + + @Test + @WithMockUser(roles = "OWNER_ADMIN") + void testCreatePetError() throws Exception { + PetDto newPet = pets.get(0); + newPet.setId(null); + newPet.setName(null); + ObjectMapper mapper = new ObjectMapper(); + mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd")); + mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + mapper.registerModule(new JavaTimeModule()); + String newPetAsJSON = mapper.writeValueAsString(newPet); + this.mockMvc.perform(post("/api/owners/1/pets/") + .content(newPetAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isBadRequest()).andDo(MockMvcResultHandlers.print()); + } + + @Test + @WithMockUser(roles = "OWNER_ADMIN") + void testCreateVisitSuccess() throws Exception { + VisitDto newVisit = visits.get(0); + newVisit.setId(999); + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + String newVisitAsJSON = mapper.writeValueAsString(visitMapper.toVisit(newVisit)); + System.out.println("newVisitAsJSON " + newVisitAsJSON); + this.mockMvc.perform(post("/api/owners/1/pets/1/visits") + .content(newVisitAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isCreated()); + } + + @Test + @WithMockUser(roles = "OWNER_ADMIN") + void testGetOwnerPetSuccess() throws Exception { + var owner = ownerMapper.toOwner(owners.get(0)); + given(this.clinicService.findOwnerById(2)).willReturn(owner); + var pet = petMapper.toPet(pets.get(0)); + pet.setOwner(owner); + given(this.clinicService.findPetById(1)).willReturn(pet); + this.mockMvc.perform(get("/api/owners/2/pets/1") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/json")); + } + + @Test + @WithMockUser(roles = "OWNER_ADMIN") + void testGetOwnersPetsWithOwnerNotFound() throws Exception { + owners.clear(); + given(this.clinicService.findAllOwners()).willReturn(ownerMapper.toOwners(owners)); + this.mockMvc.perform(get("/api/owners/1/pets/1") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + @WithMockUser(roles = "OWNER_ADMIN") + void testGetOwnersPetsWithPetNotFound() throws Exception { + var owner1 = ownerMapper.toOwner(owners.get(0)); + given(this.clinicService.findOwnerById(1)).willReturn(owner1); + this.mockMvc.perform(get("/api/owners/1/pets/2") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + +} diff --git a/backend/src/test/java/org/springframework/samples/petclinic/rest/controller/PetRestControllerTests.java b/backend/src/test/java/org/springframework/samples/petclinic/rest/controller/PetRestControllerTests.java new file mode 100644 index 0000000..c6029fe --- /dev/null +++ b/backend/src/test/java/org/springframework/samples/petclinic/rest/controller/PetRestControllerTests.java @@ -0,0 +1,250 @@ +/* + * Copyright 2016-2017 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 + * + * http://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.rest.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +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.mock.mockito.MockBean; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.samples.petclinic.mapper.PetMapper; +import org.springframework.samples.petclinic.model.Pet; +import org.springframework.samples.petclinic.rest.advice.ExceptionControllerAdvice; +import org.springframework.samples.petclinic.rest.dto.OwnerDto; +import org.springframework.samples.petclinic.rest.dto.PetDto; +import org.springframework.samples.petclinic.rest.dto.PetTypeDto; +import org.springframework.samples.petclinic.service.ClinicService; +import org.springframework.samples.petclinic.service.clinicService.ApplicationTestConfig; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + + +/** + * Test class for {@link PetRestController} + * + * @author Vitaliy Fedoriv + */ + +@SpringBootTest +@ContextConfiguration(classes = ApplicationTestConfig.class) +@WebAppConfiguration +class PetRestControllerTests { + + @MockBean + protected ClinicService clinicService; + @Autowired + private PetRestController petRestController; + @Autowired + private PetMapper petMapper; + private MockMvc mockMvc; + + private List pets; + + @BeforeEach + void initPets() { + this.mockMvc = MockMvcBuilders.standaloneSetup(petRestController) + .setControllerAdvice(new ExceptionControllerAdvice()) + .build(); + pets = new ArrayList<>(); + + OwnerDto owner = new OwnerDto(); + owner.id(1).firstName("Eduardo") + .lastName("Rodriquez") + .address("2693 Commerce St.") + .city("McFarland") + .telephone("6085558763"); + + PetTypeDto petType = new PetTypeDto(); + petType.id(2) + .name("dog"); + + PetDto pet = new PetDto(); + pets.add(pet.id(3) + .name("Rosy") + .birthDate(LocalDate.now()) + .type(petType)); + + pet = new PetDto(); + pets.add(pet.id(4) + .name("Jewel") + .birthDate(LocalDate.now()) + .type(petType)); + } + + @Test + @WithMockUser(roles = "OWNER_ADMIN") + void testGetPetSuccess() throws Exception { + given(this.clinicService.findPetById(3)).willReturn(petMapper.toPet(pets.get(0))); + this.mockMvc.perform(get("/api/pets/3") + .accept(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/json")) + .andExpect(jsonPath("$.id").value(3)) + .andExpect(jsonPath("$.name").value("Rosy")); + } + + @Test + @WithMockUser(roles = "OWNER_ADMIN") + void testGetPetNotFound() throws Exception { + given(petMapper.toPetDto(this.clinicService.findPetById(-1))).willReturn(null); + this.mockMvc.perform(get("/api/pets/999") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + @WithMockUser(roles = "OWNER_ADMIN") + void testGetAllPetsSuccess() throws Exception { + final Collection pets = petMapper.toPets(this.pets); + System.err.println(pets); + when(this.clinicService.findAllPets()).thenReturn(pets); + //given(this.clinicService.findAllPets()).willReturn(petMapper.toPets(pets)); + this.mockMvc.perform(get("/api/pets/") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/json")) + .andExpect(jsonPath("$.[0].id").value(3)) + .andExpect(jsonPath("$.[0].name").value("Rosy")) + .andExpect(jsonPath("$.[1].id").value(4)) + .andExpect(jsonPath("$.[1].name").value("Jewel")); + } + + @Test + @WithMockUser(roles = "OWNER_ADMIN") + void testGetAllPetsNotFound() throws Exception { + pets.clear(); + given(this.clinicService.findAllPets()).willReturn(petMapper.toPets(pets)); + this.mockMvc.perform(get("/api/pets/") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + @WithMockUser(roles = "OWNER_ADMIN") + void testUpdatePetSuccess() throws Exception { + given(this.clinicService.findPetById(3)).willReturn(petMapper.toPet(pets.get(0))); + PetDto newPet = pets.get(0); + newPet.setName("Rosy I"); + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd")); + mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + + String newPetAsJSON = mapper.writeValueAsString(newPet); + this.mockMvc.perform(put("/api/pets/3") + .content(newPetAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(content().contentType("application/json")) + .andExpect(status().isNoContent()); + + this.mockMvc.perform(get("/api/pets/3") + .accept(MediaType.APPLICATION_JSON).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/json")) + .andExpect(jsonPath("$.id").value(3)) + .andExpect(jsonPath("$.name").value("Rosy I")); + + } + + @Test + @WithMockUser(roles = "OWNER_ADMIN") + void testUpdatePetError() throws Exception { + PetDto newPet = pets.get(0); + newPet.setName(null); + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd")); + mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + String newPetAsJSON = mapper.writeValueAsString(newPet); + + this.mockMvc.perform(put("/api/pets/3") + .content(newPetAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isBadRequest()); + } + + @Test + @WithMockUser(roles = "OWNER_ADMIN") + void testDeletePetSuccess() throws Exception { + PetDto newPet = pets.get(0); + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + String newPetAsJSON = mapper.writeValueAsString(newPet); + given(this.clinicService.findPetById(3)).willReturn(petMapper.toPet(pets.get(0))); + this.mockMvc.perform(delete("/api/pets/3") + .content(newPetAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isNoContent()); + } + + @Test + @WithMockUser(roles = "OWNER_ADMIN") + void testDeletePetError() throws Exception { + PetDto newPet = pets.get(0); + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + String newPetAsJSON = mapper.writeValueAsString(newPet); + given(this.clinicService.findPetById(999)).willReturn(null); + this.mockMvc.perform(delete("/api/pets/999") + .content(newPetAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isNotFound()); + } + + @Test + @WithMockUser(roles = "OWNER_ADMIN") + void testAddPetSuccess() throws Exception { + PetDto newPet = pets.get(0); + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + String newPetAsJSON = mapper.writeValueAsString(newPet); + given(this.clinicService.findPetById(3)).willReturn(petMapper.toPet(pets.get(0))); + this.mockMvc.perform(post("/api/pets") + .content(newPetAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isCreated()) + .andExpect(header().string(HttpHeaders.LOCATION, "/api/pets/3")); + } + + @Test + @WithMockUser(roles = "OWNER_ADMIN") + void testAddPetError() throws Exception { + PetDto newPet = pets.get(0); + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + String newPetAsJSON = mapper.writeValueAsString(newPet); + given(this.clinicService.findPetById(999)).willReturn(null); + this.mockMvc.perform(post("/api/pets") + // set empty JSON to force 400 error + .content("{}").accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isBadRequest()); + } +} diff --git a/backend/src/test/java/org/springframework/samples/petclinic/rest/controller/PetTypeRestControllerTests.java b/backend/src/test/java/org/springframework/samples/petclinic/rest/controller/PetTypeRestControllerTests.java new file mode 100644 index 0000000..2e21d73 --- /dev/null +++ b/backend/src/test/java/org/springframework/samples/petclinic/rest/controller/PetTypeRestControllerTests.java @@ -0,0 +1,253 @@ +/* + * Copyright 2016-2017 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 + * + * http://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.rest.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +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.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.samples.petclinic.mapper.PetTypeMapper; +import org.springframework.samples.petclinic.model.PetType; +import org.springframework.samples.petclinic.rest.advice.ExceptionControllerAdvice; +import org.springframework.samples.petclinic.service.ClinicService; +import org.springframework.samples.petclinic.service.clinicService.ApplicationTestConfig; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.util.ArrayList; +import java.util.List; + +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + + +/** + * Test class for {@link PetTypeRestController} + * + * @author Vitaliy Fedoriv + */ +@SpringBootTest +@ContextConfiguration(classes=ApplicationTestConfig.class) +@WebAppConfiguration +class PetTypeRestControllerTests { + + @Autowired + private PetTypeRestController petTypeRestController; + + @Autowired + private PetTypeMapper petTypeMapper; + + @MockBean + private ClinicService clinicService; + + private MockMvc mockMvc; + + private List petTypes; + + @BeforeEach + void initPetTypes(){ + this.mockMvc = MockMvcBuilders.standaloneSetup(petTypeRestController) + .setControllerAdvice(new ExceptionControllerAdvice()) + .build(); + petTypes = new ArrayList<>(); + + PetType petType = new PetType(); + petType.setId(1); + petType.setName("cat"); + petTypes.add(petType); + + petType = new PetType(); + petType.setId(2); + petType.setName("dog"); + petTypes.add(petType); + + petType = new PetType(); + petType.setId(3); + petType.setName("lizard"); + petTypes.add(petType); + + petType = new PetType(); + petType.setId(4); + petType.setName("snake"); + petTypes.add(petType); + } + + @Test + @WithMockUser(roles="OWNER_ADMIN") + void testGetPetTypeSuccessAsOwnerAdmin() throws Exception { + given(this.clinicService.findPetTypeById(1)).willReturn(petTypes.get(0)); + this.mockMvc.perform(get("/api/pettypes/1") + .accept(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/json")) + .andExpect(jsonPath("$.id").value(1)) + .andExpect(jsonPath("$.name").value("cat")); + } + + @Test + @WithMockUser(roles="VET_ADMIN") + void testGetPetTypeSuccessAsVetAdmin() throws Exception { + given(this.clinicService.findPetTypeById(1)).willReturn(petTypes.get(0)); + this.mockMvc.perform(get("/api/pettypes/1") + .accept(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/json")) + .andExpect(jsonPath("$.id").value(1)) + .andExpect(jsonPath("$.name").value("cat")); + } + + @Test + @WithMockUser(roles="OWNER_ADMIN") + void testGetPetTypeNotFound() throws Exception { + given(this.clinicService.findPetTypeById(999)).willReturn(null); + this.mockMvc.perform(get("/api/pettypes/999") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + @WithMockUser(roles="OWNER_ADMIN") + void testGetAllPetTypesSuccessAsOwnerAdmin() throws Exception { + petTypes.remove(0); + petTypes.remove(1); + given(this.clinicService.findAllPetTypes()).willReturn(petTypes); + this.mockMvc.perform(get("/api/pettypes/") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/json")) + .andExpect(jsonPath("$.[0].id").value(2)) + .andExpect(jsonPath("$.[0].name").value("dog")) + .andExpect(jsonPath("$.[1].id").value(4)) + .andExpect(jsonPath("$.[1].name").value("snake")); + } + + @Test + @WithMockUser(roles="VET_ADMIN") + void testGetAllPetTypesSuccessAsVetAdmin() throws Exception { + petTypes.remove(0); + petTypes.remove(1); + given(this.clinicService.findAllPetTypes()).willReturn(petTypes); + this.mockMvc.perform(get("/api/pettypes/") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/json")) + .andExpect(jsonPath("$.[0].id").value(2)) + .andExpect(jsonPath("$.[0].name").value("dog")) + .andExpect(jsonPath("$.[1].id").value(4)) + .andExpect(jsonPath("$.[1].name").value("snake")); + } + + @Test + @WithMockUser(roles="VET_ADMIN") + void testGetAllPetTypesNotFound() throws Exception { + petTypes.clear(); + given(this.clinicService.findAllPetTypes()).willReturn(petTypes); + this.mockMvc.perform(get("/api/pettypes/") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + @WithMockUser(roles="VET_ADMIN") + void testCreatePetTypeSuccess() throws Exception { + PetType newPetType = petTypes.get(0); + newPetType.setId(null); + ObjectMapper mapper = new ObjectMapper(); + String newPetTypeAsJSON = mapper.writeValueAsString(petTypeMapper.toPetTypeFieldsDto(newPetType)); + this.mockMvc.perform(post("/api/pettypes/") + .content(newPetTypeAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isCreated()); + } + + @Test + @WithMockUser(roles="VET_ADMIN") + void testCreatePetTypeError() throws Exception { + PetType newPetType = petTypes.get(0); + newPetType.setId(null); + newPetType.setName(null); + ObjectMapper mapper = new ObjectMapper(); + String newPetTypeAsJSON = mapper.writeValueAsString(petTypeMapper.toPetTypeDto(newPetType)); + this.mockMvc.perform(post("/api/pettypes/") + .content(newPetTypeAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isBadRequest()); + } + + @Test + @WithMockUser(roles="VET_ADMIN") + void testUpdatePetTypeSuccess() throws Exception { + given(this.clinicService.findPetTypeById(2)).willReturn(petTypes.get(1)); + PetType newPetType = petTypes.get(1); + newPetType.setName("dog I"); + ObjectMapper mapper = new ObjectMapper(); + String newPetTypeAsJSON = mapper.writeValueAsString(petTypeMapper.toPetTypeDto(newPetType)); + this.mockMvc.perform(put("/api/pettypes/2") + .content(newPetTypeAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(content().contentType("application/json")) + .andExpect(status().isNoContent()); + + this.mockMvc.perform(get("/api/pettypes/2") + .accept(MediaType.APPLICATION_JSON).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/json")) + .andExpect(jsonPath("$.id").value(2)) + .andExpect(jsonPath("$.name").value("dog I")); + } + + @Test + @WithMockUser(roles="VET_ADMIN") + void testUpdatePetTypeError() throws Exception { + PetType newPetType = petTypes.get(0); + newPetType.setName(""); + ObjectMapper mapper = new ObjectMapper(); + String newPetTypeAsJSON = mapper.writeValueAsString(petTypeMapper.toPetTypeDto(newPetType)); + this.mockMvc.perform(put("/api/pettypes/1") + .content(newPetTypeAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isBadRequest()); + } + + @Test + @WithMockUser(roles="VET_ADMIN") + void testDeletePetTypeSuccess() throws Exception { + PetType newPetType = petTypes.get(0); + ObjectMapper mapper = new ObjectMapper(); + String newPetTypeAsJSON = mapper.writeValueAsString(newPetType); + given(this.clinicService.findPetTypeById(1)).willReturn(petTypes.get(0)); + this.mockMvc.perform(delete("/api/pettypes/1") + .content(newPetTypeAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isNoContent()); + } + + @Test + @WithMockUser(roles="VET_ADMIN") + void testDeletePetTypeError() throws Exception { + PetType newPetType = petTypes.get(0); + ObjectMapper mapper = new ObjectMapper(); + String newPetTypeAsJSON = mapper.writeValueAsString(petTypeMapper.toPetTypeDto(newPetType)); + given(this.clinicService.findPetTypeById(999)).willReturn(null); + this.mockMvc.perform(delete("/api/pettypes/999") + .content(newPetTypeAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isNotFound()); + } + +} diff --git a/backend/src/test/java/org/springframework/samples/petclinic/rest/controller/SpecialtyRestControllerTests.java b/backend/src/test/java/org/springframework/samples/petclinic/rest/controller/SpecialtyRestControllerTests.java new file mode 100644 index 0000000..2eaaa2d --- /dev/null +++ b/backend/src/test/java/org/springframework/samples/petclinic/rest/controller/SpecialtyRestControllerTests.java @@ -0,0 +1,218 @@ +/* + * Copyright 2016-2017 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 + * + * http://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.rest.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +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.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.samples.petclinic.mapper.SpecialtyMapper; +import org.springframework.samples.petclinic.model.Specialty; +import org.springframework.samples.petclinic.rest.advice.ExceptionControllerAdvice; +import org.springframework.samples.petclinic.service.ClinicService; +import org.springframework.samples.petclinic.service.clinicService.ApplicationTestConfig; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.util.ArrayList; +import java.util.List; + +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * Test class for {@link SpecialtyRestController} + * + * @author Vitaliy Fedoriv + */ +@SpringBootTest +@ContextConfiguration(classes=ApplicationTestConfig.class) +@WebAppConfiguration +class SpecialtyRestControllerTests { + + @Autowired + private SpecialtyRestController specialtyRestController; + + @Autowired + private SpecialtyMapper specialtyMapper; + + @MockBean + private ClinicService clinicService; + + private MockMvc mockMvc; + + private List specialties; + + @BeforeEach + void initSpecialtys(){ + this.mockMvc = MockMvcBuilders.standaloneSetup(specialtyRestController) + .setControllerAdvice(new ExceptionControllerAdvice()) + .build(); + specialties = new ArrayList(); + + Specialty specialty = new Specialty(); + specialty.setId(1); + specialty.setName("radiology"); + specialties.add(specialty); + + specialty = new Specialty(); + specialty.setId(2); + specialty.setName("surgery"); + specialties.add(specialty); + + specialty = new Specialty(); + specialty.setId(3); + specialty.setName("dentistry"); + specialties.add(specialty); + + } + + @Test + @WithMockUser(roles="VET_ADMIN") + void testGetSpecialtySuccess() throws Exception { + given(this.clinicService.findSpecialtyById(1)).willReturn(specialties.get(0)); + this.mockMvc.perform(get("/api/specialties/1") + .accept(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/json")) + .andExpect(jsonPath("$.id").value(1)) + .andExpect(jsonPath("$.name").value("radiology")); + } + + @Test + @WithMockUser(roles="VET_ADMIN") + void testGetSpecialtyNotFound() throws Exception { + given(this.clinicService.findSpecialtyById(999)).willReturn(null); + this.mockMvc.perform(get("/api/specialties/999") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + @WithMockUser(roles="VET_ADMIN") + void testGetAllSpecialtysSuccess() throws Exception { + specialties.remove(0); + given(this.clinicService.findAllSpecialties()).willReturn(specialties); + this.mockMvc.perform(get("/api/specialties/") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/json")) + .andExpect(jsonPath("$.[0].id").value(2)) + .andExpect(jsonPath("$.[0].name").value("surgery")) + .andExpect(jsonPath("$.[1].id").value(3)) + .andExpect(jsonPath("$.[1].name").value("dentistry")); + } + + @Test + @WithMockUser(roles="VET_ADMIN") + void testGetAllSpecialtysNotFound() throws Exception { + specialties.clear(); + given(this.clinicService.findAllSpecialties()).willReturn(specialties); + this.mockMvc.perform(get("/api/specialties/") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + @WithMockUser(roles="VET_ADMIN") + void testCreateSpecialtySuccess() throws Exception { + Specialty newSpecialty = specialties.get(0); + newSpecialty.setId(999); + ObjectMapper mapper = new ObjectMapper(); + String newSpecialtyAsJSON = mapper.writeValueAsString(specialtyMapper.toSpecialtyDto(newSpecialty)); + this.mockMvc.perform(post("/api/specialties/") + .content(newSpecialtyAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isCreated()); + } + + @Test + @WithMockUser(roles="VET_ADMIN") + void testCreateSpecialtyError() throws Exception { + Specialty newSpecialty = specialties.get(0); + newSpecialty.setId(null); + newSpecialty.setName(null); + ObjectMapper mapper = new ObjectMapper(); + String newSpecialtyAsJSON = mapper.writeValueAsString(specialtyMapper.toSpecialtyDto(newSpecialty)); + this.mockMvc.perform(post("/api/specialties/") + .content(newSpecialtyAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isBadRequest()); + } + + @Test + @WithMockUser(roles="VET_ADMIN") + void testUpdateSpecialtySuccess() throws Exception { + given(this.clinicService.findSpecialtyById(2)).willReturn(specialties.get(1)); + Specialty newSpecialty = specialties.get(1); + newSpecialty.setName("surgery I"); + ObjectMapper mapper = new ObjectMapper(); + String newSpecialtyAsJSON = mapper.writeValueAsString(specialtyMapper.toSpecialtyDto(newSpecialty)); + this.mockMvc.perform(put("/api/specialties/2") + .content(newSpecialtyAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(content().contentType("application/json")) + .andExpect(status().isNoContent()); + + this.mockMvc.perform(get("/api/specialties/2") + .accept(MediaType.APPLICATION_JSON).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/json")) + .andExpect(jsonPath("$.id").value(2)) + .andExpect(jsonPath("$.name").value("surgery I")); + } + + @Test + @WithMockUser(roles="VET_ADMIN") + void testUpdateSpecialtyError() throws Exception { + Specialty newSpecialty = specialties.get(0); + newSpecialty.setName(""); + ObjectMapper mapper = new ObjectMapper(); + String newSpecialtyAsJSON = mapper.writeValueAsString(specialtyMapper.toSpecialtyDto(newSpecialty)); + this.mockMvc.perform(put("/api/specialties/1") + .content(newSpecialtyAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isBadRequest()); + } + + @Test + @WithMockUser(roles="VET_ADMIN") + void testDeleteSpecialtySuccess() throws Exception { + Specialty newSpecialty = specialties.get(0); + ObjectMapper mapper = new ObjectMapper(); + String newSpecialtyAsJSON = mapper.writeValueAsString(specialtyMapper.toSpecialtyDto(newSpecialty)); + given(this.clinicService.findSpecialtyById(1)).willReturn(specialties.get(0)); + this.mockMvc.perform(delete("/api/specialties/1") + .content(newSpecialtyAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isNoContent()); + } + + @Test + @WithMockUser(roles="VET_ADMIN") + void testDeleteSpecialtyError() throws Exception { + Specialty newSpecialty = specialties.get(0); + ObjectMapper mapper = new ObjectMapper(); + String newSpecialtyAsJSON = mapper.writeValueAsString(specialtyMapper.toSpecialtyDto(newSpecialty)); + given(this.clinicService.findSpecialtyById(999)).willReturn(null); + this.mockMvc.perform(delete("/api/specialties/999") + .content(newSpecialtyAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isNotFound()); + } +} diff --git a/backend/src/test/java/org/springframework/samples/petclinic/rest/controller/UserRestControllerTests.java b/backend/src/test/java/org/springframework/samples/petclinic/rest/controller/UserRestControllerTests.java new file mode 100644 index 0000000..ed65ee1 --- /dev/null +++ b/backend/src/test/java/org/springframework/samples/petclinic/rest/controller/UserRestControllerTests.java @@ -0,0 +1,75 @@ +package org.springframework.samples.petclinic.rest.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.samples.petclinic.mapper.UserMapper; +import org.springframework.samples.petclinic.model.User; +import org.springframework.samples.petclinic.rest.advice.ExceptionControllerAdvice; +import org.springframework.samples.petclinic.rest.controller.UserRestController; +import org.springframework.samples.petclinic.service.UserService; +import org.springframework.samples.petclinic.service.clinicService.ApplicationTestConfig; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@ContextConfiguration(classes = ApplicationTestConfig.class) +@WebAppConfiguration +class UserRestControllerTests { + + @Mock + private UserService userService; + + @Autowired + private UserMapper userMapper; + + @Autowired + private UserRestController userRestController; + + private MockMvc mockMvc; + + @BeforeEach + void initVets() { + this.mockMvc = MockMvcBuilders.standaloneSetup(userRestController) + .setControllerAdvice(new ExceptionControllerAdvice()).build(); + } + + @Test + @WithMockUser(roles = "ADMIN") + void testCreateUserSuccess() throws Exception { + User user = new User(); + user.setUsername("username"); + user.setPassword("password"); + user.setEnabled(true); + user.addRole("OWNER_ADMIN"); + ObjectMapper mapper = new ObjectMapper(); + String newVetAsJSON = mapper.writeValueAsString(userMapper.toUserDto(user)); + this.mockMvc.perform(post("/api/users/") + .content(newVetAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isCreated()); + } + + @Test + @WithMockUser(roles = "ADMIN") + void testCreateUserError() throws Exception { + User user = new User(); + user.setUsername(""); // set empty username to force 400 error + user.setPassword("password"); + user.setEnabled(true); + ObjectMapper mapper = new ObjectMapper(); + String newVetAsJSON = mapper.writeValueAsString(userMapper.toUserDto(user)); + this.mockMvc.perform(post("/api/users/") + .content(newVetAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isBadRequest()); + } +} diff --git a/backend/src/test/java/org/springframework/samples/petclinic/rest/controller/VetRestControllerTests.java b/backend/src/test/java/org/springframework/samples/petclinic/rest/controller/VetRestControllerTests.java new file mode 100644 index 0000000..05836e3 --- /dev/null +++ b/backend/src/test/java/org/springframework/samples/petclinic/rest/controller/VetRestControllerTests.java @@ -0,0 +1,222 @@ +/* + * Copyright 2016-2017 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 + * + * http://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.rest.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +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.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.samples.petclinic.mapper.VetMapper; +import org.springframework.samples.petclinic.model.Vet; +import org.springframework.samples.petclinic.rest.advice.ExceptionControllerAdvice; +import org.springframework.samples.petclinic.service.ClinicService; +import org.springframework.samples.petclinic.service.clinicService.ApplicationTestConfig; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.util.ArrayList; +import java.util.List; + +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * Test class for {@link VetRestController} + * + * @author Vitaliy Fedoriv + */ +@SpringBootTest +@ContextConfiguration(classes=ApplicationTestConfig.class) +@WebAppConfiguration +class VetRestControllerTests { + + @Autowired + private VetRestController vetRestController; + + @Autowired + private VetMapper vetMapper; + + @MockBean + private ClinicService clinicService; + + private MockMvc mockMvc; + + private List vets; + + @BeforeEach + void initVets(){ + this.mockMvc = MockMvcBuilders.standaloneSetup(vetRestController) + .setControllerAdvice(new ExceptionControllerAdvice()) + .build(); + vets = new ArrayList(); + + + Vet vet = new Vet(); + vet.setId(1); + vet.setFirstName("James"); + vet.setLastName("Carter"); + vets.add(vet); + + vet = new Vet(); + vet.setId(2); + vet.setFirstName("Helen"); + vet.setLastName("Leary"); + vets.add(vet); + + vet = new Vet(); + vet.setId(3); + vet.setFirstName("Linda"); + vet.setLastName("Douglas"); + vets.add(vet); + } + + @Test + @WithMockUser(roles="VET_ADMIN") + void testGetVetSuccess() throws Exception { + given(this.clinicService.findVetById(1)).willReturn(vets.get(0)); + this.mockMvc.perform(get("/api/vets/1") + .accept(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/json")) + .andExpect(jsonPath("$.id").value(1)) + .andExpect(jsonPath("$.firstName").value("James")); + } + + @Test + @WithMockUser(roles="VET_ADMIN") + void testGetVetNotFound() throws Exception { + given(this.clinicService.findVetById(-1)).willReturn(null); + this.mockMvc.perform(get("/api/vets/999") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + @WithMockUser(roles="VET_ADMIN") + void testGetAllVetsSuccess() throws Exception { + given(this.clinicService.findAllVets()).willReturn(vets); + this.mockMvc.perform(get("/api/vets/") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/json")) + .andExpect(jsonPath("$.[0].id").value(1)) + .andExpect(jsonPath("$.[0].firstName").value("James")) + .andExpect(jsonPath("$.[1].id").value(2)) + .andExpect(jsonPath("$.[1].firstName").value("Helen")); + } + + @Test + @WithMockUser(roles="VET_ADMIN") + void testGetAllVetsNotFound() throws Exception { + vets.clear(); + given(this.clinicService.findAllVets()).willReturn(vets); + this.mockMvc.perform(get("/api/vets/") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + @WithMockUser(roles="VET_ADMIN") + void testCreateVetSuccess() throws Exception { + Vet newVet = vets.get(0); + newVet.setId(999); + ObjectMapper mapper = new ObjectMapper(); + String newVetAsJSON = mapper.writeValueAsString(vetMapper.toVetDto(newVet)); + this.mockMvc.perform(post("/api/vets/") + .content(newVetAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isCreated()); + } + + @Test + @WithMockUser(roles="VET_ADMIN") + void testCreateVetError() throws Exception { + Vet newVet = vets.get(0); + newVet.setId(null); + newVet.setFirstName(null); + ObjectMapper mapper = new ObjectMapper(); + String newVetAsJSON = mapper.writeValueAsString(vetMapper.toVetDto(newVet)); + this.mockMvc.perform(post("/api/vets/") + .content(newVetAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isBadRequest()); + } + + @Test + @WithMockUser(roles="VET_ADMIN") + void testUpdateVetSuccess() throws Exception { + given(this.clinicService.findVetById(1)).willReturn(vets.get(0)); + Vet newVet = vets.get(0); + newVet.setFirstName("James"); + ObjectMapper mapper = new ObjectMapper(); + String newVetAsJSON = mapper.writeValueAsString(vetMapper.toVetDto(newVet)); + this.mockMvc.perform(put("/api/vets/1") + .content(newVetAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(content().contentType("application/json")) + .andExpect(status().isNoContent()); + + this.mockMvc.perform(get("/api/vets/1") + .accept(MediaType.APPLICATION_JSON).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/json")) + .andExpect(jsonPath("$.id").value(1)) + .andExpect(jsonPath("$.firstName").value("James")); + + } + + @Test + @WithMockUser(roles="VET_ADMIN") + void testUpdateVetError() throws Exception { + Vet newVet = vets.get(0); + newVet.setFirstName(null); + ObjectMapper mapper = new ObjectMapper(); + String newVetAsJSON = mapper.writeValueAsString(vetMapper.toVetDto(newVet)); + this.mockMvc.perform(put("/api/vets/1") + .content(newVetAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isBadRequest()); + } + + @Test + @WithMockUser(roles="VET_ADMIN") + void testDeleteVetSuccess() throws Exception { + Vet newVet = vets.get(0); + ObjectMapper mapper = new ObjectMapper(); + String newVetAsJSON = mapper.writeValueAsString(vetMapper.toVetDto(newVet)); + given(this.clinicService.findVetById(1)).willReturn(vets.get(0)); + this.mockMvc.perform(delete("/api/vets/1") + .content(newVetAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isNoContent()); + } + + @Test + @WithMockUser(roles="VET_ADMIN") + void testDeleteVetError() throws Exception { + Vet newVet = vets.get(0); + ObjectMapper mapper = new ObjectMapper(); + String newVetAsJSON = mapper.writeValueAsString(vetMapper.toVetDto(newVet)); + given(this.clinicService.findVetById(-1)).willReturn(null); + this.mockMvc.perform(delete("/api/vets/999") + .content(newVetAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isNotFound()); + } + +} diff --git a/backend/src/test/java/org/springframework/samples/petclinic/rest/controller/VisitRestControllerTests.java b/backend/src/test/java/org/springframework/samples/petclinic/rest/controller/VisitRestControllerTests.java new file mode 100644 index 0000000..74217cc --- /dev/null +++ b/backend/src/test/java/org/springframework/samples/petclinic/rest/controller/VisitRestControllerTests.java @@ -0,0 +1,254 @@ +/* + * Copyright 2016-2017 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 + * + * http://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.rest.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +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.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.samples.petclinic.mapper.VisitMapper; +import org.springframework.samples.petclinic.model.Owner; +import org.springframework.samples.petclinic.model.Pet; +import org.springframework.samples.petclinic.model.PetType; +import org.springframework.samples.petclinic.model.Visit; +import org.springframework.samples.petclinic.rest.advice.ExceptionControllerAdvice; +import org.springframework.samples.petclinic.service.ClinicService; +import org.springframework.samples.petclinic.service.clinicService.ApplicationTestConfig; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * Test class for {@link VisitRestController} + * + * @author Vitaliy Fedoriv + */ +@SpringBootTest +@ContextConfiguration(classes=ApplicationTestConfig.class) +@WebAppConfiguration +class VisitRestControllerTests { + + @Autowired + private VisitRestController visitRestController; + + @MockBean + private ClinicService clinicService; + + @Autowired + private VisitMapper visitMapper; + + private MockMvc mockMvc; + + private List visits; + + @BeforeEach + void initVisits(){ + this.mockMvc = MockMvcBuilders.standaloneSetup(visitRestController) + .setControllerAdvice(new ExceptionControllerAdvice()) + .build(); + + visits = new ArrayList<>(); + + Owner owner = new Owner(); + owner.setId(1); + owner.setFirstName("Eduardo"); + owner.setLastName("Rodriquez"); + owner.setAddress("2693 Commerce St."); + owner.setCity("McFarland"); + owner.setTelephone("6085558763"); + + PetType petType = new PetType(); + petType.setId(2); + petType.setName("dog"); + + Pet pet = new Pet(); + pet.setId(8); + pet.setName("Rosy"); + pet.setBirthDate(LocalDate.now()); + pet.setOwner(owner); + pet.setType(petType); + + + Visit visit = new Visit(); + visit.setId(2); + visit.setPet(pet); + visit.setDate(LocalDate.now()); + visit.setDescription("rabies shot"); + visits.add(visit); + + visit = new Visit(); + visit.setId(3); + visit.setPet(pet); + visit.setDate(LocalDate.now()); + visit.setDescription("neutered"); + visits.add(visit); + + + } + + @Test + @WithMockUser(roles="OWNER_ADMIN") + void testGetVisitSuccess() throws Exception { + given(this.clinicService.findVisitById(2)).willReturn(visits.get(0)); + this.mockMvc.perform(get("/api/visits/2") + .accept(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/json")) + .andExpect(jsonPath("$.id").value(2)) + .andExpect(jsonPath("$.description").value("rabies shot")); + } + + @Test + @WithMockUser(roles="OWNER_ADMIN") + void testGetVisitNotFound() throws Exception { + given(this.clinicService.findVisitById(999)).willReturn(null); + this.mockMvc.perform(get("/api/visits/999") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + @WithMockUser(roles="OWNER_ADMIN") + void testGetAllVisitsSuccess() throws Exception { + given(this.clinicService.findAllVisits()).willReturn(visits); + this.mockMvc.perform(get("/api/visits/") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/json")) + .andExpect(jsonPath("$.[0].id").value(2)) + .andExpect(jsonPath("$.[0].description").value("rabies shot")) + .andExpect(jsonPath("$.[1].id").value(3)) + .andExpect(jsonPath("$.[1].description").value("neutered")); + } + + @Test + @WithMockUser(roles="OWNER_ADMIN") + void testGetAllVisitsNotFound() throws Exception { + visits.clear(); + given(this.clinicService.findAllVisits()).willReturn(visits); + this.mockMvc.perform(get("/api/visits/") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + @WithMockUser(roles="OWNER_ADMIN") + void testCreateVisitSuccess() throws Exception { + Visit newVisit = visits.get(0); + newVisit.setId(999); + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + String newVisitAsJSON = mapper.writeValueAsString(visitMapper.toVisitDto(newVisit)); + System.out.println("newVisitAsJSON " + newVisitAsJSON); + this.mockMvc.perform(post("/api/visits/") + .content(newVisitAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isCreated()); + } + + @Test + @WithMockUser(roles="OWNER_ADMIN") + void testCreateVisitError() throws Exception { + Visit newVisit = visits.get(0); + newVisit.setId(null); + newVisit.setDescription(null); + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + String newVisitAsJSON = mapper.writeValueAsString(visitMapper.toVisitDto(newVisit)); + this.mockMvc.perform(post("/api/visits/") + .content(newVisitAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isBadRequest()); + } + + @Test + @WithMockUser(roles="OWNER_ADMIN") + void testUpdateVisitSuccess() throws Exception { + given(this.clinicService.findVisitById(2)).willReturn(visits.get(0)); + Visit newVisit = visits.get(0); + newVisit.setDescription("rabies shot test"); + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + String newVisitAsJSON = mapper.writeValueAsString(visitMapper.toVisitDto(newVisit)); + this.mockMvc.perform(put("/api/visits/2") + .content(newVisitAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(content().contentType("application/json")) + .andExpect(status().isNoContent()); + + this.mockMvc.perform(get("/api/visits/2") + .accept(MediaType.APPLICATION_JSON).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/json")) + .andExpect(jsonPath("$.id").value(2)) + .andExpect(jsonPath("$.description").value("rabies shot test")); + } + + @Test + @WithMockUser(roles="OWNER_ADMIN") + void testUpdateVisitError() throws Exception { + Visit newVisit = visits.get(0); + newVisit.setDescription(null); + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + String newVisitAsJSON = mapper.writeValueAsString(visitMapper.toVisitDto(newVisit)); + this.mockMvc.perform(put("/api/visits/2") + .content(newVisitAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isBadRequest()); + } + + @Test + @WithMockUser(roles="OWNER_ADMIN") + void testDeleteVisitSuccess() throws Exception { + Visit newVisit = visits.get(0); + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + String newVisitAsJSON = mapper.writeValueAsString(visitMapper.toVisitDto(newVisit)); + given(this.clinicService.findVisitById(2)).willReturn(visits.get(0)); + this.mockMvc.perform(delete("/api/visits/2") + .content(newVisitAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isNoContent()); + } + + @Test + @WithMockUser(roles="OWNER_ADMIN") + void testDeleteVisitError() throws Exception { + Visit newVisit = visits.get(0); + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + String newVisitAsJSON = mapper.writeValueAsString(visitMapper.toVisitDto(newVisit)); + given(this.clinicService.findVisitById(999)).willReturn(null); + this.mockMvc.perform(delete("/api/visits/999") + .content(newVisitAsJSON).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isNotFound()); + } + +} diff --git a/backend/src/test/java/org/springframework/samples/petclinic/service/clinicService/AbstractClinicServiceTests.java b/backend/src/test/java/org/springframework/samples/petclinic/service/clinicService/AbstractClinicServiceTests.java new file mode 100644 index 0000000..aff2ec1 --- /dev/null +++ b/backend/src/test/java/org/springframework/samples/petclinic/service/clinicService/AbstractClinicServiceTests.java @@ -0,0 +1,502 @@ +/* + * Copyright 2002-2017 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 + * + * http://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.clinicService; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.samples.petclinic.model.*; +import org.springframework.samples.petclinic.service.ClinicService; +import org.springframework.samples.petclinic.util.EntityUtils; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + *

Base class for {@link ClinicService} integration tests.

Subclasses should specify Spring context + * configuration using {@link ContextConfiguration @ContextConfiguration} annotation

+ * AbstractclinicServiceTests and its subclasses benefit from the following services provided by the Spring + * TestContext Framework:

  • Spring IoC container caching which spares us unnecessary set up + * time between test execution.
  • Dependency Injection of test fixture instances, meaning that + * we don't need to perform application context lookups. See the use of {@link Autowired @Autowired} on the {@link + * AbstractClinicServiceTests#clinicService clinicService} instance variable, which uses autowiring by + * type.
  • Transaction management, 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.
  • An {@link org.springframework.context.ApplicationContext + * ApplicationContext} is also inherited and can be used for explicit bean lookup if necessary.
+ * + * @author Ken Krebs + * @author Rod Johnson + * @author Juergen Hoeller + * @author Sam Brannen + * @author Michael Isvy + * @author Vitaliy Fedoriv + */ +abstract class AbstractClinicServiceTests { + + @Autowired + protected ClinicService clinicService; + + @Test + void shouldFindOwnersByLastName() { + Collection owners = this.clinicService.findOwnerByLastName("Davis"); + assertThat(owners.size()).isEqualTo(2); + + owners = this.clinicService.findOwnerByLastName("Daviss"); + assertThat(owners.isEmpty()).isTrue(); + } + + @Test + void shouldFindSingleOwnerWithPet() { + Owner owner = this.clinicService.findOwnerById(1); + assertThat(owner.getLastName()).startsWith("Franklin"); + assertThat(owner.getPets().size()).isEqualTo(1); + assertThat(owner.getPets().get(0).getType()).isNotNull(); + assertThat(owner.getPets().get(0).getType().getName()).isEqualTo("cat"); + } + + @Test + @Transactional + void shouldInsertOwner() { + Collection owners = this.clinicService.findOwnerByLastName("Schultz"); + int found = owners.size(); + + Owner owner = new Owner(); + owner.setFirstName("Sam"); + owner.setLastName("Schultz"); + owner.setAddress("4, Evans Street"); + owner.setCity("Wollongong"); + owner.setTelephone("4444444444"); + this.clinicService.saveOwner(owner); + assertThat(owner.getId().longValue()).isNotEqualTo(0); + assertThat(owner.getPet("null value")).isNull(); + owners = this.clinicService.findOwnerByLastName("Schultz"); + assertThat(owners.size()).isEqualTo(found + 1); + } + + @Test + @Transactional + void shouldUpdateOwner() { + Owner owner = this.clinicService.findOwnerById(1); + String oldLastName = owner.getLastName(); + String newLastName = oldLastName + "X"; + + owner.setLastName(newLastName); + this.clinicService.saveOwner(owner); + + // retrieving new name from database + owner = this.clinicService.findOwnerById(1); + assertThat(owner.getLastName()).isEqualTo(newLastName); + } + + @Test + void shouldFindPetWithCorrectId() { + Pet pet7 = this.clinicService.findPetById(7); + assertThat(pet7.getName()).startsWith("Samantha"); + assertThat(pet7.getOwner().getFirstName()).isEqualTo("Jean"); + + } + +// @Test +// void shouldFindAllPetTypes() { +// Collection petTypes = this.clinicService.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.clinicService.findOwnerById(6); + int found = owner6.getPets().size(); + + Pet pet = new Pet(); + pet.setName("bowser"); + Collection types = this.clinicService.findPetTypes(); + pet.setType(EntityUtils.getById(types, PetType.class, 2)); + pet.setBirthDate(LocalDate.now()); + owner6.addPet(pet); + assertThat(owner6.getPets().size()).isEqualTo(found + 1); + + this.clinicService.savePet(pet); + this.clinicService.saveOwner(owner6); + + owner6 = this.clinicService.findOwnerById(6); + assertThat(owner6.getPets().size()).isEqualTo(found + 1); + // checks that id has been generated + assertThat(pet.getId()).isNotNull(); + } + + @Test + @Transactional + void shouldUpdatePetName() throws Exception { + Pet pet7 = this.clinicService.findPetById(7); + String oldName = pet7.getName(); + + String newName = oldName + "X"; + pet7.setName(newName); + this.clinicService.savePet(pet7); + + pet7 = this.clinicService.findPetById(7); + assertThat(pet7.getName()).isEqualTo(newName); + } + + @Test + void shouldFindVets() { + Collection vets = this.clinicService.findVets(); + + 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() { + Pet pet7 = this.clinicService.findPetById(7); + int found = pet7.getVisits().size(); + Visit visit = new Visit(); + pet7.addVisit(visit); + visit.setDescription("test"); + this.clinicService.saveVisit(visit); + this.clinicService.savePet(pet7); + + pet7 = this.clinicService.findPetById(7); + assertThat(pet7.getVisits().size()).isEqualTo(found + 1); + assertThat(visit.getId()).isNotNull(); + } + + @Test + void shouldFindVisitsByPetId() throws Exception { + Collection visits = this.clinicService.findVisitsByPetId(7); + assertThat(visits.size()).isEqualTo(2); + Visit[] visitArr = visits.toArray(new Visit[visits.size()]); + assertThat(visitArr[0].getPet()).isNotNull(); + assertThat(visitArr[0].getDate()).isNotNull(); + assertThat(visitArr[0].getPet().getId()).isEqualTo(7); + } + + @Test + void shouldFindAllPets(){ + Collection pets = this.clinicService.findAllPets(); + Pet pet1 = EntityUtils.getById(pets, Pet.class, 1); + assertThat(pet1.getName()).isEqualTo("Leo"); + Pet pet3 = EntityUtils.getById(pets, Pet.class, 3); + assertThat(pet3.getName()).isEqualTo("Rosy"); + } + + @Test + @Transactional + void shouldDeletePet(){ + Pet pet = this.clinicService.findPetById(1); + this.clinicService.deletePet(pet); + try { + pet = this.clinicService.findPetById(1); + } catch (Exception e) { + pet = null; + } + assertThat(pet).isNull(); + } + + @Test + void shouldFindVisitDyId(){ + Visit visit = this.clinicService.findVisitById(1); + assertThat(visit.getId()).isEqualTo(1); + assertThat(visit.getPet().getName()).isEqualTo("Samantha"); + } + + @Test + void shouldFindAllVisits(){ + Collection visits = this.clinicService.findAllVisits(); + Visit visit1 = EntityUtils.getById(visits, Visit.class, 1); + assertThat(visit1.getPet().getName()).isEqualTo("Samantha"); + Visit visit3 = EntityUtils.getById(visits, Visit.class, 3); + assertThat(visit3.getPet().getName()).isEqualTo("Max"); + } + + @Test + @Transactional + void shouldInsertVisit() { + Collection visits = this.clinicService.findAllVisits(); + int found = visits.size(); + + Pet pet = this.clinicService.findPetById(1); + + Visit visit = new Visit(); + visit.setPet(pet); + visit.setDate(LocalDate.now()); + visit.setDescription("new visit"); + + + this.clinicService.saveVisit(visit); + assertThat(visit.getId().longValue()).isNotEqualTo(0); + + visits = this.clinicService.findAllVisits(); + assertThat(visits.size()).isEqualTo(found + 1); + } + + @Test + @Transactional + void shouldUpdateVisit(){ + Visit visit = this.clinicService.findVisitById(1); + String oldDesc = visit.getDescription(); + String newDesc = oldDesc + "X"; + visit.setDescription(newDesc); + this.clinicService.saveVisit(visit); + visit = this.clinicService.findVisitById(1); + assertThat(visit.getDescription()).isEqualTo(newDesc); + } + + @Test + @Transactional + void shouldDeleteVisit(){ + Visit visit = this.clinicService.findVisitById(1); + this.clinicService.deleteVisit(visit); + try { + visit = this.clinicService.findVisitById(1); + } catch (Exception e) { + visit = null; + } + assertThat(visit).isNull(); + } + + @Test + void shouldFindVetDyId(){ + Vet vet = this.clinicService.findVetById(1); + assertThat(vet.getFirstName()).isEqualTo("James"); + assertThat(vet.getLastName()).isEqualTo("Carter"); + } + + @Test + @Transactional + void shouldInsertVet() { + Collection vets = this.clinicService.findAllVets(); + int found = vets.size(); + + Vet vet = new Vet(); + vet.setFirstName("John"); + vet.setLastName("Dow"); + + this.clinicService.saveVet(vet); + assertThat(vet.getId().longValue()).isNotEqualTo(0); + + vets = this.clinicService.findAllVets(); + assertThat(vets.size()).isEqualTo(found + 1); + } + + @Test + @Transactional + void shouldUpdateVet(){ + Vet vet = this.clinicService.findVetById(1); + String oldLastName = vet.getLastName(); + String newLastName = oldLastName + "X"; + vet.setLastName(newLastName); + this.clinicService.saveVet(vet); + vet = this.clinicService.findVetById(1); + assertThat(vet.getLastName()).isEqualTo(newLastName); + } + + @Test + @Transactional + void shouldDeleteVet(){ + Vet vet = this.clinicService.findVetById(1); + this.clinicService.deleteVet(vet); + try { + vet = this.clinicService.findVetById(1); + } catch (Exception e) { + vet = null; + } + assertThat(vet).isNull(); + } + + @Test + void shouldFindAllOwners(){ + Collection owners = this.clinicService.findAllOwners(); + Owner owner1 = EntityUtils.getById(owners, Owner.class, 1); + assertThat(owner1.getFirstName()).isEqualTo("George"); + Owner owner3 = EntityUtils.getById(owners, Owner.class, 3); + assertThat(owner3.getFirstName()).isEqualTo("Eduardo"); + } + + @Test + @Transactional + void shouldDeleteOwner(){ + Owner owner = this.clinicService.findOwnerById(1); + this.clinicService.deleteOwner(owner); + try { + owner = this.clinicService.findOwnerById(1); + } catch (Exception e) { + owner = null; + } + assertThat(owner).isNull(); + } + + @Test + void shouldFindPetTypeById(){ + PetType petType = this.clinicService.findPetTypeById(1); + assertThat(petType.getName()).isEqualTo("cat"); + } + + @Test + void shouldFindAllPetTypes(){ + Collection petTypes = this.clinicService.findAllPetTypes(); + PetType petType1 = EntityUtils.getById(petTypes, PetType.class, 1); + assertThat(petType1.getName()).isEqualTo("cat"); + PetType petType3 = EntityUtils.getById(petTypes, PetType.class, 3); + assertThat(petType3.getName()).isEqualTo("lizard"); + } + + @Test + @Transactional + void shouldInsertPetType() { + Collection petTypes = this.clinicService.findAllPetTypes(); + int found = petTypes.size(); + + PetType petType = new PetType(); + petType.setName("tiger"); + + this.clinicService.savePetType(petType); + assertThat(petType.getId().longValue()).isNotEqualTo(0); + + petTypes = this.clinicService.findAllPetTypes(); + assertThat(petTypes.size()).isEqualTo(found + 1); + } + + @Test + @Transactional + void shouldUpdatePetType(){ + PetType petType = this.clinicService.findPetTypeById(1); + String oldLastName = petType.getName(); + String newLastName = oldLastName + "X"; + petType.setName(newLastName); + this.clinicService.savePetType(petType); + petType = this.clinicService.findPetTypeById(1); + assertThat(petType.getName()).isEqualTo(newLastName); + } + + @Test + @Transactional + void shouldDeletePetType(){ + PetType petType = this.clinicService.findPetTypeById(1); + this.clinicService.deletePetType(petType); + try { + petType = this.clinicService.findPetTypeById(1); + } catch (Exception e) { + petType = null; + } + assertThat(petType).isNull(); + } + + @Test + void shouldFindSpecialtyById(){ + Specialty specialty = this.clinicService.findSpecialtyById(1); + assertThat(specialty.getName()).isEqualTo("radiology"); + } + + @Test + void shouldFindAllSpecialtys(){ + Collection specialties = this.clinicService.findAllSpecialties(); + Specialty specialty1 = EntityUtils.getById(specialties, Specialty.class, 1); + assertThat(specialty1.getName()).isEqualTo("radiology"); + Specialty specialty3 = EntityUtils.getById(specialties, Specialty.class, 3); + assertThat(specialty3.getName()).isEqualTo("dentistry"); + } + + @Test + @Transactional + void shouldInsertSpecialty() { + Collection specialties = this.clinicService.findAllSpecialties(); + int found = specialties.size(); + + Specialty specialty = new Specialty(); + specialty.setName("dermatologist"); + + this.clinicService.saveSpecialty(specialty); + assertThat(specialty.getId().longValue()).isNotEqualTo(0); + + specialties = this.clinicService.findAllSpecialties(); + assertThat(specialties.size()).isEqualTo(found + 1); + } + + @Test + @Transactional + void shouldUpdateSpecialty(){ + Specialty specialty = this.clinicService.findSpecialtyById(1); + String oldLastName = specialty.getName(); + String newLastName = oldLastName + "X"; + specialty.setName(newLastName); + this.clinicService.saveSpecialty(specialty); + specialty = this.clinicService.findSpecialtyById(1); + assertThat(specialty.getName()).isEqualTo(newLastName); + } + + @Test + @Transactional + void shouldDeleteSpecialty(){ + Specialty specialty = new Specialty(); + specialty.setName("test"); + this.clinicService.saveSpecialty(specialty); + Integer specialtyId = specialty.getId(); + assertThat(specialtyId).isNotNull(); + specialty = this.clinicService.findSpecialtyById(specialtyId); + assertThat(specialty).isNotNull(); + this.clinicService.deleteSpecialty(specialty); + try { + specialty = this.clinicService.findSpecialtyById(specialtyId); + } catch (Exception e) { + specialty = null; + } + assertThat(specialty).isNull(); + } + + @Test + @Transactional + void shouldFindSpecialtiesByNameIn() { + Specialty specialty1 = new Specialty(); + specialty1.setName("radiology"); + specialty1.setId(1); + Specialty specialty2 = new Specialty(); + specialty2.setName("surgery"); + specialty2.setId(2); + Specialty specialty3 = new Specialty(); + specialty3.setName("dentistry"); + specialty3.setId(3); + List expectedSpecialties = List.of(specialty1, specialty2, specialty3); + Set specialtyNames = expectedSpecialties.stream() + .map(Specialty::getName) + .collect(Collectors.toSet()); + Collection actualSpecialties = this.clinicService.findSpecialtiesByNameIn(specialtyNames); + assertThat(actualSpecialties).isNotNull(); + assertThat(actualSpecialties.size()).isEqualTo(expectedSpecialties.size()); + for (Specialty expected : expectedSpecialties) { + assertThat(actualSpecialties.stream() + .anyMatch( + actual -> actual.getName().equals(expected.getName()) + && actual.getId().equals(expected.getId()))).isTrue(); + } + } +} diff --git a/backend/src/test/java/org/springframework/samples/petclinic/service/clinicService/ApplicationTestConfig.java b/backend/src/test/java/org/springframework/samples/petclinic/service/clinicService/ApplicationTestConfig.java new file mode 100644 index 0000000..79398dc --- /dev/null +++ b/backend/src/test/java/org/springframework/samples/petclinic/service/clinicService/ApplicationTestConfig.java @@ -0,0 +1,13 @@ +package org.springframework.samples.petclinic.service.clinicService; + +import org.mockito.MockitoAnnotations; +import org.springframework.boot.test.context.TestConfiguration; + +@TestConfiguration +public class ApplicationTestConfig { + + public ApplicationTestConfig(){ + MockitoAnnotations.openMocks(this); + } + +} diff --git a/backend/src/test/java/org/springframework/samples/petclinic/service/clinicService/ClinicServiceJdbcTests.java b/backend/src/test/java/org/springframework/samples/petclinic/service/clinicService/ClinicServiceJdbcTests.java new file mode 100644 index 0000000..43724aa --- /dev/null +++ b/backend/src/test/java/org/springframework/samples/petclinic/service/clinicService/ClinicServiceJdbcTests.java @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2013 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 + * + * http://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.clinicService; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +/** + *

Integration test using the jdbc profile. + * + * @author Thomas Risberg + * @author Michael Isvy + * @see AbstractClinicServiceTests AbstractClinicServiceTests for more details.

+ */ +@SpringBootTest +@ActiveProfiles({"jdbc", "hsqldb"}) +class ClinicServiceJdbcTests extends AbstractClinicServiceTests { + +} diff --git a/backend/src/test/java/org/springframework/samples/petclinic/service/clinicService/ClinicServiceJpaTests.java b/backend/src/test/java/org/springframework/samples/petclinic/service/clinicService/ClinicServiceJpaTests.java new file mode 100644 index 0000000..1a73050 --- /dev/null +++ b/backend/src/test/java/org/springframework/samples/petclinic/service/clinicService/ClinicServiceJpaTests.java @@ -0,0 +1,19 @@ +package org.springframework.samples.petclinic.service.clinicService; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +/** + *

Integration test using the jpa profile. + * + * @author Rod Johnson + * @author Sam Brannen + * @author Michael Isvy + * @see AbstractClinicServiceTests AbstractClinicServiceTests for more details.

+ */ + +@SpringBootTest +@ActiveProfiles({"jpa", "hsqldb"}) +class ClinicServiceJpaTests extends AbstractClinicServiceTests { + +} diff --git a/backend/src/test/java/org/springframework/samples/petclinic/service/clinicService/ClinicServiceSpringDataJpaTests.java b/backend/src/test/java/org/springframework/samples/petclinic/service/clinicService/ClinicServiceSpringDataJpaTests.java new file mode 100644 index 0000000..b5c57c0 --- /dev/null +++ b/backend/src/test/java/org/springframework/samples/petclinic/service/clinicService/ClinicServiceSpringDataJpaTests.java @@ -0,0 +1,17 @@ +package org.springframework.samples.petclinic.service.clinicService; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +/** + *

Integration test using the 'Spring Data' profile. + * + * @author Michael Isvy + * @see AbstractClinicServiceTests AbstractClinicServiceTests for more details.

+ */ + +@SpringBootTest +@ActiveProfiles({"spring-data-jpa", "hsqldb"}) +class ClinicServiceSpringDataJpaTests extends AbstractClinicServiceTests { + +} diff --git a/backend/src/test/java/org/springframework/samples/petclinic/service/userService/AbstractUserServiceTests.java b/backend/src/test/java/org/springframework/samples/petclinic/service/userService/AbstractUserServiceTests.java new file mode 100644 index 0000000..54ecdde --- /dev/null +++ b/backend/src/test/java/org/springframework/samples/petclinic/service/userService/AbstractUserServiceTests.java @@ -0,0 +1,35 @@ +package org.springframework.samples.petclinic.service.userService; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockitoAnnotations; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.samples.petclinic.model.User; +import org.springframework.samples.petclinic.service.UserService; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +public abstract class AbstractUserServiceTests { + + @Autowired + private UserService userService; + + @BeforeEach + public void init() { + MockitoAnnotations.openMocks(this); + } + + @Test + public void shouldAddUser() throws Exception { + User user = new User(); + user.setUsername("username"); + user.setPassword("password"); + user.setEnabled(true); + user.addRole("OWNER_ADMIN"); + + userService.saveUser(user); + assertThat(user.getRoles().parallelStream().allMatch(role -> role.getName().startsWith("ROLE_")), is(true)); + assertThat(user.getRoles().parallelStream().allMatch(role -> role.getUser() != null), is(true)); + } +} diff --git a/backend/src/test/java/org/springframework/samples/petclinic/service/userService/UserServiceJdbcTests.java b/backend/src/test/java/org/springframework/samples/petclinic/service/userService/UserServiceJdbcTests.java new file mode 100644 index 0000000..8cfbdaf --- /dev/null +++ b/backend/src/test/java/org/springframework/samples/petclinic/service/userService/UserServiceJdbcTests.java @@ -0,0 +1,10 @@ +package org.springframework.samples.petclinic.service.userService; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +@SpringBootTest +@ActiveProfiles({"jdbc", "hsqldb"}) +class UserServiceJdbcTests extends AbstractUserServiceTests { + +} diff --git a/backend/src/test/java/org/springframework/samples/petclinic/service/userService/UserServiceJpaTests.java b/backend/src/test/java/org/springframework/samples/petclinic/service/userService/UserServiceJpaTests.java new file mode 100644 index 0000000..0db9a12 --- /dev/null +++ b/backend/src/test/java/org/springframework/samples/petclinic/service/userService/UserServiceJpaTests.java @@ -0,0 +1,10 @@ +package org.springframework.samples.petclinic.service.userService; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +@SpringBootTest +@ActiveProfiles({"jpa", "hsqldb"}) +class UserServiceJpaTests extends AbstractUserServiceTests { + +} diff --git a/backend/src/test/java/org/springframework/samples/petclinic/service/userService/UserServiceSpringDataJpaTests.java b/backend/src/test/java/org/springframework/samples/petclinic/service/userService/UserServiceSpringDataJpaTests.java new file mode 100644 index 0000000..d7240c8 --- /dev/null +++ b/backend/src/test/java/org/springframework/samples/petclinic/service/userService/UserServiceSpringDataJpaTests.java @@ -0,0 +1,10 @@ +package org.springframework.samples.petclinic.service.userService; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +@SpringBootTest +@ActiveProfiles({"spring-data-jpa", "hsqldb"}) +class UserServiceSpringDataJpaTests extends AbstractUserServiceTests { + +} diff --git a/backend/src/test/resources/application.properties b/backend/src/test/resources/application.properties new file mode 100644 index 0000000..f57025f --- /dev/null +++ b/backend/src/test/resources/application.properties @@ -0,0 +1,43 @@ +# active profiles config +# +# application use two active profiles +# +# one for select repository layer +# ------------------------------------------------ +# When using HSQL, use: hsqldb +# When using MySQL, use: mysql +# When using PostgeSQL, use: postgres +# ------------------------------------------------ +# +# one - for select database +# ------------------------------------------------ +# When using Spring jpa, use: jpa +# When using Spring JDBC, use: jdbc +# When using Spring Data JPA, use: spring-data-jpa +# ------------------------------------------------ + +spring.profiles.active=hsqldb,spring-data-jpa + +# ------------------------------------------------ + +server.port=9966 +server.servlet.context-path=/petclinic/ +spring.jpa.open-in-view=false + +# database init +spring.sql.init.schema-locations=classpath*:db/hsqldb/schema.sql +spring.sql.init.data-locations=classpath*:db/hsqldb/data.sql + +spring.messages.basename=messages/messages +logging.level.org.springframework=INFO +#logging.level.org.springframework=DEBUG + +#logging.level.org.hibernate.SQL=DEBUG +#logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE + +# enable the desired authentication type +# by default the authentication is disabled +security.ignored=/** +basic.authentication.enabled=true +petclinic.security.enable=true + diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..a541719 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,9 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * This is a general purpose Gradle build. + * To learn more about Gradle by exploring our Samples at https://docs.gradle.org/8.7/samples + */ +plugins { + idea +} \ No newline at end of file diff --git a/flake/flake.lock b/flake/flake.lock new file mode 100644 index 0000000..ff8b138 --- /dev/null +++ b/flake/flake.lock @@ -0,0 +1,57 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 0, + "narHash": "sha256-q2yjIWFFcTzp5REWQUOU9L6kHdCDmFDpqeix86SOvDc=", + "path": "/nix/store/9g88fck8ggiah5znz5xn2kxzfr6l7cdq-source", + "type": "path" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake/flake.nix b/flake/flake.nix new file mode 100644 index 0000000..2744220 --- /dev/null +++ b/flake/flake.nix @@ -0,0 +1,12 @@ +{ + inputs.flake-utils.url = "github:/numtide/flake-utils"; + outputs = { self, nixpkgs, flake-utils }: + flake-utils.lib.eachDefaultSystem + (system: + let pkgs = nixpkgs.legacyPackages.${system}; in + with pkgs; { + devShells.default = mkShell { + packages = [ jdk21 nodejs_22]; + }; + }); +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..2ea8b6b --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +mapstructVersion=1.6.2 \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..4ac3234 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,2 @@ +# This file was generated by the Gradle 'init' task. +# https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..a4b76b9 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..e2847c8 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..f5feea6 --- /dev/null +++ b/gradlew @@ -0,0 +1,252 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original 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. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..9d21a21 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..b672baa --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,8 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * For more detailed information on multi-project builds, please refer to https://docs.gradle.org/8.7/userguide/multi_project_builds.html in the Gradle documentation. + */ +include("backend") +rootProject.name = "petclinic"