├── .github ├── dependabot.yml └── workflows │ ├── build-test.yml │ ├── mta-platforms-verify.yml │ └── scripts │ ├── assertMta.bat │ ├── assertMta.sh │ ├── install-docker-macos.sh │ └── install-podman-macos.sh ├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── LICENSE ├── README.md ├── devfile.yaml ├── images ├── mta-app-list.png ├── mta-issues-after-pom-fixes.png ├── mta-issues-mandatory.png ├── mta-issues.png ├── mta-project-dashboard.png ├── mta-remove-springbootapplication.png ├── quarkus-dev-ui.png ├── spring-datasource-properties-desc.png ├── spring-pom-to-quarkus-bom-desc.png ├── spring-pom-to-quarkus-bom-issues.png ├── spring-todo-1.png └── spring-todo-2.png ├── mvnw ├── mvnw.cmd ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── acme │ │ └── todo │ │ ├── TodoApplication.java │ │ ├── domain │ │ └── TodoEntity.java │ │ ├── repository │ │ └── TodoRepository.java │ │ └── rest │ │ └── TodoController.java └── resources │ ├── META-INF │ └── resources │ │ ├── index.html │ │ ├── todo-component.html │ │ ├── todo-component.js │ │ ├── todo.css │ │ └── todo.js │ ├── application.properties │ └── import.sql └── test ├── java └── com │ └── acme │ └── todo │ └── rest │ └── TodoControllerTests.java └── resources └── application.properties /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: maven 4 | directory: / 5 | open-pull-requests-limit: 10 6 | schedule: 7 | interval: daily 8 | 9 | - package-ecosystem: maven 10 | directory: / 11 | open-pull-requests-limit: 10 12 | target-branch: solution 13 | schedule: 14 | interval: daily 15 | 16 | - package-ecosystem: github-actions 17 | directory: / 18 | open-pull-requests-limit: 10 19 | schedule: 20 | interval: daily 21 | 22 | - package-ecosystem: github-actions 23 | directory: / 24 | open-pull-requests-limit: 10 25 | target-branch: solution 26 | schedule: 27 | interval: daily -------------------------------------------------------------------------------- /.github/workflows/build-test.yml: -------------------------------------------------------------------------------- 1 | name: Build and test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths-ignore: 8 | - '**/*.md' 9 | - 'images/**' 10 | pull_request: 11 | branches: 12 | - main 13 | paths-ignore: 14 | - '**/*.md' 15 | - 'images/**' 16 | workflow_dispatch: 17 | 18 | concurrency: 19 | group: "workflow = ${{ github.workflow }}, ref = ${{ github.event.ref }}, pr = ${{ github.event.pull_request.id }}" 20 | cancel-in-progress: ${{ github.event_name == 'pull_request' }} 21 | 22 | jobs: 23 | build-test: 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v4 27 | 28 | - name: Setup Java 29 | uses: actions/setup-java@v4 30 | with: 31 | java-version: 17 32 | distribution: temurin 33 | cache: maven 34 | 35 | - name: build-test 36 | run: ./mvnw -B clean verify -------------------------------------------------------------------------------- /.github/workflows/mta-platforms-verify.yml: -------------------------------------------------------------------------------- 1 | name: Test MTA works on various platforms 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | workflow_dispatch: 11 | 12 | jobs: 13 | verify-mta-linux: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout project 17 | uses: actions/checkout@v4 18 | 19 | - name: Run MTA 20 | run: docker run -v $(pwd):/opt/project:z -u $(id -u):$(id -g) quay.io/rhappsvcs/spring-to-quarkus-mta-cli:latest 21 | 22 | - name: Assert MTA output 23 | run: .github/workflows/scripts/assertMta.sh 24 | 25 | # verify-mta-macos-10-podman: 26 | # runs-on: macos-10.15 27 | # steps: 28 | # - name: Checkout project 29 | # uses: actions/checkout@v2 30 | # - name: Install podman 31 | # run: .github/workflows/scripts/install-podman-macos.sh 32 | # - name: Run MTA 33 | # run: podman run -v $GITHUB_WORKSPACE:/opt/project:z,U quay.io/edeandrea/mta-cli:latest 34 | # - name: Assert MTA output 35 | # run: .github/workflows/scripts/assertMta.sh 36 | 37 | # verify-mta-macos-11-podman: 38 | # runs-on: macos-11 39 | # steps: 40 | # - name: Checkout project 41 | # uses: actions/checkout@v2 42 | # - name: Install podman 43 | # run: .github/workflows/scripts/install-podman-macos.sh 44 | # - name: Run MTA 45 | # run: podman run -v $GITHUB_WORKSPACE:/opt/project:z,U quay.io/edeandrea/mta-cli:latest 46 | # - name: Assert MTA output 47 | # run: .github/workflows/scripts/assertMta.sh 48 | 49 | # verify-mta-macos-10: 50 | # runs-on: macos-10.15 51 | # steps: 52 | # - name: Checkout project 53 | # uses: actions/checkout@v2 54 | # - name: Install docker 55 | # run: .github/workflows/scripts/install-docker-macos.sh 56 | # - name: Run MTA 57 | # run: | 58 | # eval $(docker-machine env mta) 59 | # docker run -v $(pwd):/opt/project -u $(id -u):$(id -g) quay.io/edeandrea/mta-cli:latest 60 | # - name: Assert MTA output 61 | # run: .github/workflows/scripts/assertMta.sh 62 | 63 | # verify-mta-macos-11: 64 | # runs-on: macos-11 65 | # steps: 66 | # - name: Checkout project 67 | # uses: actions/checkout@v2 68 | # - name: Install docker 69 | # run: .github/workflows/scripts/install-docker-macos.sh 70 | # - name: Run MTA 71 | # run: | 72 | # eval $(docker-machine env mta) 73 | # docker run -v $(pwd):/opt/project -u $(id -u):$(id -g) quay.io/edeandrea/mta-cli:latest 74 | # - name: Assert MTA output 75 | # run: .github/workflows/scripts/assertMta.sh 76 | 77 | # verify-mta-windows: 78 | # runs-on: windows-latest 79 | # steps: 80 | # - name: Checkout project 81 | # uses: actions/checkout@v2 82 | # - name: Switch to linux container 83 | # run: "& $Env:ProgramFiles\\Docker\\Docker\\DockerCli.exe -SwitchDaemon" 84 | # - name: Run MTA 85 | # run: docker run -v %cd%:/opt/project quay.io/edeandrea/mta-cli:latest 86 | # - name: Assert MTA output 87 | # run: .github/workflows/scripts/assertMta.bat 88 | -------------------------------------------------------------------------------- /.github/workflows/scripts/assertMta.bat: -------------------------------------------------------------------------------- 1 | set MTA_REPORT_DIR=%GITHUB_WORKSPACE% 2 | set MTA_REPORT_FILE=%MTA_REPORT_DIR% 3 | 4 | echo "Checking whether %MTA_REPORT_DIR% exists" 5 | IF NOT EXIST %MTA_REPORT_DIR% ( echo "Directory %MTA_REPORT_DIR% DOES NOT exist" && exit 7 ) 6 | 7 | echo "Checking whether %MTA_REPORT_FILE% exists" 8 | IF NOT EXIST %MTA_REPORT_DIR% ( echo "File %MTA_REPORT_FILE% DOES NOT exist" && exit 7 ) 9 | 10 | exit 0 -------------------------------------------------------------------------------- /.github/workflows/scripts/assertMta.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | MTA_REPORT_DIR=$GITHUB_WORKSPACE/windup-report 4 | MTA_REPORT_FILE=$MTA_REPORT_DIR/index.html 5 | 6 | echo "Checking whether $MTA_REPORT_DIR exists" 7 | [ ! -d $MTA_REPORT_DIR ] && echo "Directory $MTA_REPORT_DIR DOES NOT exist" && exit -1 8 | 9 | echo "Checking whether $MTA_REPORT_FILE exists" 10 | [ ! -s $MTA_REPORT_FILE ] && echo "File $MTA_REPORT_FILE DOES NOT exist" && exit -1 11 | 12 | exit 0 13 | -------------------------------------------------------------------------------- /.github/workflows/scripts/install-docker-macos.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Taken from https://github.com/actions/virtual-environments/issues/1143#issuecomment-652264388 4 | 5 | # mkdir -p ~/.docker/machine/cache 6 | # curl -Lo ~/.docker/machine/cache/boot2docker.iso https://github.com/boot2docker/boot2docker/releases/download/v19.03.12/boot2docker.iso 7 | # brew uninstall virtualbox 8 | # brew update 9 | # brew install --cask virtualbox 10 | # brew install docker docker-machine 11 | # docker-machine create --driver virtualbox default 12 | # docker-machine env default 13 | 14 | # brew cask install docker 15 | # # allow the app to run without confirmation 16 | # xattr -d -r com.apple.quarantine /Applications/Docker.app 17 | 18 | # # preemptively do docker.app's setup to avoid any gui prompts 19 | # sudo /bin/cp /Applications/Docker.app/Contents/Library/LaunchServices/com.docker.vmnetd /Library/PrivilegedHelperTools 20 | # sudo /bin/cp /Applications/Docker.app/Contents/Resources/com.docker.vmnetd.plist /Library/LaunchDaemons/ 21 | # sudo /bin/chmod 544 /Library/PrivilegedHelperTools/com.docker.vmnetd 22 | # sudo /bin/chmod 644 /Library/LaunchDaemons/com.docker.vmnetd.plist 23 | # sudo /bin/launchctl load /Library/LaunchDaemons/com.docker.vmnetd.plist 24 | # open -g -a Docker.app 25 | 26 | # # Wait for the server to start up, if applicable. 27 | # i=0 28 | # while ! docker system info &>/dev/null; do 29 | # (( i++ == 0 )) && printf %s '-- Waiting for Docker to finish starting up...' || printf '.' 30 | # sleep 1 31 | # done 32 | # (( i )) && printf '\n' 33 | 34 | # echo "-- Docker is ready." 35 | 36 | # brew update 37 | # brew cleanup 38 | # brew install docker-machine-driver-xhyve 39 | # sudo chown root:wheel $(brew --prefix)/opt/docker-machine-driver-xhyve/bin/docker-machine-driver-xhyve 40 | # sudo chmod u+s $(brew --prefix)/opt/docker-machine-driver-xhyve/bin/docker-machine-driver-xhyve 41 | # brew install docker docker-machine 42 | # # brew install --cask virtualbox 43 | # docker-machine create --driver xhyve mta 44 | # docker-machine env mta -------------------------------------------------------------------------------- /.github/workflows/scripts/install-podman-macos.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ssh-keygen -q -t rsa -N '' <<< $'\ny' >/dev/null 2>&1 4 | brew install podman 5 | podman machine init 6 | podman machine start 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #Maven 2 | target/ 3 | pom.xml.tag 4 | pom.xml.releaseBackup 5 | pom.xml.versionsBackup 6 | release.properties 7 | 8 | # Eclipse 9 | .project 10 | .classpath 11 | .settings/ 12 | bin/ 13 | 14 | # IntelliJ 15 | .idea 16 | *.ipr 17 | *.iml 18 | *.iws 19 | 20 | # NetBeans 21 | nb-configuration.xml 22 | 23 | # Visual Studio Code 24 | .vscode 25 | .factorypath 26 | 27 | # OSX 28 | .DS_Store 29 | 30 | # Vim 31 | *.swp 32 | *.swo 33 | 34 | # patch 35 | *.orig 36 | *.rej 37 | 38 | # Local environment 39 | .env 40 | mta-report 41 | windup-report 42 | ? 43 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/spring-to-quarkus-todo/8a5ecb8a8f4750533fb824766c97ed5bda25a8ac/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.8/apache-maven-3.8.8-bin.zip 18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Table of Contents 2 | - [Background](#background) 3 | - [Additional Resources](#additional-resources) 4 | - [Prerequisites](#prerequisites) 5 | - [Run the application](#run-the-application) 6 | - [Run the tests](#run-the-tests) 7 | - [Examine the internals](#examine-the-internals) 8 | - [Analyze the application for migration](#analyze-the-application-for-migration) 9 | - [Correct Issues](#correct-issues) 10 | - [Re-analyze the application](#re-analyze-the-application) 11 | - [Migrate Data source properties](#migrate-data-source-properties) 12 | - [Fix the tests](#fix-the-tests) 13 | - [Bonus: No hassle native image](#bonus-no-hassle-native-image) 14 | 15 | # Background 16 | Hands-on tutorial based on a demo application that [builds and runs as either Spring Boot or Quarkus](https://developers.redhat.com/blog/2021/02/09/spring-boot-on-quarkus-magic-or-madness). 17 | 18 | This tutorial takes a Spring Boot application using Spring MVC, Spring Data JPA, and a PostgreSQL database and converts it to Quarkus with little-to-no source code changes. It uses the [Red Hat Migration Toolkit for Applications](https://developers.redhat.com/products/mta/overview) to analyze the Spring Boot application and offer suggestions for how to migrate it to Quarkus. 19 | 20 | The completed solution to this exercise can be found in this repo's `solution` branch. 21 | 22 | # Additional Resources 23 | - [Quarkus for Spring Developers eBook](https://red.ht/quarkus-spring-devs) 24 | - [Quarkus Insights: Quarkus for Spring Developers](https://youtu.be/RvO8MUfc0kA) 25 | - [Why should I choose Quarkus over Spring for my microservices?](https://developers.redhat.com/articles/2021/08/31/why-should-i-choose-quarkus-over-spring-my-microservices) 26 | - [Spring Boot on Quarkus - Magic or Madness?](https://developers.redhat.com/blog/2021/02/09/spring-boot-on-quarkus-magic-or-madness) 27 | - [Evolution of the Quarkus Developer Experience](https://dzone.com/articles/evolution-of-the-quarkus-developer-experience) 28 | - [Red Hat Migration Toolkit for Applications](https://developers.redhat.com/products/mta/overview) 29 | 30 | # Prerequisites 31 | - A Java 17 runtime 32 | - A container runtime (i.e. [Docker](https://www.docker.com/) or [Podman](https://podman.io/)) 33 | - `docker` commands are used throughout this example 34 | - Access to the internet 35 | 36 | # Run the application 37 | 1. Start the required PostgreSQL database: 38 | ``` 39 | docker run -it --rm --name tododb -e POSTGRES_USER=todo -e POSTGRES_PASSWORD=todo -e POSTGRES_DB=tododb -p 5432:5432 postgres:17 40 | ``` 41 | 42 | > [!NOTE] 43 | > If you see an error related to rate limits (something like `You have reached your pull rate limit`), you need to first do a `docker login -u ` in order to pull the image. If you don't have a username, you can [create a free account](https://hub.docker.com/signup). 44 | 45 | 2. Run the application: 46 | ```shell 47 | ./mvnw clean spring-boot:run 48 | ``` 49 | 50 | You should see the standard Spring Boot Banner: 51 | ```shell 52 | 53 | . ____ _ __ _ _ 54 | /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ 55 | ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ 56 | \\/ ___)| |_)| | | | | || (_| | ) ) ) ) 57 | ' |____| .__|_| |_|_| |_\__, | / / / / 58 | =========|_|==============|___/=/_/_/_/ 59 | 60 | :: Spring Boot :: (v3.4.3) 61 | 62 | INFO 71818 --- [ restartedMain] com.acme.todo.TodoApplication : Started TodoApplication in 2.411 seconds (process running for 2.602) 63 | ``` 64 | 3. Open your browser to http://localhost:8080. You should see 65 | 66 | ![Initial Application Screen](images/spring-todo-1.png) 67 | 68 | 4. Play around with the application a bit. Type a new todo into the text box and press `Enter`. That todo will show up in the list: 69 | 70 | ![Add a new todo](images/spring-todo-2.png) 71 | 72 | 1. Click the empty circle next to a todo to complete it, or uncheck it to mark it as incomplete. 73 | 2. Click the `X` to remove a todo. 74 | 3. The `OpenAPI` link at the bottom of the page will open the OpenAPI 3.0 specification for the application. 75 | 4. The `Swagger UI` link opens the embedded [Swagger UI](https://swagger.io/tools/swagger-ui/), which can be used to execute some of the [RESTful endpoints](https://en.wikipedia.org/wiki/Representational_state_transfer) directly. 76 | 5. The `Prometheus Metrics` link leads to the [Prometheus metrics endpoint](https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-metrics-export-prometheus), which would be scraped intermittently by [Prometheus](https://prometheus.io/). 77 | 6. The `Health Check` link opens the [built-in health check](https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-health) exposed by Spring Boot. 78 | 5. Go ahead and play around a bit to see it all in action. Use `CTRL-C` in the terminal to stop the application before proceeding. 79 | 6. Run `./mvnw clean` to clean things up. 80 | 7. **IMPORTANT!** Also make sure to stop the docker daemon running the PostgreSQL database from step 1 before proceeding (you can use `CTRL-C` to stop it). 81 | 82 | # Run the tests 83 | Every application should have tests! This application is no different! 84 | 85 | Run the command `./mvnw clean verify` to run the tests. You should notice that doing this will automatically start a PostgreSQL database. The application uses the [Testcontainers Postgres Module](https://www.testcontainers.org/modules/databases/postgres/) to accomplish this. 86 | 87 | # Examine the internals 88 | - [Spring MVC](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html) for building the REST layer: 89 | - Open [`src/main/java/com/acme/todo/rest/TodoController.java`](src/main/java/com/acme/todo/rest/TodoController.java) to find the Spring MVC RESTful controller, exposing the various endpoints available to the user interface. 90 | - [Spring Data JPA](https://docs.spring.io/spring-data/jpa/docs/current/reference/html) for defining relational entities as well as storing and retrieving them: 91 | - Open [`src/main/java/com/acme/todo/domain/TodoEntity.java`](src/main/java/com/acme/todo/domain/TodoEntity.java) to find the [Java Persistence API (JPA)](https://www.oracle.com/java/technologies/persistence-jsp.html), representing the relational table for storing the todos. 92 | - Open [`src/main/java/com/acme/todo/repository/TodoRepository.java`](src/main/java/com/acme/todo/repository/TodoRepository.java) to find the [Spring Data JPA Repository](https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories), exposing all of the create, read, update, and delete operations for the `TodoEntity`. 93 | - [Spring Boot Actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html) for providing operational capabilities, including health checks and metrics gathering. 94 | - [SpringDoc OpenAPI 3](https://springdoc.org/v2) for generating and exposing RESTful API information as well as the embedded Swagger UI endpoint. 95 | > [!NOTE] 96 | > Spring Boot on its own does not have a starter providing this capability. 97 | - [Prometheus Micrometer Registry](https://micrometer.io/docs/registry/prometheus) for exposing metrics to Prometheus. 98 | - Testing 99 | - Open [`src/test/java/com/acme/todo/test/TodoControllerTests.java`](src/test/java/com/acme/todo/rest/TodoControllerTests.java) to find the [rest-assured](https://rest-assured.io/) tests for the controller layer. 100 | - Open [`src/test/resources/application.properties`](src/test/resources/application.properties) to find the [Testcontainers Postgres Module](https://www.testcontainers.org/modules/databases/postgres/) configuration. 101 | - Open [`src/main/resources/META-INF/resources`](src/main/resources/META-INF/resources) to find the user interface components used. 102 | - Open [`src/main/resources/application.properties`](src/main/resources/application.properties) to find the application configuration. 103 | - Open [`src/main/resources/import.sql`](src/main/resources/import.sql) to find some SQL that will pre-populate the database table with an initial set of data. 104 | 105 | # Analyze the application for migration 106 | We are going to use the [Red Hat Migration Toolkit for Applications (MTA)](https://developers.redhat.com/products/mta/overview) to analyze the application. MTA can be run in a number of different ways: 107 | - A [web application](https://access.redhat.com/documentation/en-us/migration_toolkit_for_runtimes/1.2/html/web_console_guide/index) running locally or on some remote machine. 108 | - A [command line interface](https://access.redhat.com/documentation/en-us/migration_toolkit_for_runtimes/1.2/html/cli_guide/index). 109 | - A [Maven plugin](https://access.redhat.com/documentation/en-us/migration_toolkit_for_runtimes/1.2/html/maven_plugin_guide/index). 110 | - A plugin to most major IDEs 111 | - [Eclipse and Red Hat CodeReady Studio](https://access.redhat.com/documentation/en-us/migration_toolkit_for_runtimes/1.2/html/eclipse_and_red_hat_codeready_studio_guide/index) 112 | - [IntelliJ IDEA](https://access.redhat.com/documentation/en-us/migration_toolkit_for_runtimes/1.2/html/intellij_idea_plugin_guide/index) 113 | - [Visual Studio Code](https://access.redhat.com/documentation/en-us/migration_toolkit_for_runtimes/1.2/html/visual_studio_code_extension_guide/index) 114 | 115 | For this exercise we have [pre-built a container image](https://quay.io/repository/rhappsvcs/spring-to-quarkus-mta-cli) that runs the [command line interface](https://access.redhat.com/documentation/en-us/migration_toolkit_for_runtimes/1.2/html/cli_guide/index). This approach was chosen to make it easier to run without having to install anything on a local machine. 116 | > [!NOTE] 117 | > The [`spring-to-quarkus-mta-cli` repository](https://github.com/RedHat-Middleware-Workshops/spring-to-quarkus-mta-cli) contains the tooling to create the container image being used. 118 | 119 | 1. On the terminal from the project directory, run one of the following commands based on the operating system you are running: 120 | - **\*nix/macos/Windows Subsystem for Linux (WSL):** `docker run -it -v $(pwd):/opt/project:z -u $(id -u):$(id -g) quay.io/rhappsvcs/spring-to-quarkus-mta-cli:latest` 121 | > [!TIP] 122 | > If using [podman](http://podman.io/), you could use the command `podman run -it -v $(pwd):/opt/project:z,U quay.io/rhappsvcs/spring-to-quarkus-mta-cli:latest` 123 | 124 | - **Windows**: 125 | - **cmd (not PowerShell):** `docker run -it -v %cd%:/opt/project quay.io/rhappsvcs/spring-to-quarkus-mta-cli:latest` 126 | - **PowerShell:** `docker run -it -v ${PWD}:/opt/project quay.io/rhappsvcs/spring-to-quarkus-mta-cli:latest` 127 | - **git bash:** `winpty docker run -it -v "/$(pwd -W):/opt/project" quay.io/rhappsvcs/spring-to-quarkus-mta-cli:latest` or `winpty docker run -it -v "/$(cmd //c cd):/opt/project" quay.io/rhappsvcs/spring-to-quarkus-mta-cli:latest` 128 | 129 | > If all else fails, you can hard-code the path to your current working directory (i.e. `docker run -it -v c:/path/to/spring-to-quarkus-todo:/opt/project quay.io/rhappsvcs/spring-to-quarkus-mta-cli:latest`). 130 | > 131 | > If none of those options work for you, [see here](https://stackoverflow.com/questions/41485217/mount-current-directory-as-a-volume-in-docker-on-windows-10) for more information on obtaining the current working directory for the `-v` option. 132 | 133 | 2. Once completed you will see something like: 134 | ```shell 135 | Report created: /opt/project/windup-report/index.html 136 | Access it at this URL: file:///opt/project/windup-report/index.html 137 | ``` 138 | 3. In your browser, open up the newly-created `windup-report/index.html` page within the project. You should see the **Applications** page: 139 | 140 | ![Applications](images/mta-app-list.png) 141 | 142 | 4. Click on **project** to move to the **Dashboard** page: 143 | 144 | ![Dashboard](images/mta-project-dashboard.png) 145 | 146 | 5. Click on the **issues** tab at the top to move to the **Issues** page: 147 | 148 | ![Issues](images/mta-issues.png) 149 | 150 | The analysis produced by the Migration Toolkit for Applications contains many links to Quarkus documentation explaining various concepts, like datasource configuration, build tools, etc. Feel free to click around and visit some of these links. 151 | 152 | # Correct Issues 153 | Each issue is something that needs to be dealt with to convert the application from Spring to Quarkus. The majority of the issues presented are related to dependencies within the [`pom.xml`](pom.xml). Let's fix all of those issues first. 154 | 155 | 1. In the **Category** dropdown, select **Mandatory** to filter the issues by mandatory. 156 | 157 | ![Mandatory Issues](images/mta-issues-mandatory.png) 158 | 159 | 2. Next, find and expand the `Replace the Spring Parent POM with Quarkus BOM` issue. This will expand and explain the issue detail: 160 | 161 | ![Spring POM to Quarkus BOM](images/spring-pom-to-quarkus-bom-desc.png) 162 | 163 | 3. Clicking on `pom.xml` will bring up a window describing all the necessary changes needed to the project's `pom.xml`. The window will have a small icon in the gutter on the left-hand side as well as some squiggly lines where issues are found. You can hover your mouse over each of these lines for a detailed description of the issue and how to remediate it. 164 | 165 | ![Issue hovers](images/spring-pom-to-quarkus-bom-issues.png) 166 | 167 | ## `pom.xml` 168 | The first issue is replacing the Spring parent POM with the Quarkus BOM. 169 | 170 | > Quarkus does not use a parent POM. Instead, [Quarkus imports a BOM](https://quarkus.io/guides/maven-tooling#build-tool-maven) inside the `` section of the pom 171 | 172 | While we're in `pom.xml` we may as well fix all the issues related to it. 173 | 174 | 1. In your editor/IDE, open [`pom.xml`](pom.xml) 175 | 2. Find the `` section and remove it 176 | 3. In the `` section, add `3.19.2` 177 | 4. After the `` section find the `` section and add the following inside the ``: 178 | ```xml 179 | 180 | io.quarkus.platform 181 | quarkus-bom 182 | ${quarkus.platform.version} 183 | pom 184 | import 185 | 186 | ``` 187 | 188 | 5. The next issue is `Replace the Spring Web artifact with Quarkus 'spring-web' extension`. 189 | 190 | In `pom.xml`, find 191 | ```xml 192 | 193 | org.springframework.boot 194 | spring-boot-starter-web 195 | 196 | ``` 197 | 198 | and, according to the [Quarkus Spring Web Guide](https://quarkus.io/guides/spring-web), replace it with 199 | ```xml 200 | 201 | io.quarkus 202 | quarkus-spring-web 203 | 204 | ``` 205 | 206 | Additionally, the documentation on the issue mentions that [Starting with Quarkus version 2.5, the underlying JAX-RS engine must be chosen](https://github.com/quarkusio/quarkus/wiki/Migration-Guide-2.5#spring-web). 207 | 208 | In `pom.xml`, add the `quarkus-rest-jackson` extension to the `` section: 209 | ```xml 210 | 211 | io.quarkus 212 | quarkus-rest-jackson 213 | 214 | ``` 215 | 216 | 6. The next issue is `Replace the SpringBoot Data JPA artifact with Quarkus 'spring-data-jpa' extension`. 217 | 218 | In `pom.xml`, find 219 | ```xml 220 | 221 | org.springframework.boot 222 | spring-boot-starter-data-jpa 223 | 224 | ``` 225 | 226 | and, according to the [Quarkus JPA Guide](https://quarkus.io/guides/spring-data-jpa), replace it with 227 | ```xml 228 | 229 | io.quarkus 230 | quarkus-spring-data-jpa 231 | 232 | ``` 233 | 234 | 7. The next issue is `Spring component spring-boot-starter-validation requires investigation`. 235 | 236 | In `pom.xml`, find 237 | ```xml 238 | 239 | org.springframework.boot 240 | spring-boot-starter-validation 241 | 242 | ``` 243 | 244 | and, according to the [Quarkus Validation with Hibernate Validator Guide](https://quarkus.io/guides/validation), replace it with 245 | ```xml 246 | 247 | io.quarkus 248 | quarkus-hibernate-validator 249 | 250 | ``` 251 | 252 | 8. The next issue is `Spring component springdoc-openapi-starter-webmvc-ui requires investigation`. [SpringDoc OpenAPI](https://springdoc.org/) is a 3rd party open source library that isn't part of Spring itself. Luckily, there is the [Quarkus OpenAPI extension](https://quarkus.io/guides/openapi-swaggerui). 253 | 254 | In `pom.xml`, find 255 | ```xml 256 | 257 | org.springdoc 258 | springdoc-openapi-starter-webmvc-ui 259 | 2.8.5 260 | 261 | ``` 262 | 263 | and replace it with 264 | ```xml 265 | 266 | io.quarkus 267 | quarkus-smallrye-openapi 268 | 269 | ``` 270 | 271 | 9. The next issue is `Replace the Spring Boot Actuator dependency with Quarkus Smallrye Health extension`. 272 | 273 | In `pom.xml`, find 274 | ```xml 275 | 276 | org.springframework.boot 277 | spring-boot-starter-actuator 278 | 279 | ``` 280 | 281 | and, according to the [Quarkus - SmallRye Health Guide](https://quarkus.io/guides/smallrye-health), replace it with 282 | ```xml 283 | 284 | io.quarkus 285 | quarkus-smallrye-health 286 | 287 | ``` 288 | 289 | 10. The next issue is `Replace the 'micrometer-registry-prometheus' dependency with Quarkus 'quarkus-micrometer-registry-prometheus' extension`. [Micrometer Metrics](https://micrometer.io/) are used in Quarkus as well as in Spring Boot, but Quarkus applications need to use the [Quarkus Micrometer Metrics Extension](https://quarkus.io/guides/micrometer). 290 | 291 | In `pom.xml`, find 292 | ```xml 293 | 294 | io.micrometer 295 | micrometer-registry-prometheus 296 | 297 | ``` 298 | 299 | and replace it with 300 | ```xml 301 | 302 | io.quarkus 303 | quarkus-micrometer-registry-prometheus 304 | 305 | ``` 306 | 307 | 11. The next issue is `Replace the 'postgresql' dependency with Quarkus 'quarkus-jdbc-postgresql' extension`. The `org.postgresql:postgresql` dependency needs to be swapped for the [Quarkus PostgreSQL extension](https://quarkus.io/guides/datasource#jdbc-datasource-2). 308 | 309 | In `pom.xml`, find 310 | ```xml 311 | 312 | org.postgresql 313 | postgresql 314 | runtime 315 | 316 | ``` 317 | 318 | and replace it with 319 | ```xml 320 | 321 | io.quarkus 322 | quarkus-jdbc-postgresql 323 | 324 | ``` 325 | 326 | 12. The next issue is `Remove spring-boot-devtools dependency`. The `org.springframework.boot:spring-boot-devtools` isn't needed. The [Spring Boot Developer Tools](https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.devtools) provides features aiming to enhance developer productivity, such as live reload. These features are part of the core of Quarkus. 327 | 328 | In `pom.xml`, find 329 | ```xml 330 | 331 | org.springframework.boot 332 | spring-boot-devtools 333 | true 334 | 335 | ``` 336 | 337 | and remove it 338 | 339 | 13. The next issue is `Spring component spring-boot-starter-test requires investigation`. 340 | 341 | In `pom.xml`, find 342 | ```xml 343 | 344 | org.springframework.boot 345 | spring-boot-starter-test 346 | test 347 | 348 | ``` 349 | 350 | and, according to the [Quarkus testing guide](https://quarkus.io/guides/getting-started-testing), replace it with both of these dependencies: 351 | ```xml 352 | 353 | io.quarkus 354 | quarkus-junit5 355 | test 356 | 357 | ``` 358 | 359 | AND 360 | 361 | ```xml 362 | 363 | io.quarkus 364 | quarkus-junit5-mockito 365 | test 366 | 367 | ``` 368 | 369 | 14. The next issue is `Replace the spring-boot-maven-plugin dependency`. The `org.springframework.boot:spring-boot-maven-plugin` needs to be changed so that the application [is built with Quarkus](https://quarkus.io/guides/maven-tooling#build-tool-maven), both for running on the JVM and in native image. 370 | 371 | In `pom.xml`, find 372 | ```xml 373 | 374 | 375 | 376 | org.springframework.boot 377 | spring-boot-maven-plugin 378 | 379 | 380 | 381 | ``` 382 | 383 | and replace it with 384 | ```xml 385 | 386 | 387 | 388 | io.quarkus 389 | quarkus-maven-plugin 390 | ${quarkus.platform.version} 391 | true 392 | 393 | 394 | 395 | build 396 | generate-code 397 | generate-code-tests 398 | 399 | 400 | 401 | 402 | 403 | maven-compiler-plugin 404 | ${compiler-plugin.version} 405 | 406 | true 407 | 408 | 409 | 410 | maven-surefire-plugin 411 | ${surefire-plugin.version} 412 | 413 | 414 | org.jboss.logmanager.LogManager 415 | ${maven.home} 416 | 417 | 418 | 419 | 420 | maven-failsafe-plugin 421 | ${surefire-plugin.version} 422 | 423 | 424 | 425 | integration-test 426 | verify 427 | 428 | 429 | 430 | ${project.build.directory}/${project.build.finalName}-runner 431 | org.jboss.logmanager.LogManager 432 | ${maven.home} 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | native 443 | 444 | 445 | native 446 | 447 | 448 | 449 | true 450 | false 451 | 452 | 453 | 454 | ``` 455 | 456 | > [!NOTE] 457 | > While this replacement might seem like a lot of XML, it also sets up the application to [build a native image](https://quarkus.io/guides/building-native-image) using the `native` Maven profile. 458 | 459 | ### `@SpringBootApplication` class 460 | Navigate back to the main issues page and find the `Remove the SpringBoot @SpringBootApplication annotation` issue, then expand it. 461 | 462 | ![Remove SpringBootApplication annotation](images/mta-remove-springbootapplication.png) 463 | 464 | A Spring Boot application also contains a "main" class with the `@SpringBootApplication` annotation. A Quarkus application does not have such a class. There are 2 options that can be taken: 465 | 1. Remove the [`src/main/java/com/acme/todo/TodoApplication.java`](src/main/java/com/acme/todo/TodoApplication.java) class 466 | 467 | **OR** 468 | 469 | 2. Add the `org.springframework.boot:spring-boot-autoconfigure` dependency as an `optional` Maven dependency. An `optional` dependency is available when an application compiles but is not packaged with the application at runtime. Doing this would allow the application to compile without modification, but you would also need to maintain a Spring version along with the Quarkus application. 470 | 471 | To use this option, add this to the `` section of `pom.xml`: 472 | ```xml 473 | 474 | org.springframework.boot 475 | spring-boot-autoconfigure 476 | 3.4.3 477 | true 478 | 479 | ``` 480 | 481 | > [!TIP] 482 | > This is the option chosen in the `solution` branch of this repository. This option was chosen purely because we did not want to have to change any source code within the project. In a more "real world" scenario, the better option would most likely be option 1. 483 | 484 | --- 485 | 486 | Some issues that weren't caught by the tool but also need to be fixed: 487 | 1. The application uses the [AssertJ library](https://assertj.github.io/doc) for testing. The AssertJ version is managed by the Spring Boot BOM but not by the Quarkus BOM, therefore you need to explicitly declare its version. 488 | 489 | In `pom.xml`, find 490 | ```xml 491 | 492 | org.assertj 493 | assertj-core 494 | test 495 | 496 | ``` 497 | 498 | and add `3.27.3` because the Quarkus BOM does not manage the version of the [AssertJ](https://assertj.github.io/doc/) dependency. The resulting dependency should be 499 | 500 | ```xml 501 | 502 | org.assertj 503 | assertj-core 504 | 3.27.3 505 | test 506 | 507 | ``` 508 | 509 | 2. Remove the [Testcontainers](https://www.testcontainers.org) dependencies. 510 | 511 | In `pom.xml`, find the following within the `` section: 512 | ```xml 513 | 514 | org.testcontainers 515 | testcontainers-bom 516 | 1.20.6 517 | pom 518 | import 519 | 520 | ``` 521 | 522 | and remove it, then in the main `` section find 523 | ```xml 524 | 525 | org.testcontainers 526 | junit-jupiter 527 | test 528 | 529 | 530 | org.testcontainers 531 | postgresql 532 | test 533 | 534 | ``` 535 | 536 | and remove them as well. 537 | 538 | 3. Change the [rest-assured](https://rest-assured.io) dependency. Currently the application uses [rest-assured's Spring Support](https://github.com/rest-assured/rest-assured/wiki/Spring#spring-support). Rest-assured support is supported out-of-the-box in Quarkus and it's version is managed by the Quarkus BOM. 539 | 540 | In `pom.xml`, find 541 | ```xml 542 | 543 | io.rest-assured 544 | spring-mock-mvc 545 | 5.5.1 546 | test 547 | 548 | ``` 549 | 550 | and replace it with 551 | ```xml 552 | 553 | io.rest-assured 554 | rest-assured 555 | test 556 | 557 | ``` 558 | 559 | Now that the changes to `pom.xml` are complete, save and close it. 560 | 561 | When completed, your `pom.xml` should look like the [`pom.xml` in the solution branch](https://github.com/RedHat-Middleware-Workshops/spring-to-quarkus-todo/blob/solution/pom.xml). 562 | 563 | # Re-analyze the application 564 | Now let's re-analyze the application to see how much of the migration has been completed. 565 | 566 | 1. On the terminal from the project directory, run one of the following commands based on the operating system you are running: 567 | - **\*nix/macos/Windows Subsystem for Linux (WSL):** `docker run -it -v $(pwd):/opt/project:z -u $(id -u):$(id -g) quay.io/rhappsvcs/spring-to-quarkus-mta-cli:latest` 568 | > [!TIP] 569 | > If using [podman](http://podman.io/), you could use the command `podman run -it -v $(pwd):/opt/project:z,U quay.io/rhappsvcs/spring-to-quarkus-mta-cli:latest` 570 | 571 | - **Windows**: 572 | - **cmd (not PowerShell):** `docker run -it -v %cd%:/opt/project quay.io/rhappsvcs/spring-to-quarkus-mta-cli:latest` 573 | - **PowerShell:** `docker run -it -v ${PWD}:/opt/project quay.io/rhappsvcs/spring-to-quarkus-mta-cli:latest` 574 | - **git bash:** `winpty docker run -it -v "/$(pwd -W):/opt/project" quay.io/rhappsvcs/spring-to-quarkus-mta-cli:latest` or `winpty docker run -it -v "/$(cmd //c cd):/opt/project" quay.io/rhappsvcs/spring-to-quarkus-mta-cli:latest` 575 | 576 | > If all else fails, you can hard-code the path to your current working directory (i.e. `docker run -it -v c:/path/to/spring-to-quarkus-todo:/opt/project quay.io/rhappsvcs/spring-to-quarkus-mta-cli:latest`). 577 | > 578 | > If none of those options work for you, [see here](https://stackoverflow.com/questions/41485217/mount-current-directory-as-a-volume-in-docker-on-windows-10) for more information on obtaining the current working directory for the `-v` option. 579 | 580 | 2. Once completed you will see something like: 581 | ```shell 582 | Report created: /opt/project/windup-report/index.html 583 | Access it at this URL: file:///opt/project/windup-report/index.html 584 | ``` 585 | 586 | 3. Refresh the browser and return to the **Issues** tab as you did before. There should only be a few issues remaining. The remainder of the issues are fixed with configuration in the [`application.properties`](src/main/resources/application.properties) file. 587 | 588 | ![Issues after pom.xml fixes](images/mta-issues-after-pom-fixes.png) 589 | 590 | 4. Before proceeding, let's start the newly-converted Quarkus application in [Quarkus's Dev Mode](https://quarkus.io/guides/maven-tooling#dev-mode). 591 | 5. In the terminal, run `./mvnw clean quarkus:dev`. 592 | > You will probably get some test compilation errors. That's ok at this point. We will fix that in a few minutes. 593 | 594 | 6. The Quarkus application should start up, and you should see the Quarkus banner: 595 | ```shell 596 | INFO [io.qua.dat.dep.dev.DevServicesDatasourceProcessor] (build-8) Dev Services for default datasource (postgresql) started - container ID is a440b4c6e51e 597 | __ ____ __ _____ ___ __ ____ ______ 598 | --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 599 | -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \ 600 | --\___\_\____/_/ |_/_/|_/_/|_|\____/___/ 601 | WARN [org.hib.eng.jdb.spi.SqlExceptionHelper] (JPA Startup Thread) SQL Warning Code: 0, SQLState: 00000 602 | INFO [io.quarkus] (Quarkus Main Thread) spring-to-quarkus-todo 0.0.1-SNAPSHOT on JVM (powered by Quarkus 3.19.2) started in 3.404s. Listening on: http://localhost:8080 603 | INFO [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated. 604 | INFO [io.quarkus] (Quarkus Main Thread) Installed features: [agroal, cdi, hibernate-orm, hibernate-orm-panache, hibernate-validator, jdbc-postgresql, micrometer, narayana-jta, rest, rest-jackson, smallrye-context-propagation, smallrye-health, smallrye-openapi, spring-data-jpa, spring-di, spring-web, swagger-ui, vertx] 605 | ``` 606 | 607 | > Notice the line `Dev Services for the default datasource (postgresql) started`. [Quarkus Dev Services](https://quarkus.io/guides/dev-services) noticed the PostgreSQL extension on the classpath and started a PostgreSQL container image automatically, while also automatically setting all the configuration properties for the application to communicate with it! 608 | 609 | 7. Re-open your browser to http://localhost:8080. 610 | 8. You may (or may not) notice a bunch of exceptions in the console log. This is because we haven't finished converting the application. We still need to migrate some Spring datasource configuration. 611 | 612 | # Migrate Data source properties 613 | The other issues relate to properties within `src/main/resources/application.properties`. 614 | 615 | 1. In your browser tab containing the Migration Toolkit analysis, go back to the **Issues** tab, expand the `Replace Spring datasource property key/value pairs with Quarkus properties` issue and then click on the `src/main/resources/application.properties` link. You will see a similar editor as you did previously when you opened the `pom.xml` file. 616 | ![Replace Spring datasource property key/value pairs with Quarkus properties](images/spring-datasource-properties-desc.png) 617 | 618 | 2. In your editor/IDE, open [`src/main/resources/application.properties`](src/main/resources/application.properties) 619 | 3. The first issue is `Replace Spring JPA Hibernate property with Quarkus property` 620 | 621 | In `src/main/resources/application.properties`, find `spring.jpa.hibernate.ddl-auto=create-drop` and replace with `quarkus.hibernate-orm.database.generation=drop-and-create` according to the [Quarkus Hibernate ORM and JPA Guide](https://quarkus.io/guides/hibernate-orm). This will allow Quarkus (& Hibernate under the covers) to automatically create the database schema upon startup. 622 | 623 | 4. The next issue is `Replace Spring datasource property key/value pairs with Quarkus properties`. 624 | 625 | In `src/main/resources/application.properties`, find `spring.datasource.url=jdbc:postgresql://localhost:5432/tododb` and remove it completely 626 | > As you saw, [Quarkus Dev Services](https://quarkus.io/guides/dev-services) will automatically create the database for us and bind it to our application. 627 | 628 | Similarly, find and remove `spring.datasource.username=todo` and `spring.datasource.password=todo` as well 629 | 630 | 5. The next issue is `Replace Spring OpenAPI endpoint mapping`. 631 | 632 | In `src/main/resources/application.properties`, find `springdoc.api-docs.path=/openapi` 633 | 634 | and replace it with `quarkus.smallrye-openapi.path=/openapi` 635 | 636 | to map the default Quarkus OpenAPI endpoint from `/q/openapi` to `/openapi`. 637 | 638 | 6. The next issue is `Replace Spring Swagger endpoint mapping`. 639 | 640 | In `src/main/resources/application.properties`, find `springdoc.swagger-ui.path=/swagger-ui` 641 | 642 | and replace it with `quarkus.swagger-ui.path=/swagger-ui` 643 | 644 | to map the default Quarkus Swagger UI page from `/q/swagger-ui` to `/swagger-ui`. 645 | 646 | Additionally, add `quarkus.swagger-ui.always-include=true` so that Quarkus will always expose the Swagger UI endpoint. By default, it is only exposed in Dev Mode. 647 | 648 | 7. The next issue is `Replace Spring Prometheus Metrics endpoint mapping`. 649 | 650 | In `src/main/resources/application.properties`, add `quarkus.micrometer.export.prometheus.path=/actuator/prometheus` to map the default Quarkus Prometheus metrics endpoint from `/q/metrics` to `/actuator/prometheus`. 651 | 652 | 8. The next issue is `Replace Spring Health endpoint mapping`. 653 | 654 | In `src/main/resources/application.properties`, add `quarkus.smallrye-health.root-path=/actuator/health` to map the default Quarkus health endpoint from `/q/health` to `/actuator/health`. 655 | 656 | Additionally, find and remove `management.endpoints.web.exposure.include=prometheus,health`. 657 | --- 658 | 659 | There are a couple of other properties that the analysis didn't find. 660 | 1. Add `quarkus.datasource.metrics.enabled=true` so that Quarkus will automatically expose datasource-related metrics to Micrometer. 661 | 662 | 2. Now, go back to your browser tab containing the http://localhost:8080 page and refresh it. Magically the page should load and no exceptions in the console! Everything should work as it did before! 663 | > [Quarkus Dev Mode](https://quarkus.io/guides/maven-tooling#dev-mode) has seamlessly redeployed your application while also creating the necessary schema and even importing sample data from [`src/main/resources/import.sql`](src/main/resources/import.sql). 664 | 665 | 3. Navigate to http://localhost:8080/q/dev (or in your terminal where Dev Mode is running, press `d`) to view the [Quarkus Dev UI](https://quarkus.io/guides/dev-ui), a landing page for interacting with your application. All extensions used by the application should show up here along with links to their documentation. Some extensions provide the ability to interact and modify configuration right from the UI. 666 | 667 | # Fix the tests 668 | Now let's fix the tests so that they run. 669 | 670 | 1. In your terminal where Quarkus dev mode is running, press the `r` key to enable [Quarkus Continuous Testing](https://quarkus.io/guides/continuous-testing). You should see the console display `No tests found`. 671 | 2. [Quarkus Dev Services for Databases](https://quarkus.io/guides/databases-dev-services) automatically provisions a database container for the running application AND while executing tests. Therefore, we do not need the [`src/test/resources/application.properties`](src/test/resources/application.properties) file. Delete [`src/test/resources/application.properties`](src/test/resources/application.properties). 672 | 3. Open [`src/test/java/com/acme/todo/rest/TodoControllerTests.java`](src/test/java/com/acme/todo/rest/TodoControllerTests.java). 673 | > You might find as you move through the following steps that your terminal window starts going crazy with lots of errors. [Quarkus Continuous Testing](https://quarkus.io/guides/continuous-testing) is continually recompiling and re-executing the tests in the background and reporting back to the console. Once you complete the following steps you should get green text at the bottom saying that all tests pass. 674 | 675 | 1. Remove the `@SpringBootTest` and `@AutoConfigureMockMvc` annotations on the class. 676 | 2. Add the `@QuarkusTest` annotation to the class. 677 | 3. Find 678 | 679 | ```java 680 | @Autowired 681 | MockMvc mockMvc; 682 | ``` 683 | 684 | and remove it. 685 | 4. Find 686 | 687 | ```java 688 | @MockBean 689 | TodoRepository todoRepository; 690 | ``` 691 | 692 | and change it to 693 | 694 | ```java 695 | @InjectMock 696 | TodoRepository todoRepository; 697 | ``` 698 | 699 | > Make sure to import `io.quarkus.test.InjectMock` and not `io.quarkus.test.junit.mockito`! 700 | 701 | 5. Find 702 | 703 | ```java 704 | @BeforeEach 705 | public void beforeEach() { 706 | RestAssuredMockMvc.mockMvc(this.mockMvc); 707 | } 708 | ``` 709 | 710 | and remove it. 711 | 712 | 6. Find 713 | 714 | ```java 715 | import static io.restassured.module.mockmvc.RestAssuredMockMvc.*; 716 | ``` 717 | 718 | and replace it with 719 | 720 | ```java 721 | import static io.restassured.RestAssured.*; 722 | ``` 723 | 724 | 7. You may need to optimize your imports depending on your IDE. You can inspect the imports on the [`solution` branch](https://github.com/RedHat-Middleware-Workshops/spring-to-quarkus-todo/blob/solution/src/test/java/com/acme/todo/rest/TodoControllerTests.java) to see what you need if you run into a problem. 725 | 726 | 8. In the terminal you should now see `All 5 tests are passing (0 skipped), 5 tests were run in 396ms. Tests completed at 14:40:08 due to changes to TodoControllerTests.class.` indicating that all the tests are now passing. 727 | 728 | > If you modify any tests, or any of the source code, the tests should automatically re-run, giving you an instant feedback loop. 729 | 730 | 4. Hit `CTRL-C` (or press `q`) in your terminal once done. 731 | 732 | # Bonus: No hassle native image 733 | As a bonus exercise, let's create and run a Quarkus native image. Writing this exercise we don't know what host OS each individual is using, so we will use container images to facilitate building the native executable as a Linux executable, and then create a coontainer image from it. This will also alleviate the need to [install GraalVM on our local machines](https://quarkus.io/guides/building-native-image#graalvm). 734 | 735 | The easiest way to create a container image containing a native executable is to leverage one of the [Quarkus container-image extensions](https://quarkus.io/guides/building-native-image#using-the-container-image-extensions). If one of those extensions is present, then creating a container image for the native executable is essentially a matter of executing a single command. 736 | 737 | > [!NOTE] 738 | > Native image creation is a CPU and memory-intensive operation. It may or may not work depending on your hardware specs. You may need at lease 6 GB of RAM allocated to your Docker daemon. 739 | 740 | Since we already have a Docker runtime we'll use the [Docker container image extension](https://quarkus.io/guides/container-image#docker) to perform the container image build. 741 | 742 | 1. To install the extension into the project, return to the terminal and run `./mvnw quarkus:add-extension -Dextensions="container-image-docker"` 743 | 2. Since this is an existing non-Quarkus application, we need to create the `Dockerfile` for the native image. 744 | > If we had created a new Quarkus application from [Code Quarkus](https://code.quarkus.io), this would have been created for us. 745 | 746 | 1. Create the directory `src/main/docker` 747 | 2. Inside `src/main/docker`, create the file `Dockerfile.native` 748 | 3. Paste in the following into `Dockerfile.native`: 749 | ```dockerfile 750 | FROM quay.io/quarkus/quarkus-micro-image:2.0 751 | WORKDIR /work/ 752 | RUN chown 1001 /work \ 753 | && chmod "g+rwX" /work \ 754 | && chown 1001:root /work 755 | COPY --chown=1001:root target/*-runner /work/application 756 | 757 | EXPOSE 8080 758 | USER 1001 759 | 760 | CMD ["./application", "-Dquarkus.http.host=0.0.0.0"] 761 | ``` 762 | 763 | 4. Save and close `src/main/docker/Dockerfile.native` 764 | 765 | 3. Building a native image can be accomplished by running `./mvnw package -Pnative -Dquarkus.native.container-build=true -Dquarkus.container-image.build=true -Dquarkus.container-image.group=` in the terminal. Building a native image may take several minutes to complete depending on the specs of your machine and how much CPU/RAM is available. 766 | > There are many [container image options](https://quarkus.io/guides/container-image#container-image-options) available. The `quarkus.container-image.group=` option removes the `${user.name}` from the final image name. If we did not include this option, the final image would be created as `${user.name}/${quarkus.application.name}:${quarkus.application.version}`. This simply makes it easier to write this tutorial without having to worry about people's usernames! 767 | 768 | > [!NOTE] 769 | > If the native image build fails due to an out of memory error, you may need to increase the memory size of your docker daemon to a minimum of 6GB. 770 | > 771 | > You could also try adding the parameter `-Dquarkus.native.additional-build-args=-J-XX:TieredStopAtLevel=1` to the `./mvnw package` command you ran. 772 | 773 | 4. Once the native image build is complete, start the PostgreSQL database container needed by the application: 774 | ```shell 775 | docker run -it --rm --name tododb -e POSTGRES_USER=todo -e POSTGRES_PASSWORD=todo -e POSTGRES_DB=tododb -p 5432:5432 postgres:17 776 | ``` 777 | 778 | > Quarkus Dev Services is only available in development mode. Running a native executable runs in production mode. 779 | 780 | 5. Before starting the native image container, we first need to get the internal ip address of the running PostgreSQL DB so that our Quarkus application can connect to it. 781 | - In another terminal, run `docker inspect tododb | grep IPAddress`. You should see something like 782 | ```shell 783 | "SecondaryIPAddresses": null, 784 | "IPAddress": "172.17.0.2", 785 | "IPAddress": "172.17.0.2", 786 | ``` 787 | 788 | In this example, the ip address is `172.17.0.2`. 789 | 790 | 6. Now run the native executable image, **making sure to substitute the ip address gathered in the previous step** 791 | ```shell 792 | docker run -i --rm -p 8080:8080 -e QUARKUS_DATASOURCE_JDBC_URL=jdbc:postgresql://172.17.0.2:5432/tododb -e QUARKUS_DATASOURCE_USERNAME=todo -e QUARKUS_DATASOURCE_PASSWORD=todo spring-to-quarkus-todo:0.0.1-SNAPSHOT 793 | ``` 794 | > [!TIP] 795 | > If this command didn't work, make sure you substituted the ip address you gathered in **step 5** in the command! 796 | 797 | > [!IMPORTANT] 798 | > Notice the startup time. It should start up in only a few milliseconds! 799 | 800 | 7. Return to your browser to http://localhost:8080 801 | 8. Everything should work as before! No hassle native image generation! 802 | 9. Close both the application and the PostgreSQL instances via `CTRL-C` when you're done. -------------------------------------------------------------------------------- /devfile.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: 1.0.0 2 | metadata: 3 | name: spring-to-quarkus-todo 4 | projects: 5 | - name: spring-to-quarkus-todo 6 | source: 7 | location: 'https://github.com/RedHat-Middleware-Workshops/spring-to-quarkus-todo' 8 | type: git 9 | branch: main 10 | components: 11 | - id: redhat/quarkus-java11/latest 12 | type: chePlugin 13 | - mountSources: true 14 | endpoints: 15 | - name: todo-application 16 | port: 8080 17 | memoryLimit: 1024Mi 18 | type: dockerimage 19 | volumes: 20 | - name: m2 21 | containerPath: /home/jboss/.m2 22 | alias: todo-application 23 | image: 'registry.redhat.io/codeready-workspaces/plugin-java11-rhel8:latest' 24 | env: 25 | - value: '-XX:+UseParallelGC -XX:MinHeapFreeRatio=10 -XX:MaxHeapFreeRatio=20 -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -Dsun.zip.disableMemoryMapping=true -Djava.security.egd=file:/dev/./urandom -Duser.home=/home/jboss' 26 | name: JAVA_OPTS 27 | - value: $(JAVA_OPTS) 28 | name: MAVEN_OPTS 29 | - alias: tododb 30 | endpoints: 31 | - name: tododb 32 | port: 5432 33 | memoryLimit: 1024Mi 34 | type: dockerimage 35 | image: 'registry.redhat.io/rhel8/postgresql-14:latest' 36 | env: 37 | - value: todo 38 | name: POSTGRESQL_USER 39 | - value: todo 40 | name: POSTGRESQL_PASSWORD 41 | - value: tododb 42 | name: POSTGRESQL_DATABASE 43 | commands: 44 | - name: Run application 45 | actions: 46 | - workdir: '${CHE_PROJECTS_ROOT}/spring-to-quarkus-todo' 47 | type: exec 48 | command: './mvnw clean spring-boot:run' 49 | component: todo-application 50 | - name: Attach remote debugger 51 | actions: 52 | - referenceContent: | 53 | { 54 | "version": "0.2.0", 55 | "configurations": [ 56 | { 57 | "type": "java", 58 | "request": "attach", 59 | "name": "Attach to Remote App", 60 | "hostName": "localhost", 61 | "port": 5005 62 | } 63 | ] 64 | } 65 | type: vscode-launch 66 | -------------------------------------------------------------------------------- /images/mta-app-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/spring-to-quarkus-todo/8a5ecb8a8f4750533fb824766c97ed5bda25a8ac/images/mta-app-list.png -------------------------------------------------------------------------------- /images/mta-issues-after-pom-fixes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/spring-to-quarkus-todo/8a5ecb8a8f4750533fb824766c97ed5bda25a8ac/images/mta-issues-after-pom-fixes.png -------------------------------------------------------------------------------- /images/mta-issues-mandatory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/spring-to-quarkus-todo/8a5ecb8a8f4750533fb824766c97ed5bda25a8ac/images/mta-issues-mandatory.png -------------------------------------------------------------------------------- /images/mta-issues.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/spring-to-quarkus-todo/8a5ecb8a8f4750533fb824766c97ed5bda25a8ac/images/mta-issues.png -------------------------------------------------------------------------------- /images/mta-project-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/spring-to-quarkus-todo/8a5ecb8a8f4750533fb824766c97ed5bda25a8ac/images/mta-project-dashboard.png -------------------------------------------------------------------------------- /images/mta-remove-springbootapplication.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/spring-to-quarkus-todo/8a5ecb8a8f4750533fb824766c97ed5bda25a8ac/images/mta-remove-springbootapplication.png -------------------------------------------------------------------------------- /images/quarkus-dev-ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/spring-to-quarkus-todo/8a5ecb8a8f4750533fb824766c97ed5bda25a8ac/images/quarkus-dev-ui.png -------------------------------------------------------------------------------- /images/spring-datasource-properties-desc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/spring-to-quarkus-todo/8a5ecb8a8f4750533fb824766c97ed5bda25a8ac/images/spring-datasource-properties-desc.png -------------------------------------------------------------------------------- /images/spring-pom-to-quarkus-bom-desc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/spring-to-quarkus-todo/8a5ecb8a8f4750533fb824766c97ed5bda25a8ac/images/spring-pom-to-quarkus-bom-desc.png -------------------------------------------------------------------------------- /images/spring-pom-to-quarkus-bom-issues.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/spring-to-quarkus-todo/8a5ecb8a8f4750533fb824766c97ed5bda25a8ac/images/spring-pom-to-quarkus-bom-issues.png -------------------------------------------------------------------------------- /images/spring-todo-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/spring-to-quarkus-todo/8a5ecb8a8f4750533fb824766c97ed5bda25a8ac/images/spring-todo-1.png -------------------------------------------------------------------------------- /images/spring-todo-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/spring-to-quarkus-todo/8a5ecb8a8f4750533fb824766c97ed5bda25a8ac/images/spring-todo-2.png -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Apache Maven Wrapper startup batch script, version 3.2.0 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | # e.g. to debug Maven itself, use 32 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | # ---------------------------------------------------------------------------- 35 | 36 | if [ -z "$MAVEN_SKIP_RC" ] ; then 37 | 38 | if [ -f /usr/local/etc/mavenrc ] ; then 39 | . /usr/local/etc/mavenrc 40 | fi 41 | 42 | if [ -f /etc/mavenrc ] ; then 43 | . /etc/mavenrc 44 | fi 45 | 46 | if [ -f "$HOME/.mavenrc" ] ; then 47 | . "$HOME/.mavenrc" 48 | fi 49 | 50 | fi 51 | 52 | # OS specific support. $var _must_ be set to either true or false. 53 | cygwin=false; 54 | darwin=false; 55 | mingw=false 56 | case "$(uname)" in 57 | CYGWIN*) cygwin=true ;; 58 | MINGW*) mingw=true;; 59 | Darwin*) darwin=true 60 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 61 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 62 | if [ -z "$JAVA_HOME" ]; then 63 | if [ -x "/usr/libexec/java_home" ]; then 64 | JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME 65 | else 66 | JAVA_HOME="/Library/Java/Home"; export JAVA_HOME 67 | fi 68 | fi 69 | ;; 70 | esac 71 | 72 | if [ -z "$JAVA_HOME" ] ; then 73 | if [ -r /etc/gentoo-release ] ; then 74 | JAVA_HOME=$(java-config --jre-home) 75 | fi 76 | fi 77 | 78 | # For Cygwin, ensure paths are in UNIX format before anything is touched 79 | if $cygwin ; then 80 | [ -n "$JAVA_HOME" ] && 81 | JAVA_HOME=$(cygpath --unix "$JAVA_HOME") 82 | [ -n "$CLASSPATH" ] && 83 | CLASSPATH=$(cygpath --path --unix "$CLASSPATH") 84 | fi 85 | 86 | # For Mingw, ensure paths are in UNIX format before anything is touched 87 | if $mingw ; then 88 | [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && 89 | JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" 90 | fi 91 | 92 | if [ -z "$JAVA_HOME" ]; then 93 | javaExecutable="$(which javac)" 94 | if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then 95 | # readlink(1) is not available as standard on Solaris 10. 96 | readLink=$(which readlink) 97 | if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then 98 | if $darwin ; then 99 | javaHome="$(dirname "\"$javaExecutable\"")" 100 | javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" 101 | else 102 | javaExecutable="$(readlink -f "\"$javaExecutable\"")" 103 | fi 104 | javaHome="$(dirname "\"$javaExecutable\"")" 105 | javaHome=$(expr "$javaHome" : '\(.*\)/bin') 106 | JAVA_HOME="$javaHome" 107 | export JAVA_HOME 108 | fi 109 | fi 110 | fi 111 | 112 | if [ -z "$JAVACMD" ] ; then 113 | if [ -n "$JAVA_HOME" ] ; then 114 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 115 | # IBM's JDK on AIX uses strange locations for the executables 116 | JAVACMD="$JAVA_HOME/jre/sh/java" 117 | else 118 | JAVACMD="$JAVA_HOME/bin/java" 119 | fi 120 | else 121 | JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" 122 | fi 123 | fi 124 | 125 | if [ ! -x "$JAVACMD" ] ; then 126 | echo "Error: JAVA_HOME is not defined correctly." >&2 127 | echo " We cannot execute $JAVACMD" >&2 128 | exit 1 129 | fi 130 | 131 | if [ -z "$JAVA_HOME" ] ; then 132 | echo "Warning: JAVA_HOME environment variable is not set." 133 | fi 134 | 135 | # traverses directory structure from process work directory to filesystem root 136 | # first directory with .mvn subdirectory is considered project base directory 137 | find_maven_basedir() { 138 | if [ -z "$1" ] 139 | then 140 | echo "Path not specified to find_maven_basedir" 141 | return 1 142 | fi 143 | 144 | basedir="$1" 145 | wdir="$1" 146 | while [ "$wdir" != '/' ] ; do 147 | if [ -d "$wdir"/.mvn ] ; then 148 | basedir=$wdir 149 | break 150 | fi 151 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 152 | if [ -d "${wdir}" ]; then 153 | wdir=$(cd "$wdir/.." || exit 1; pwd) 154 | fi 155 | # end of workaround 156 | done 157 | printf '%s' "$(cd "$basedir" || exit 1; pwd)" 158 | } 159 | 160 | # concatenates all lines of a file 161 | concat_lines() { 162 | if [ -f "$1" ]; then 163 | # Remove \r in case we run on Windows within Git Bash 164 | # and check out the repository with auto CRLF management 165 | # enabled. Otherwise, we may read lines that are delimited with 166 | # \r\n and produce $'-Xarg\r' rather than -Xarg due to word 167 | # splitting rules. 168 | tr -s '\r\n' ' ' < "$1" 169 | fi 170 | } 171 | 172 | log() { 173 | if [ "$MVNW_VERBOSE" = true ]; then 174 | printf '%s\n' "$1" 175 | fi 176 | } 177 | 178 | BASE_DIR=$(find_maven_basedir "$(dirname "$0")") 179 | if [ -z "$BASE_DIR" ]; then 180 | exit 1; 181 | fi 182 | 183 | MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR 184 | log "$MAVEN_PROJECTBASEDIR" 185 | 186 | ########################################################################################## 187 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 188 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 189 | ########################################################################################## 190 | wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" 191 | if [ -r "$wrapperJarPath" ]; then 192 | log "Found $wrapperJarPath" 193 | else 194 | log "Couldn't find $wrapperJarPath, downloading it ..." 195 | 196 | if [ -n "$MVNW_REPOURL" ]; then 197 | wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 198 | else 199 | wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 200 | fi 201 | while IFS="=" read -r key value; do 202 | # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) 203 | safeValue=$(echo "$value" | tr -d '\r') 204 | case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; 205 | esac 206 | done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" 207 | log "Downloading from: $wrapperUrl" 208 | 209 | if $cygwin; then 210 | wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") 211 | fi 212 | 213 | if command -v wget > /dev/null; then 214 | log "Found wget ... using wget" 215 | [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" 216 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 217 | wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 218 | else 219 | wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 220 | fi 221 | elif command -v curl > /dev/null; then 222 | log "Found curl ... using curl" 223 | [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" 224 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 225 | curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" 226 | else 227 | curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" 228 | fi 229 | else 230 | log "Falling back to using Java to download" 231 | javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" 232 | javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" 233 | # For Cygwin, switch paths to Windows format before running javac 234 | if $cygwin; then 235 | javaSource=$(cygpath --path --windows "$javaSource") 236 | javaClass=$(cygpath --path --windows "$javaClass") 237 | fi 238 | if [ -e "$javaSource" ]; then 239 | if [ ! -e "$javaClass" ]; then 240 | log " - Compiling MavenWrapperDownloader.java ..." 241 | ("$JAVA_HOME/bin/javac" "$javaSource") 242 | fi 243 | if [ -e "$javaClass" ]; then 244 | log " - Running MavenWrapperDownloader.java ..." 245 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" 246 | fi 247 | fi 248 | fi 249 | fi 250 | ########################################################################################## 251 | # End of extension 252 | ########################################################################################## 253 | 254 | # If specified, validate the SHA-256 sum of the Maven wrapper jar file 255 | wrapperSha256Sum="" 256 | while IFS="=" read -r key value; do 257 | case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; 258 | esac 259 | done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" 260 | if [ -n "$wrapperSha256Sum" ]; then 261 | wrapperSha256Result=false 262 | if command -v sha256sum > /dev/null; then 263 | if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then 264 | wrapperSha256Result=true 265 | fi 266 | elif command -v shasum > /dev/null; then 267 | if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then 268 | wrapperSha256Result=true 269 | fi 270 | else 271 | echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." 272 | echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." 273 | exit 1 274 | fi 275 | if [ $wrapperSha256Result = false ]; then 276 | echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 277 | echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 278 | echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 279 | exit 1 280 | fi 281 | fi 282 | 283 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 284 | 285 | # For Cygwin, switch paths to Windows format before running java 286 | if $cygwin; then 287 | [ -n "$JAVA_HOME" ] && 288 | JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") 289 | [ -n "$CLASSPATH" ] && 290 | CLASSPATH=$(cygpath --path --windows "$CLASSPATH") 291 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 292 | MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") 293 | fi 294 | 295 | # Provide a "standardized" way to retrieve the CLI args that will 296 | # work with both Windows and non-Windows executions. 297 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" 298 | export MAVEN_CMD_LINE_ARGS 299 | 300 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 301 | 302 | # shellcheck disable=SC2086 # safe args 303 | exec "$JAVACMD" \ 304 | $MAVEN_OPTS \ 305 | $MAVEN_DEBUG_OPTS \ 306 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 307 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 308 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 309 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Apache Maven Wrapper startup batch script, version 3.2.0 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 28 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 29 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 30 | @REM e.g. to debug Maven itself, use 31 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 32 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 33 | @REM ---------------------------------------------------------------------------- 34 | 35 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 36 | @echo off 37 | @REM set title of command window 38 | title %0 39 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 40 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 41 | 42 | @REM set %HOME% to equivalent of $HOME 43 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 44 | 45 | @REM Execute a user defined script before this one 46 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 47 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 48 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 49 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 50 | :skipRcPre 51 | 52 | @setlocal 53 | 54 | set ERROR_CODE=0 55 | 56 | @REM To isolate internal variables from possible post scripts, we use another setlocal 57 | @setlocal 58 | 59 | @REM ==== START VALIDATION ==== 60 | if not "%JAVA_HOME%" == "" goto OkJHome 61 | 62 | echo. 63 | echo Error: JAVA_HOME not found in your environment. >&2 64 | echo Please set the JAVA_HOME variable in your environment to match the >&2 65 | echo location of your Java installation. >&2 66 | echo. 67 | goto error 68 | 69 | :OkJHome 70 | if exist "%JAVA_HOME%\bin\java.exe" goto init 71 | 72 | echo. 73 | echo Error: JAVA_HOME is set to an invalid directory. >&2 74 | echo JAVA_HOME = "%JAVA_HOME%" >&2 75 | echo Please set the JAVA_HOME variable in your environment to match the >&2 76 | echo location of your Java installation. >&2 77 | echo. 78 | goto error 79 | 80 | @REM ==== END VALIDATION ==== 81 | 82 | :init 83 | 84 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 85 | @REM Fallback to current working directory if not found. 86 | 87 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 88 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 89 | 90 | set EXEC_DIR=%CD% 91 | set WDIR=%EXEC_DIR% 92 | :findBaseDir 93 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 94 | cd .. 95 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 96 | set WDIR=%CD% 97 | goto findBaseDir 98 | 99 | :baseDirFound 100 | set MAVEN_PROJECTBASEDIR=%WDIR% 101 | cd "%EXEC_DIR%" 102 | goto endDetectBaseDir 103 | 104 | :baseDirNotFound 105 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 106 | cd "%EXEC_DIR%" 107 | 108 | :endDetectBaseDir 109 | 110 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 111 | 112 | @setlocal EnableExtensions EnableDelayedExpansion 113 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 114 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 115 | 116 | :endReadAdditionalConfig 117 | 118 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 123 | 124 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 125 | IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B 126 | ) 127 | 128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 130 | if exist %WRAPPER_JAR% ( 131 | if "%MVNW_VERBOSE%" == "true" ( 132 | echo Found %WRAPPER_JAR% 133 | ) 134 | ) else ( 135 | if not "%MVNW_REPOURL%" == "" ( 136 | SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 137 | ) 138 | if "%MVNW_VERBOSE%" == "true" ( 139 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 140 | echo Downloading from: %WRAPPER_URL% 141 | ) 142 | 143 | powershell -Command "&{"^ 144 | "$webclient = new-object System.Net.WebClient;"^ 145 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 146 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 147 | "}"^ 148 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ 149 | "}" 150 | if "%MVNW_VERBOSE%" == "true" ( 151 | echo Finished downloading %WRAPPER_JAR% 152 | ) 153 | ) 154 | @REM End of extension 155 | 156 | @REM If specified, validate the SHA-256 sum of the Maven wrapper jar file 157 | SET WRAPPER_SHA_256_SUM="" 158 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 159 | IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B 160 | ) 161 | IF NOT %WRAPPER_SHA_256_SUM%=="" ( 162 | powershell -Command "&{"^ 163 | "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ 164 | "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ 165 | " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ 166 | " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ 167 | " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ 168 | " exit 1;"^ 169 | "}"^ 170 | "}" 171 | if ERRORLEVEL 1 goto error 172 | ) 173 | 174 | @REM Provide a "standardized" way to retrieve the CLI args that will 175 | @REM work with both Windows and non-Windows executions. 176 | set MAVEN_CMD_LINE_ARGS=%* 177 | 178 | %MAVEN_JAVA_EXE% ^ 179 | %JVM_CONFIG_MAVEN_PROPS% ^ 180 | %MAVEN_OPTS% ^ 181 | %MAVEN_DEBUG_OPTS% ^ 182 | -classpath %WRAPPER_JAR% ^ 183 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 184 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 185 | if ERRORLEVEL 1 goto error 186 | goto end 187 | 188 | :error 189 | set ERROR_CODE=1 190 | 191 | :end 192 | @endlocal & set ERROR_CODE=%ERROR_CODE% 193 | 194 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 195 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 196 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 197 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 198 | :skipRcPost 199 | 200 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 201 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 202 | 203 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 204 | 205 | cmd /C exit /B %ERROR_CODE% 206 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.springframework.boot 6 | spring-boot-starter-parent 7 | 3.4.3 8 | 9 | 10 | io.quarkus 11 | spring-to-quarkus-todo 12 | 0.0.1-SNAPSHOT 13 | spring-to-quarkus-todo 14 | A Todo application that builds and runs as either Spring Boot and Quarkus! 15 | 16 | 17 17 | 17 18 | 17 19 | 3.14.0 20 | 3.5.2 21 | 22 | 23 | 24 | 25 | org.testcontainers 26 | testcontainers-bom 27 | 1.20.6 28 | pom 29 | import 30 | 31 | 32 | 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-starter-web 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter-data-jpa 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-starter-validation 45 | 46 | 47 | org.springdoc 48 | springdoc-openapi-starter-webmvc-ui 49 | 2.8.5 50 | 51 | 52 | org.springframework.boot 53 | spring-boot-starter-actuator 54 | 55 | 56 | io.micrometer 57 | micrometer-registry-prometheus 58 | 59 | 60 | org.postgresql 61 | postgresql 62 | runtime 63 | 64 | 65 | org.springframework.boot 66 | spring-boot-devtools 67 | true 68 | 69 | 70 | org.springframework.boot 71 | spring-boot-starter-test 72 | test 73 | 74 | 75 | org.assertj 76 | assertj-core 77 | test 78 | 79 | 80 | io.rest-assured 81 | spring-mock-mvc 82 | 5.5.1 83 | test 84 | 85 | 86 | org.testcontainers 87 | junit-jupiter 88 | test 89 | 90 | 91 | org.testcontainers 92 | postgresql 93 | test 94 | 95 | 96 | 97 | 98 | 99 | org.springframework.boot 100 | spring-boot-maven-plugin 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /src/main/java/com/acme/todo/TodoApplication.java: -------------------------------------------------------------------------------- 1 | package com.acme.todo; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class TodoApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(TodoApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/acme/todo/domain/TodoEntity.java: -------------------------------------------------------------------------------- 1 | package com.acme.todo.domain; 2 | 3 | import jakarta.persistence.Entity; 4 | import jakarta.persistence.GeneratedValue; 5 | import jakarta.persistence.Id; 6 | import jakarta.persistence.Table; 7 | 8 | @Entity 9 | @Table(name = "todo") 10 | public class TodoEntity { 11 | @Id 12 | @GeneratedValue 13 | private Long id; 14 | private String title; 15 | private boolean completed = false; 16 | 17 | public TodoEntity() { 18 | } 19 | 20 | public TodoEntity(Long id, String title, boolean completed) { 21 | this.id = id; 22 | this.title = title; 23 | this.completed = completed; 24 | } 25 | 26 | public Long getId() { 27 | return this.id; 28 | } 29 | 30 | public void setId(Long id) { 31 | this.id = id; 32 | } 33 | 34 | public String getTitle() { 35 | return this.title; 36 | } 37 | 38 | public void setTitle(String title) { 39 | this.title = title; 40 | } 41 | 42 | public boolean getCompleted() { 43 | return this.completed; 44 | } 45 | 46 | public void setCompleted(boolean completed) { 47 | this.completed = completed; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/acme/todo/repository/TodoRepository.java: -------------------------------------------------------------------------------- 1 | package com.acme.todo.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import org.springframework.stereotype.Repository; 5 | 6 | import com.acme.todo.domain.TodoEntity; 7 | 8 | @Repository 9 | public interface TodoRepository extends JpaRepository {} 10 | -------------------------------------------------------------------------------- /src/main/java/com/acme/todo/rest/TodoController.java: -------------------------------------------------------------------------------- 1 | package com.acme.todo.rest; 2 | 3 | import java.util.List; 4 | 5 | import jakarta.transaction.Transactional; 6 | 7 | import org.springframework.data.domain.Sort; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.web.bind.annotation.DeleteMapping; 10 | import org.springframework.web.bind.annotation.GetMapping; 11 | import org.springframework.web.bind.annotation.PathVariable; 12 | import org.springframework.web.bind.annotation.PostMapping; 13 | import org.springframework.web.bind.annotation.PutMapping; 14 | import org.springframework.web.bind.annotation.RequestBody; 15 | import org.springframework.web.bind.annotation.RequestMapping; 16 | import org.springframework.web.bind.annotation.ResponseStatus; 17 | import org.springframework.web.bind.annotation.RestController; 18 | 19 | import com.acme.todo.domain.TodoEntity; 20 | import com.acme.todo.repository.TodoRepository; 21 | 22 | @RestController 23 | @RequestMapping("/todo") 24 | public class TodoController { 25 | private final TodoRepository todoRepository; 26 | 27 | public TodoController(TodoRepository todoRepository) { 28 | this.todoRepository = todoRepository; 29 | } 30 | 31 | @GetMapping 32 | public List findAll() { 33 | return this.todoRepository.findAll(Sort.by(Sort.Direction.DESC, "id")); 34 | } 35 | 36 | @GetMapping("/{id}") 37 | public TodoEntity findById(@PathVariable("id") Long id) { 38 | return this.todoRepository.findById(id).get(); 39 | } 40 | 41 | @PutMapping 42 | @Transactional 43 | @ResponseStatus(HttpStatus.NO_CONTENT) 44 | public void update(@RequestBody TodoEntity resource) { 45 | this.todoRepository.save(resource); 46 | } 47 | 48 | @PostMapping 49 | @Transactional 50 | public TodoEntity create(@RequestBody TodoEntity resource) { 51 | return this.todoRepository.save(resource); 52 | } 53 | 54 | @DeleteMapping("/{id}") 55 | @Transactional 56 | @ResponseStatus(HttpStatus.NO_CONTENT) 57 | public void delete(@PathVariable("id") Long id) { 58 | this.todoRepository.deleteById(id); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Spring Todo 6 | 7 | 8 | 9 | 10 | 11 |

Spring Todos

12 | 13 |
14 | 15 |
16 | 17 | 25 | 26 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/todo-component.html: -------------------------------------------------------------------------------- 1 | 68 |
69 | 77 |
-------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/todo-component.js: -------------------------------------------------------------------------------- 1 | class TodoComponent extends HTMLElement { 2 | 3 | get id() { 4 | return this.get('value','id'); 5 | } 6 | 7 | set id(id) { 8 | this.set('value','id',id) 9 | } 10 | 11 | get title() { 12 | return this.get('value','title'); 13 | } 14 | 15 | set title(title) { 16 | this.set('value','title',title) 17 | } 18 | 19 | get completed() { 20 | return this.get('checked','completed'); 21 | } 22 | 23 | set completed(val) { 24 | this.set('checked','completed',val) 25 | } 26 | 27 | // Get the property from either the attribute or the form field 28 | get(prop, name) { 29 | const ele = this.form.elements[name]; 30 | if (ele[prop] == '') { 31 | if(this.getAttribute(name) != null) { 32 | ele[prop] = decodeURI(this.getAttribute(name)); 33 | } 34 | } 35 | return ele[prop]; 36 | } 37 | 38 | // Set the property to the attribute and the form field 39 | set(prop, name, val) { 40 | this.form.elements[name][prop] = val; 41 | this.setAttribute(name, encodeURI(val)); 42 | } 43 | 44 | constructor() { 45 | super(); 46 | this.attachShadow({mode: 'open'}); 47 | 48 | fetch('./todo-component.html') 49 | .then(stream => stream.text()) 50 | .then(html => { 51 | const tmpl = document.createElement('template'); 52 | tmpl.innerHTML = html; 53 | this.shadowRoot.appendChild(tmpl.content.cloneNode(true)); 54 | this.initialize(); 55 | }); 56 | } 57 | 58 | initialize() { 59 | const todo = this; 60 | this.form = this.shadowRoot.querySelector('form'); 61 | 62 | // Initialize the form fields by calling the getters 63 | this.id; 64 | this.title; 65 | this.completed; 66 | 67 | this.form.elements['delete'].addEventListener('click', function(e) { 68 | fetch('/todo/' + todo.id, { 69 | method: 'DELETE' 70 | }) 71 | .then(response => { 72 | todo.remove(); 73 | }) 74 | .catch(err => console.error(err)); 75 | }); 76 | 77 | this.form.elements['title'].addEventListener('keyup', function(e) { 78 | e.preventDefault(); 79 | if (e.keyCode === 13) { 80 | new FormData(todo.form); 81 | e.target.blur(); 82 | } 83 | }); 84 | 85 | this.form.elements['completed'].addEventListener('click', (e) => { 86 | new FormData(todo.form); 87 | }); 88 | 89 | this.form.addEventListener('submit', (e) => { 90 | e.preventDefault(); 91 | }); 92 | 93 | this.form.addEventListener('formdata', (e) => { 94 | e.preventDefault(); 95 | const data = Object.fromEntries(e.formData); 96 | 97 | todo.title = data.title; 98 | todo.completed = data.completed; 99 | 100 | if(data.id == '') { // New Todo 101 | fetch('/todo', { 102 | method: 'POST', 103 | headers: {'Content-Type': 'application/json'}, 104 | body: JSON.stringify(data) 105 | }) 106 | .then(response => response.json()) 107 | .then(data => { 108 | todo.id = data.id; 109 | console.log('Added: ' + JSON.stringify(data)); 110 | }) 111 | .catch(err => console.error(err)); 112 | 113 | var newTodo = document.createElement('todo-component'); 114 | todo.parentElement.prepend(newTodo); 115 | } 116 | else { // Update Todo 117 | // Convert completed to boolean value 118 | (data.completed == 'on') ? data.completed = true : data.completed = false; 119 | fetch('/todo', { 120 | method: 'PUT', 121 | headers: {'Content-Type': 'application/json'}, 122 | body: JSON.stringify(data) 123 | }) 124 | .catch(err => console.error(err)); 125 | } 126 | }); 127 | } 128 | } 129 | 130 | window.customElements.define('todo-component', TodoComponent); -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/todo.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Raleway:wght@400;700&display=swap'); 2 | 3 | :root { 4 | --main-accent-color: green; 5 | --todo-text-color: white; 6 | } 7 | 8 | body { 9 | background: #222; 10 | font-family: 'Raleway', sans-serif; 11 | color: white; 12 | } 13 | 14 | body > h1 { 15 | text-align: center; 16 | margin: 4rem; 17 | font-size: 2.5rem; 18 | font-weight: bold; 19 | color: var(--main-accent-color); 20 | } 21 | 22 | todo-component { 23 | text-align: center; 24 | margin: 1rem; 25 | } 26 | 27 | footer { 28 | position: fixed; 29 | left: 0; 30 | bottom: 0; 31 | width: 100%; 32 | background-color: var(--main-accent-color); 33 | color: white; 34 | text-align: center; 35 | font-weight: bold; 36 | } 37 | 38 | footer a { 39 | color: white; 40 | text-decoration: none; 41 | } -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/todo.js: -------------------------------------------------------------------------------- 1 | 2 | document.addEventListener("DOMContentLoaded", function(){ 3 | fetch('/todo') 4 | .then(response => response.json()) 5 | .then(arr => { 6 | var todos = document.getElementById("todos"); 7 | 8 | arr.forEach(element => { 9 | var todoEle = document.createElement("todo-component"); 10 | todoEle.setAttribute("id", element.id); 11 | todoEle.setAttribute("title", element.title); 12 | if(element.completed == true) { 13 | todoEle.setAttribute("completed", 'true'); 14 | } 15 | todos.appendChild(todoEle); 16 | }); 17 | }) 18 | .catch(err => console.error(err)); 19 | }); -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # Spring Properties 2 | spring.jpa.hibernate.ddl-auto=create-drop 3 | spring.datasource.url=jdbc:postgresql://localhost:5432/tododb 4 | spring.datasource.username=todo 5 | spring.datasource.password=todo 6 | 7 | springdoc.api-docs.path=/openapi 8 | springdoc.swagger-ui.path=/swagger-ui 9 | 10 | management.endpoints.web.exposure.include=prometheus,health 11 | -------------------------------------------------------------------------------- /src/main/resources/import.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO todo(id, title, completed) VALUES (0, 'My first todo', 'true'); -------------------------------------------------------------------------------- /src/test/java/com/acme/todo/rest/TodoControllerTests.java: -------------------------------------------------------------------------------- 1 | package com.acme.todo.rest; 2 | 3 | import static io.restassured.module.mockmvc.RestAssuredMockMvc.*; 4 | import static org.assertj.core.api.Assertions.assertThat; 5 | import static org.mockito.Mockito.when; 6 | import static org.mockito.Mockito.*; 7 | 8 | import java.util.List; 9 | import java.util.Optional; 10 | 11 | import org.junit.jupiter.api.BeforeEach; 12 | import org.junit.jupiter.api.Test; 13 | 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 16 | import org.springframework.boot.test.context.SpringBootTest; 17 | import org.springframework.boot.test.mock.mockito.MockBean; 18 | import org.springframework.data.domain.Sort; 19 | import org.springframework.test.web.servlet.MockMvc; 20 | 21 | import com.acme.todo.domain.TodoEntity; 22 | import com.acme.todo.repository.TodoRepository; 23 | import io.restassured.http.ContentType; 24 | import io.restassured.module.mockmvc.RestAssuredMockMvc; 25 | 26 | @SpringBootTest 27 | @AutoConfigureMockMvc 28 | class TodoControllerTests { 29 | private static final TodoEntity TODO = new TodoEntity(1L, "Go on vacation", false); 30 | 31 | @Autowired 32 | MockMvc mockMvc; 33 | 34 | @MockBean 35 | TodoRepository todoRepository; 36 | 37 | @BeforeEach 38 | public void beforeEach() { 39 | RestAssuredMockMvc.mockMvc(this.mockMvc); 40 | } 41 | 42 | @Test 43 | void findAll() { 44 | when(this.todoRepository.findAll(any(Sort.class))) 45 | .thenReturn(List.of(TODO)); 46 | 47 | var todos = get("/todo").then() 48 | .statusCode(200) 49 | .contentType(ContentType.JSON) 50 | .extract().body() 51 | .jsonPath().getList(".", TodoEntity.class); 52 | 53 | assertThat(todos) 54 | .singleElement() 55 | .usingRecursiveComparison() 56 | .isEqualTo(TODO); 57 | 58 | verify(this.todoRepository).findAll(any(Sort.class)); 59 | verifyNoMoreInteractions(this.todoRepository); 60 | } 61 | 62 | @Test 63 | void findById() { 64 | when(this.todoRepository.findById(eq(TODO.getId()))) 65 | .thenReturn(Optional.of(TODO)); 66 | 67 | var foundTodo = get("/todo/{id}", String.valueOf(TODO.getId())).then() 68 | .statusCode(200) 69 | .contentType(ContentType.JSON) 70 | .extract().as(TodoEntity.class); 71 | 72 | assertThat(foundTodo) 73 | .isNotNull() 74 | .usingRecursiveComparison() 75 | .isEqualTo(TODO); 76 | 77 | verify(this.todoRepository).findById(eq(TODO.getId())); 78 | verifyNoMoreInteractions(this.todoRepository); 79 | } 80 | 81 | @Test 82 | void update() { 83 | when(this.todoRepository.save(any(TodoEntity.class))) 84 | .thenReturn(TODO); 85 | 86 | given() 87 | .body(TODO) 88 | .contentType(ContentType.JSON) 89 | .put("/todo").then() 90 | .statusCode(204); 91 | 92 | verify(this.todoRepository).save(any(TodoEntity.class)); 93 | verifyNoMoreInteractions(this.todoRepository); 94 | } 95 | 96 | @Test 97 | void create() { 98 | when(this.todoRepository.save(any(TodoEntity.class))) 99 | .thenReturn(TODO); 100 | 101 | var createdTodo = given() 102 | .body(TODO) 103 | .contentType(ContentType.JSON) 104 | .post("/todo").then() 105 | .statusCode(200) 106 | .contentType(ContentType.JSON) 107 | .extract().as(TodoEntity.class); 108 | 109 | assertThat(createdTodo) 110 | .isNotNull() 111 | .usingRecursiveComparison() 112 | .isEqualTo(TODO); 113 | 114 | verify(this.todoRepository).save(any(TodoEntity.class)); 115 | verifyNoMoreInteractions(this.todoRepository); 116 | } 117 | 118 | @Test 119 | void delete() { 120 | doNothing() 121 | .when(this.todoRepository) 122 | .deleteById(eq(TODO.getId())); 123 | 124 | given().delete("/todo/{id}", String.valueOf(TODO.getId())).then() 125 | .statusCode(204); 126 | 127 | verify(this.todoRepository).deleteById(eq(TODO.getId())); 128 | verifyNoMoreInteractions(this.todoRepository); 129 | } 130 | } -------------------------------------------------------------------------------- /src/test/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=jdbc:tc:postgresql:17:///tododb 2 | spring.jpa.hibernate.ddl-auto=create-drop 3 | spring.jpa.defer-datasource-initialization=true 4 | spring.sql.init.mode=always --------------------------------------------------------------------------------