diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..2ba68e2
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,25 @@
+name: Generator SpringBoot
+on:
+ push:
+ branches:
+ - '**'
+jobs:
+ build:
+ name: npm test
+ runs-on: ${{ matrix.os }}
+ timeout-minutes: 20
+ strategy:
+ fail-fast: false
+ matrix:
+ node_version: [18.x, 20.x]
+ os: [ubuntu-latest, macos-latest, windows-latest]
+ steps:
+ - uses: actions/checkout@v4
+ - name: Use Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ matrix.node_version }}
+ - run: npm install
+ - run: npm test
+ env:
+ CI: true
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
new file mode 100644
index 0000000..1c2c2a9
--- /dev/null
+++ b/.github/workflows/docs.yml
@@ -0,0 +1,17 @@
+name: Publish Docs via GitHub Pages
+on:
+ push:
+ branches:
+ - main
+permissions:
+ contents: write
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-python@v4
+ with:
+ python-version: 3.x
+ - run: pip install mkdocs-material
+ - run: mkdocs gh-deploy --force
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b6ef21b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+.idea/
+*.iml
+.DS_Store
+node_modules/
+/testapp
+/myservice/
+logs/
+generators/server/templates/gradle/.gradle/
diff --git a/.gitpod.Dockerfile b/.gitpod.Dockerfile
new file mode 100644
index 0000000..dfd305a
--- /dev/null
+++ b/.gitpod.Dockerfile
@@ -0,0 +1,7 @@
+FROM gitpod/workspace-full
+
+USER gitpod
+
+RUN bash -c ". /home/gitpod/.sdkman/bin/sdkman-init.sh \
+ && sdk install java 22.3.r17-nik \
+ && sdk default java 22.3.r17-nik"
\ No newline at end of file
diff --git a/.gitpod.yml b/.gitpod.yml
new file mode 100644
index 0000000..b133e93
--- /dev/null
+++ b/.gitpod.yml
@@ -0,0 +1,13 @@
+# This configuration file was automatically generated by Gitpod.
+# Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file)
+# and commit this file to your remote git repository to share the goodness with others.
+
+image:
+ file: .gitpod.Dockerfile
+
+tasks:
+ - before: npm install -g yo
+ init: npm install
+
+
+
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..0be1c0c
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,4 @@
+{
+ "java.configuration.updateBuildConfiguration": "automatic",
+ "java.compile.nullAnalysis.mode": "automatic"
+}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..496364e
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,68 @@
+# Change Log
+
+## Version 0.1.5
+* Upgrade Spring Boot version to 3.1.5
+* Upgraded Maven and Gradle versions
+* Implemented best practices to not expose entity
+* Upgraded generator dependencies (chai, sinon, etc)
+* Supports running application with Java 21 and Spring Boot 3.2.0
+
+## Version 0.1.4
+* Upgrade Spring Boot version to 3.1.3
+* Upgraded Maven and Gradle versions
+* Refine Testcontainers configuration
+* Simplify LocalStack configuration
+* Removed H2 database config
+
+## Version 0.1.3
+* Upgrade Spring Boot version to 3.1.1
+* Introduced Testcontainers support for dev mode
+* Fixed LocalStack configuration issues
+* Upgraded generator dependencies (chai, sinon, etc)
+* Upgraded Maven and Gradle versions
+* Updated googleJavaFormat configuration.
+
+## Version 0.1.2
+* Fixes issue with mysql and mariadb when flyway is selected ([#58](https://github.com/sivaprasadreddy/generator-springboot/issues/58))
+* Support Mariadb Sequences ([#59](https://github.com/sivaprasadreddy/generator-springboot/issues/59))
+* Support other liquibase formats ([#69](https://github.com/sivaprasadreddy/generator-springboot/issues/69))
+* Upgraded SpringBoot to 3.0.2 and other library versions
+
+## Version 0.1.1
+* Upgraded SpringBoot to 3.0.0 and other library versions
+* Upgraded AWS to 3.0.0-M3, compatible version with SpringBoot 3 which uses AWS 2.0 API
+* Tweaked code to get All entries from datasource using pagination
+* Supporting developing application in VSCode
+* Enhanced support for logback encoder when elk stack is selected
+* Fixes issue while generating api and tables when `tablename` contains camelCase([#47](https://github.com/sivaprasadreddy/generator-springboot/issues/47))
+* Upgraded liquibase configuration to use Out of the Box format and location
+
+## Version 0.1.0
+* Upgraded SpringBoot to 2.7.4 and other library versions
+* Fixed code formatting
+* Fixed Flyway with MySQL and MariaDB issue
+
+## Version 0.0.10
+* Upgraded SpringBoot to 2.6.7 and library versions
+* Updated Spring Cloud AWS setup to use new https://awspring.io/ based configuration
+* Removed `springfox-boot-starter` and used `springdoc-openapi-ui`
+* Added google-java-format support
+* Upgraded plugins versions
+* Removed Checkstyle, PMD plugins
+
+## Version 0.0.8
+* Configured Checkstyle, PMD, SonarQube, google-java-format plugins
+* Added Localstack autoconfiguration support
+
+## Version 0.0.7
+* Removed support for generation of `config-server` and `service-registry`
+* Updated SpringBoot and other libraries version
+
+## Version 0.0.6
+* Updated to use testcontainers-spring-boot https://github.com/testcontainers/testcontainers-spring-boot
+* Generate Zipkin docker-compose file when Distributed Tracing is selected
+* Fixed Flyway/Liquibase db migration script generation issue
+* Added tests for sanity check
+
+## Version 0.0.5
+* Added support for generating docker-compose yml files for application, ELK, Prometheus, Grafana
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..2da6295
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 K. Siva Prasad Reddy
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a1690d2
--- /dev/null
+++ b/README.md
@@ -0,0 +1,223 @@
+# generator-springboot
+The Yeoman generator for generating Spring Boot microservices.
+
+## Prerequisites
+* Node 18+
+* JDK 17+
+
+## Installation
+```shell
+$ npm install -g yo
+$ npm install -g generator-springboot
+```
+
+## How to use?
+Run the following command and answer the questions:
+
+```shell
+$ yo springboot
+```
+
+## Features
+The generator-springboot generates a Spring Boot application with the following features configured:
+
+* Spring Boot project with Maven and Gradle support
+* Spring Data JPA integration with an option to select databases like MySQL, Postgresql, MariaDB.
+* Flyway and Liquibase database migration support.
+* Spring Cloud AWS support with LocalStack configuration.
+* CORS configuration
+* Swagger UI Integration
+* SpringBoot Actuator configuration
+* Testcontainers based Testing and Local dev mode setup
+* DockerCompose configuration for application, ELK, Prometheus, Grafana
+* GitHub Actions Configuration
+* Dockerfile
+* Jenkinsfile
+* SonarQube and JaCoCo based static analysis tools configuration
+* Code formatting using Spotless and google-java-format
+* JUnit 5
+
+### Generate a SpringBoot Microservice
+After installing the `generator-springboot`, you can generate a new Spring Boot application as follows:
+
+```shell
+$ yo springboot
+Generating SpringBoot Application
+? What is the application name? blog
+? What is the default package name? com.sivalabs.blog
+? Which type of database you want to use? Postgresql
+? Which type of database migration tool you want to use? FlywayDB
+? Select the features you want? ELK Docker configuration, Prometheus, Grafana Docker configuration, Localstack Docker configuration
+? Which build tool do you want to use? Maven
+ force blog/.yo-rc.json
+ create blog/mvnw
+ create blog/mvnw.cmd
+ create blog/.gitignore
+ create blog/.mvn/wrapper/maven-wrapper.jar
+ create blog/.mvn/wrapper/maven-wrapper.properties
+ create blog/pom.xml
+ create blog/Dockerfile
+ create blog/Jenkinsfile
+ create blog/lombok.config
+ create blog/sonar-project.properties
+ create blog/README.md
+ create blog/.github/workflows/maven.yml
+ create blog/src/main/resources/db/migration/postgresql/V1__01_init.sql
+ create blog/docker/docker-compose.yml
+ create blog/docker/docker-compose-app.yml
+ create blog/docker/docker-compose-monitoring.yml
+ create blog/config/prometheus/prometheus.yml
+ create blog/config/grafana/provisioning/dashboards/basic-dashboard.json
+ create blog/config/grafana/provisioning/dashboards/dashboard.yml
+ create blog/config/grafana/provisioning/dashboards/jvm-micrometer_rev10.json
+ create blog/config/grafana/provisioning/datasources/datasource.yml
+ create blog/docker/docker-compose-elk.yml
+ create blog/config/elk/logstash.conf
+ create blog/.localstack/01_init.sh
+ create blog/src/main/java/com/sivalabs/blog/Application.java
+ create blog/src/main/java/com/sivalabs/blog/config/WebMvcConfig.java
+ create blog/src/main/java/com/sivalabs/blog/config/SwaggerConfig.java
+ create blog/src/main/java/com/sivalabs/blog/config/ApplicationProperties.java
+ create blog/src/main/java/com/sivalabs/blog/config/Initializer.java
+ create blog/src/main/java/com/sivalabs/blog/config/GlobalExceptionHandler.java
+ create blog/src/main/java/com/sivalabs/blog/config/logging/Loggable.java
+ create blog/src/main/java/com/sivalabs/blog/config/logging/LoggingAspect.java
+ create blog/src/main/java/com/sivalabs/blog/exception/ResourceNotFoundException.java
+ create blog/src/main/java/com/sivalabs/blog/model/response/PagedResult.java
+ create blog/src/main/java/com/sivalabs/blog/utils/AppConstants.java
+ create blog/src/main/resources/application.properties
+ create blog/src/main/resources/application-local.properties
+ create blog/src/main/resources/logback-spring.xml
+ create blog/src/test/java/com/sivalabs/blog/ApplicationIntegrationTest.java
+ create blog/src/test/java/com/sivalabs/blog/SchemaValidationTest.java
+ create blog/src/test/java/com/sivalabs/blog/common/ContainersConfig.java
+ create blog/src/test/java/com/sivalabs/blog/common/AbstractIntegrationTest.java
+ create blog/src/test/java/com/sivalabs/blog/TestApplication.java
+ create blog/src/test/java/com/sivalabs/blog/SqsListenerIntegrationTest.java
+ create blog/src/test/resources/application-test.properties
+ create blog/src/test/resources/logback-test.xml
+
+No change to package.json was detected. No package manager install will be executed.
+Picked up JAVA_TOOL_OPTIONS: -Xmx3489m
+[INFO] Scanning for projects...
+[INFO]
+[INFO] -----------------------< com.sivalabs.blog:blog >-----------------------
+[INFO] Building blog 0.0.1-SNAPSHOT
+[INFO] from pom.xml
+[INFO] --------------------------------[ jar ]---------------------------------
+[INFO]
+[INFO] --- spotless:2.39.0:apply (default-cli) @ blog ---
+[INFO] Index file does not exist. Fallback to an empty index
+[INFO] Writing clean file: /workspace/generator-springboot/blog/src/main/java/com/sivalabs/blog/config/SwaggerConfig.java
+[INFO] Writing clean file: /workspace/generator-springboot/blog/src/main/java/com/sivalabs/blog/config/GlobalExceptionHandler.java
+[INFO] Writing clean file: /workspace/generator-springboot/blog/src/main/java/com/sivalabs/blog/config/logging/LoggingAspect.java
+[INFO] Writing clean file: /workspace/generator-springboot/blog/src/main/java/com/sivalabs/blog/exception/ResourceNotFoundException.java
+[INFO] Writing clean file: /workspace/generator-springboot/blog/src/main/java/com/sivalabs/blog/model/response/PagedResult.java
+[INFO] Writing clean file: /workspace/generator-springboot/blog/src/test/java/com/sivalabs/blog/common/ContainersConfig.java
+[INFO] Writing clean file: /workspace/generator-springboot/blog/src/test/java/com/sivalabs/blog/common/AbstractIntegrationTest.java
+[INFO] Writing clean file: /workspace/generator-springboot/blog/src/test/java/com/sivalabs/blog/SchemaValidationTest.java
+[INFO] Writing clean file: /workspace/generator-springboot/blog/src/test/java/com/sivalabs/blog/TestApplication.java
+[INFO] Writing clean file: /workspace/generator-springboot/blog/src/test/java/com/sivalabs/blog/SqsListenerIntegrationTest.java
+[INFO] Spotless.Java is keeping 17 files clean - 10 were changed to be clean, 7 were already clean, 0 were skipped because caching determined they were already clean
+[INFO] ------------------------------------------------------------------------
+[INFO] BUILD SUCCESS
+[INFO] ------------------------------------------------------------------------
+[INFO] Total time: 4.454 s
+[INFO] Finished at: 2023-10-25T16:57:22Z
+[INFO] ------------------------------------------------------------------------
+==========================================
+Your application is generated successfully
+ cd blog
+ > ./mvnw spring-boot:run
+==========================================
+
+```
+
+### Generate REST API with CRUD operations
+You can generate REST API with CRUD operation using the following command:
+
+**IMPORTANT:** You should run the following command from within the generated project folder.
+
+```shell
+$ cd blog
+$ yo springboot:controller Customer --base-path /api/customers
+```
+
+This sub-generator will generate the following:
+
+* JPA entity
+* Spring Data JPA Repository
+* Service
+* Spring MVC REST Controller with CRUD operations
+* Unit and Integration Tests for REST Controller
+* Flyway or Liquibase migration to create table
+
+```shell
+$ yo springboot:controller Customer --base-path /api/customers
+Generating JPA entity, repository, service and controller
+EntityName: Customer, basePath: /api/customers
+ force .yo-rc.json
+ create src/main/java/com/sivalabs/blog/entities/Customer.java
+ create src/main/java/com/sivalabs/blog/exception/CustomerNotFoundException.java
+ create src/main/java/com/sivalabs/blog/mapper/CustomerMapper.java
+ create src/main/java/com/sivalabs/blog/model/query/FindCustomersQuery.java
+ create src/main/java/com/sivalabs/blog/model/request/CustomerRequest.java
+ create src/main/java/com/sivalabs/blog/model/response/CustomerResponse.java
+ create src/main/java/com/sivalabs/blog/repositories/CustomerRepository.java
+ create src/main/java/com/sivalabs/blog/services/CustomerService.java
+ create src/main/java/com/sivalabs/blog/web/controllers/CustomerController.java
+ create src/test/java/com/sivalabs/blog/web/controllers/CustomerControllerTest.java
+ create src/test/java/com/sivalabs/blog/web/controllers/CustomerControllerIT.java
+ create src/test/java/com/sivalabs/blog/services/CustomerServiceTest.java
+ create src/main/resources/db/migration/postgresql/V2__create_customers_table.sql
+
+No change to package.json was detected. No package manager install will be executed.
+Picked up JAVA_TOOL_OPTIONS: -Xmx3489m
+[INFO] Scanning for projects...
+[INFO]
+[INFO] -----------------------< com.sivalabs.blog:blog >-----------------------
+[INFO] Building blog 0.0.1-SNAPSHOT
+[INFO] from pom.xml
+[INFO] --------------------------------[ jar ]---------------------------------
+[INFO]
+[INFO] --- spotless:2.39.0:apply (default-cli) @ blog ---
+[INFO] Writing clean file: /workspace/generator-springboot/blog/src/main/java/com/sivalabs/blog/exception/CustomerNotFoundException.java
+[INFO] Writing clean file: /workspace/generator-springboot/blog/src/main/java/com/sivalabs/blog/model/query/FindCustomersQuery.java
+[INFO] Writing clean file: /workspace/generator-springboot/blog/src/main/java/com/sivalabs/blog/model/request/CustomerRequest.java
+[INFO] Writing clean file: /workspace/generator-springboot/blog/src/main/java/com/sivalabs/blog/entities/Customer.java
+[INFO] Writing clean file: /workspace/generator-springboot/blog/src/main/java/com/sivalabs/blog/mapper/CustomerMapper.java
+[INFO] Writing clean file: /workspace/generator-springboot/blog/src/main/java/com/sivalabs/blog/services/CustomerService.java
+[INFO] Writing clean file: /workspace/generator-springboot/blog/src/main/java/com/sivalabs/blog/web/controllers/CustomerController.java
+[INFO] Writing clean file: /workspace/generator-springboot/blog/src/test/java/com/sivalabs/blog/web/controllers/CustomerControllerIT.java
+[INFO] Writing clean file: /workspace/generator-springboot/blog/src/test/java/com/sivalabs/blog/web/controllers/CustomerControllerTest.java
+[INFO] Writing clean file: /workspace/generator-springboot/blog/src/test/java/com/sivalabs/blog/services/CustomerServiceTest.java
+[INFO] Spotless.Java is keeping 28 files clean - 10 were changed to be clean, 1 were already clean, 17 were skipped because caching determined they were already clean
+[INFO] ------------------------------------------------------------------------
+[INFO] BUILD SUCCESS
+[INFO] ------------------------------------------------------------------------
+[INFO] Total time: 2.246 s
+[INFO] Finished at: 2023-10-25T16:59:48Z
+[INFO] ------------------------------------------------------------------------
+```
+
+## Local Development Setup
+
+```shell
+$ git clone https://github.com/sivaprasadreddy/generator-springboot.git
+$ cd generator-springboot
+$ npm install -g yo
+$ npm install
+$ npm link
+$ yo springboot
+```
+
+## Releasing a new version
+Before publishing a new release, make sure to update the version number in `package.json` updated.
+
+```shell
+$ npm login
+$ npm publish
+```
+
+## License
+The **generator-springboot** is an Open Source software released under the [MIT Licence](https://opensource.org/license/mit/)
diff --git a/docs/crud-generation.png b/docs/crud-generation.png
new file mode 100644
index 0000000..d15fc83
Binary files /dev/null and b/docs/crud-generation.png differ
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000..d41893a
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,187 @@
+# Generator SpringBoot
+The Yeoman generator for generating Spring Boot microservices.
+
+## Prerequisites
+* Node 16+
+* JDK 17+
+
+## Installation
+```shell
+$ npm install -g yo
+$ npm install -g generator-springboot
+```
+
+## How to use?
+Run the following command and answer the questions:
+
+```shell
+$ yo springboot
+```
+
+## Features
+The generator-springboot generates a Spring Boot application with the following features configured:
+
+* Spring Boot project with Maven and Gradle support
+* Spring Data JPA integration with an option to select databases like MySQL, Postgresql, MariaDB.
+* Flyway and Liquibase database migration support.
+* Spring Cloud AWS support with LocalStack configuration.
+* CORS configuration
+* Swagger UI Integration
+* SpringBoot Actuator configuration
+* Testcontainers based Testing and Local dev mode setup
+* DockerCompose configuration for application, ELK, Prometheus, Grafana
+* GitHub Actions Configuration
+* Dockerfile
+* Jenkinsfile
+* SonarQube and JaCoCo based static analysis tools configuration
+* Code formatting using Spotless and google-java-format
+* JUnit 5
+
+### Generate a SpringBoot Microservice
+After installing the `generator-springboot`, you can generate a new Spring Boot application as follows:
+
+```shell
+$ yo springboot
+Generating SpringBoot Application
+? What is the application name? blog
+? What is the default package name? com.sivalabs.blog
+? Which type of database you want to use? Postgresql
+? Which type of database migration tool you want to use? FlywayDB
+? Select the features you want? ELK Docker configuration, Prometheus, Grafana Docker configuration, Localstack Docker configuration
+? Which build tool do you want to use? Maven
+ force blog/.yo-rc.json
+ create blog/mvnw
+ create blog/mvnw.cmd
+ create blog/.gitignore
+ create blog/.mvn/wrapper/maven-wrapper.jar
+ create blog/.mvn/wrapper/maven-wrapper.properties
+ create blog/pom.xml
+ create blog/Dockerfile
+ create blog/Jenkinsfile
+ create blog/lombok.config
+ create blog/sonar-project.properties
+ create blog/README.md
+ create blog/.github/workflows/maven.yml
+ create blog/src/main/resources/db/migration/postgresql/V1__01_init.sql
+ create blog/docker/docker-compose.yml
+ create blog/docker/docker-compose-app.yml
+ create blog/docker/docker-compose-monitoring.yml
+ create blog/config/prometheus/prometheus.yml
+ create blog/config/grafana/provisioning/dashboards/basic-dashboard.json
+ create blog/config/grafana/provisioning/dashboards/dashboard.yml
+ create blog/config/grafana/provisioning/dashboards/jvm-micrometer_rev10.json
+ create blog/config/grafana/provisioning/datasources/datasource.yml
+ create blog/docker/docker-compose-elk.yml
+ create blog/config/elk/logstash.conf
+ create blog/.localstack/01_init.sh
+ create blog/src/main/java/com/sivalabs/blog/Application.java
+ create blog/src/main/java/com/sivalabs/blog/config/WebMvcConfig.java
+ create blog/src/main/java/com/sivalabs/blog/config/SwaggerConfig.java
+ create blog/src/main/java/com/sivalabs/blog/config/ApplicationProperties.java
+ create blog/src/main/java/com/sivalabs/blog/config/Initializer.java
+ create blog/src/main/java/com/sivalabs/blog/config/GlobalExceptionHandler.java
+ create blog/src/main/java/com/sivalabs/blog/config/logging/Loggable.java
+ create blog/src/main/java/com/sivalabs/blog/config/logging/LoggingAspect.java
+ create blog/src/main/java/com/sivalabs/blog/utils/AppConstants.java
+ create blog/src/main/resources/application.properties
+ create blog/src/main/resources/application-local.properties
+ create blog/src/main/resources/logback-spring.xml
+ create blog/src/test/java/com/sivalabs/blog/ApplicationIntegrationTest.java
+ create blog/src/test/java/com/sivalabs/blog/SchemaValidationTest.java
+ create blog/src/test/java/com/sivalabs/blog/common/ContainersConfig.java
+ create blog/src/test/java/com/sivalabs/blog/common/AbstractIntegrationTest.java
+ create blog/src/test/java/com/sivalabs/blog/TestApplication.java
+ create blog/src/test/java/com/sivalabs/blog/SqsListenerIntegrationTest.java
+ create blog/src/test/resources/application-test.properties
+ create blog/src/test/resources/logback-test.xml
+
+No change to package.json was detected. No package manager install will be executed.
+[INFO] Scanning for projects...
+[INFO]
+[INFO] -----------------------< com.sivalabs.blog:blog >-----------------------
+[INFO] Building blog 0.0.1-SNAPSHOT
+[INFO] from pom.xml
+[INFO] --------------------------------[ jar ]---------------------------------
+[INFO]
+[INFO] --- spotless:2.39.0:apply (default-cli) @ blog ---
+[INFO] Index file does not exist. Fallback to an empty index
+[INFO] Writing clean file: /Users/siva/blog/src/test/java/com/sivalabs/blog/TestApplication.java
+[INFO] Writing clean file: /Users/siva/blog/src/test/java/com/sivalabs/blog/SqsListenerIntegrationTest.java
+[INFO] Writing clean file: /Users/siva/blog/src/test/java/com/sivalabs/blog/SchemaValidationTest.java
+[INFO] Writing clean file: /Users/siva/blog/src/test/java/com/sivalabs/blog/common/AbstractIntegrationTest.java
+[INFO] Writing clean file: /Users/siva/blog/src/test/java/com/sivalabs/blog/common/ContainersConfig.java
+[INFO] Writing clean file: /Users/siva/blog/src/main/java/com/sivalabs/blog/config/GlobalExceptionHandler.java
+[INFO] Writing clean file: /Users/siva/blog/src/main/java/com/sivalabs/blog/config/SwaggerConfig.java
+[INFO] Writing clean file: /Users/siva/blog/src/main/java/com/sivalabs/blog/config/logging/LoggingAspect.java
+[INFO] Spotless.Java is keeping 15 files clean - 8 were changed to be clean, 7 were already clean, 0 were skipped because caching determined they were already clean
+[INFO] ------------------------------------------------------------------------
+[INFO] BUILD SUCCESS
+[INFO] ------------------------------------------------------------------------
+[INFO] Total time: 1.192 s
+[INFO] Finished at: 2023-08-30T11:30:00+05:30
+[INFO] ------------------------------------------------------------------------
+==========================================
+Your application is generated successfully
+ cd blog
+ > ./mvnw spring-boot:run
+==========================================
+```
+
+### Generate REST API with CRUD operations
+You can generate REST API with CRUD operation using the following command:
+
+**IMPORTANT:** You should run the following command from within the generated project folder.
+
+```shell
+$ cd blog
+$ yo springboot:controller Customer --base-path /api/customers
+```
+
+This sub-generator will generate the following:
+
+* JPA entity
+* Spring Data JPA Repository
+* Service
+* Spring MVC REST Controller with CRUD operations
+* Unit and Integration Tests for REST Controller
+* Flyway or Liquibase migration to create table
+
+```shell
+$ yo springboot:controller Customer --base-path /api/customers
+Generating JPA entity, repository, service and controller
+EntityName: Customer, basePath: /api/customers
+ force .yo-rc.json
+ create src/main/java/com/sivalabs/blog/entities/Customer.java
+ create src/main/java/com/sivalabs/blog/model/response/PagedResult.java
+ create src/main/java/com/sivalabs/blog/repositories/CustomerRepository.java
+ create src/main/java/com/sivalabs/blog/services/CustomerService.java
+ create src/main/java/com/sivalabs/blog/web/controllers/CustomerController.java
+ create src/test/java/com/sivalabs/blog/web/controllers/CustomerControllerTest.java
+ create src/test/java/com/sivalabs/blog/web/controllers/CustomerControllerIT.java
+ create src/test/java/com/sivalabs/blog/services/CustomerServiceTest.java
+ create src/main/resources/db/migration/postgresql/V2__create_customers_table.sql
+
+No change to package.json was detected. No package manager install will be executed.
+[INFO] Scanning for projects...
+[INFO]
+[INFO] -----------------------< com.sivalabs.blog:blog >-----------------------
+[INFO] Building blog 0.0.1-SNAPSHOT
+[INFO] from pom.xml
+[INFO] --------------------------------[ jar ]---------------------------------
+[INFO]
+[INFO] --- spotless:2.39.0:apply (default-cli) @ blog ---
+[INFO] Writing clean file: /Users/siva/blog/src/test/java/com/sivalabs/blog/web/controllers/CustomerControllerTest.java
+[INFO] Writing clean file: /Users/siva/blog/src/test/java/com/sivalabs/blog/web/controllers/CustomerControllerIT.java
+[INFO] Writing clean file: /Users/siva/blog/src/test/java/com/sivalabs/blog/services/CustomerServiceTest.java
+[INFO] Writing clean file: /Users/siva/blog/src/main/java/com/sivalabs/blog/web/controllers/CustomerController.java
+[INFO] Writing clean file: /Users/siva/blog/src/main/java/com/sivalabs/blog/model/response/PagedResult.java
+[INFO] Writing clean file: /Users/siva/blog/src/main/java/com/sivalabs/blog/services/CustomerService.java
+[INFO] Writing clean file: /Users/siva/blog/src/main/java/com/sivalabs/blog/entities/Customer.java
+[INFO] Spotless.Java is keeping 23 files clean - 7 were changed to be clean, 1 were already clean, 15 were skipped because caching determined they were already clean
+[INFO] ------------------------------------------------------------------------
+[INFO] BUILD SUCCESS
+[INFO] ------------------------------------------------------------------------
+[INFO] Total time: 1.190 s
+[INFO] Finished at: 2023-08-30T11:32:50+05:30
+[INFO] ------------------------------------------------------------------------
+```
diff --git a/docs/server-generation-1.png b/docs/server-generation-1.png
new file mode 100644
index 0000000..1461576
Binary files /dev/null and b/docs/server-generation-1.png differ
diff --git a/docs/server-generation-2.png b/docs/server-generation-2.png
new file mode 100644
index 0000000..de98c38
Binary files /dev/null and b/docs/server-generation-2.png differ
diff --git a/generators/app/index.js b/generators/app/index.js
new file mode 100644
index 0000000..e7bd505
--- /dev/null
+++ b/generators/app/index.js
@@ -0,0 +1,18 @@
+'use strict';
+
+const BaseGenerator = require('../base-generator');
+
+module.exports = class extends BaseGenerator {
+
+ constructor(args, opts) {
+ super(args, opts);
+ this.configOptions = this.options.configOptions || {};
+ }
+
+ default() {
+ this.composeWith(require.resolve('../server'), {
+ configOptions: this.configOptions
+ });
+ }
+
+};
diff --git a/generators/base-generator.js b/generators/base-generator.js
new file mode 100644
index 0000000..8c84e8c
--- /dev/null
+++ b/generators/base-generator.js
@@ -0,0 +1,101 @@
+'use strict';
+const Generator = require('yeoman-generator');
+const chalk = require('chalk');
+const _ = require('lodash');
+const log = console.log;
+const shell = require('shelljs');
+
+module.exports = class extends Generator {
+
+ constructor(args, opts) {
+ super(args, opts);
+ }
+
+ logSuccess(msg) {
+ log(chalk.bold.green(msg));
+ }
+
+ logWarn(msg) {
+ log(chalk.keyword('orange')(msg));
+ }
+
+ logError(msg) {
+ log(chalk.bold.red(msg));
+ }
+
+ generateMainJavaCode(configOptions, templates) {
+ const mainJavaRootDir = 'src/main/java/';
+ this._generateCode(configOptions, templates, 'app/', mainJavaRootDir, configOptions.packageFolder);
+ }
+
+ generateMainResCode(configOptions, templates) {
+ const mainResRootDir = 'src/main/resources/';
+ this._generateCode(configOptions, templates, 'app/', mainResRootDir, '');
+ }
+
+ generateTestJavaCode(configOptions, templates) {
+ const testJavaRootDir = 'src/test/java/';
+ this._generateCode(configOptions, templates, 'app/', testJavaRootDir, configOptions.packageFolder);
+ }
+
+ generateTestResCode(configOptions, templates) {
+ const testResRootDir = 'src/test/resources/';
+ this._generateCode(configOptions, templates, 'app/', testResRootDir, '');
+ }
+
+ generateFiles(configOptions, templates, srcRoot, baseFolder) {
+ this._generateCode(configOptions, templates, srcRoot, baseFolder, '');
+ }
+
+ _generateCode(configOptions, templates, srcRoot, baseFolder, packageFolder) {
+ templates.forEach(tmpl => {
+ if (_.isString(tmpl)) {
+ this.fs.copyTpl(
+ this.templatePath(srcRoot + baseFolder + tmpl),
+ this.destinationPath(baseFolder + packageFolder + '/' + tmpl),
+ configOptions
+ );
+ } else {
+ this.fs.copyTpl(
+ this.templatePath(srcRoot + baseFolder + tmpl.src),
+ this.destinationPath(baseFolder + packageFolder + '/' + tmpl.dest),
+ configOptions
+ );
+ }
+ });
+ }
+
+ _formatCode(configOptions, baseDir) {
+ if (configOptions.buildTool === 'maven') {
+ this._formatCodeMaven(configOptions, baseDir);
+ } else {
+ this._formatCodeGradle(configOptions, baseDir);
+ }
+ }
+
+ _formatCodeMaven(configOptions, baseDir) {
+ const command = this._isWin() ? 'mvnw' : './mvnw';
+ if(baseDir) {
+ shell.cd(configOptions.appName);
+ shell.exec(`${command} spotless:apply`);
+ shell.cd('..');
+ } else {
+ shell.exec(`${command} spotless:apply`);
+ }
+ }
+
+ _formatCodeGradle(configOptions, baseDir) {
+ const command = this._isWin() ? 'gradlew' : './gradlew';
+ if(baseDir) {
+ shell.cd(configOptions.appName);
+ shell.exec(`${command} spotlessApply`);
+ shell.cd('..');
+ } else {
+ shell.exec(`${command} spotlessApply`);
+ }
+ }
+
+ _isWin() {
+ return process.platform === 'win32';
+ }
+};
diff --git a/generators/common/files/gradle/gitignore b/generators/common/files/gradle/gitignore
new file mode 100644
index 0000000..ac6d93c
--- /dev/null
+++ b/generators/common/files/gradle/gitignore
@@ -0,0 +1,41 @@
+HELP.md
+.gradle
+build/
+!gradle/wrapper/gradle-wrapper.jar
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+bin/
+!**/src/main/**/bin/
+!**/src/test/**/bin/
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+out/
+!**/src/main/**/out/
+!**/src/test/**/out/
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+
+### VS Code ###
+.vscode/
+
+### Misc ###
+*.log
+.DS_Store
\ No newline at end of file
diff --git a/generators/common/files/gradle/gradle/wrapper/gradle-wrapper.jar b/generators/common/files/gradle/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..943f0cb
Binary files /dev/null and b/generators/common/files/gradle/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/generators/common/files/gradle/gradle/wrapper/gradle-wrapper.properties b/generators/common/files/gradle/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..744c64d
--- /dev/null
+++ b/generators/common/files/gradle/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
+networkTimeout=10000
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/generators/common/files/gradle/gradlew b/generators/common/files/gradle/gradlew
new file mode 100755
index 0000000..65dcd68
--- /dev/null
+++ b/generators/common/files/gradle/gradlew
@@ -0,0 +1,244 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+# Collect all arguments for the java command;
+# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+# shell script including quotes and variable substitutions, so put them in
+# double quotes to make sure that they get re-expanded; and
+# * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/generators/common/files/gradle/gradlew.bat b/generators/common/files/gradle/gradlew.bat
new file mode 100644
index 0000000..93e3f59
--- /dev/null
+++ b/generators/common/files/gradle/gradlew.bat
@@ -0,0 +1,92 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/generators/common/files/maven/.mvn/wrapper/maven-wrapper.jar b/generators/common/files/maven/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 0000000..bf82ff0
Binary files /dev/null and b/generators/common/files/maven/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/generators/common/files/maven/.mvn/wrapper/maven-wrapper.properties b/generators/common/files/maven/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..eacdc9e
--- /dev/null
+++ b/generators/common/files/maven/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
diff --git a/generators/common/files/maven/gitignore b/generators/common/files/maven/gitignore
new file mode 100644
index 0000000..c456913
--- /dev/null
+++ b/generators/common/files/maven/gitignore
@@ -0,0 +1,37 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+### Misc ###
+*.log
+.DS_Store
\ No newline at end of file
diff --git a/generators/common/files/maven/mvnw b/generators/common/files/maven/mvnw
new file mode 100755
index 0000000..8a8fb22
--- /dev/null
+++ b/generators/common/files/maven/mvnw
@@ -0,0 +1,316 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /usr/local/etc/mavenrc ] ; then
+ . /usr/local/etc/mavenrc
+ fi
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`\\unset -f command; \\command -v java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ if [ -n "$MVNW_REPOURL" ]; then
+ jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+ else
+ jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+ fi
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+ if $cygwin; then
+ wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+ fi
+
+ if command -v wget > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl -o "$wrapperJarPath" "$jarUrl" -f
+ else
+ curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ fi
+
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaClass=`cygpath --path --windows "$javaClass"`
+ fi
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ $MAVEN_DEBUG_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" \
+ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/generators/common/files/maven/mvnw.cmd b/generators/common/files/maven/mvnw.cmd
new file mode 100644
index 0000000..1d8ab01
--- /dev/null
+++ b/generators/common/files/maven/mvnw.cmd
@@ -0,0 +1,188 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% ^
+ %JVM_CONFIG_MAVEN_PROPS% ^
+ %MAVEN_OPTS% ^
+ %MAVEN_DEBUG_OPTS% ^
+ -classpath %WRAPPER_JAR% ^
+ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%"=="on" pause
+
+if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
+
+cmd /C exit /B %ERROR_CODE%
diff --git a/generators/constants.js b/generators/constants.js
new file mode 100644
index 0000000..460afca
--- /dev/null
+++ b/generators/constants.js
@@ -0,0 +1,47 @@
+const LOCALSTACK_IMAGE_VERSION = '2.3.2';
+const POSTGRESQL_IMAGE_VERSION = '16.0-alpine';
+const MARIADB_IMAGE_VERSION = '11.0.3';
+const MYSQL_IMAGE_VERSION = '8.2.0';
+
+module.exports = {
+ JAVA_VERSION: '17',
+ SPRING_BOOT_VERSION: '3.1.5',
+ SPRING_CLOUD_VERSION: '2022.0.4',
+ SPRING_CLOUD_AWS_VERSION: '3.0.3',
+ SPRING_DEP_MNGMNT_VERSION: '1.1.3',
+ DEFAULT_APP_VERSION: '0.0.1-SNAPSHOT',
+ SPRINGDOC_OPENAPI_VERSION: '2.2.0',
+ COMMONS_IO_VERSION: '2.15.0',
+ LOGSTASH_LOGBACK_ENCODER: 7.3,
+ PALANTIR_JAVA_FORMAT_VERSION: '2.38.0',
+
+ JACOCO_MIN_COVERAGE_REQUIRED: '0.80',
+
+ GRADLE_GIT_PROPERTIES_PLUGIN_VERSION: '2.4.1',
+ GRADLE_JACOCO_PLUGIN_VERSION: '0.8.10',
+ GRADLE_SONAR_PLUGIN_VERSION: '4.3.0.3225',
+ GRADLE_OWASP_PLUGIN_VERSION: '8.2.1',
+ GRADLE_BENMANES_VERSIONS_PLUGIN_VERSION: '0.47.0',
+ GRADLE_SPOTLESS_PLUGIN_VERSION: '6.21.0',
+
+ MAVEN_DEPENDENCY_CHECK_PLUGIN_VERSION: '8.4.2',
+ MAVEN_PROPERTIES_PLUGIN_VERSION: '1.2.1',
+ MAVEN_SONAR_PLUGIN_VERSION: '3.10.0.2594',
+ MAVEN_JACOCO_PLUGIN_VERSION: '0.8.11',
+ MAVEN_SPOTLESS_PLUGIN_VERSION: '2.40.0',
+
+ KEY_FLYWAY_MIGRATION_COUNTER: 'flywayMigrationCounter',
+ KEY_LIQUIBASE_MIGRATION_COUNTER: 'liquibaseMigrationCounter',
+
+ LOCALSTACK_IMAGE_VERSION: LOCALSTACK_IMAGE_VERSION,
+ LOCALSTACK_IMAGE: 'localstack/localstack:' + LOCALSTACK_IMAGE_VERSION,
+
+ POSTGRESQL_IMAGE_VERSION: POSTGRESQL_IMAGE_VERSION,
+ POSTGRESQL_IMAGE: 'postgres:' + POSTGRESQL_IMAGE_VERSION,
+
+ MARIADB_IMAGE_VERSION: MARIADB_IMAGE_VERSION,
+ MARIADB_IMAGE: 'mariadb:' + MARIADB_IMAGE_VERSION,
+
+ MYSQL_IMAGE_VERSION: MYSQL_IMAGE_VERSION,
+ MYSQL_IMAGE: 'mysql:' + MYSQL_IMAGE_VERSION,
+}
diff --git a/generators/controller/index.js b/generators/controller/index.js
new file mode 100644
index 0000000..a605aba
--- /dev/null
+++ b/generators/controller/index.js
@@ -0,0 +1,134 @@
+'use strict';
+const BaseGenerator = require('../base-generator');
+const constants = require('../constants');
+const _ = require('lodash');
+
+module.exports = class extends BaseGenerator {
+
+ constructor(args, opts) {
+ super(args, opts);
+ this.configOptions = this.options.configOptions || {};
+
+ this.argument("entityName", {
+ type: String,
+ required: true,
+ description: "Entity name"
+ });
+
+ this.option('base-path', {
+ type: String,
+ desc: "Base URL path for REST Controller"
+ })
+ }
+
+ get initializing() {
+ this.logSuccess('Generating JPA entity, repository, service and controller');
+ return {
+ validateEntityName() {
+ const context = this.context;
+ console.log(`EntityName: ${this.options.entityName}, basePath: ${this.options.basePath}`);
+ //this.env.error("The entity name is invalid");
+ }
+ }
+ }
+
+ /*get prompting() {
+ return prompts.prompting;
+ }*/
+
+ configuring() {
+ this.configOptions = Object.assign({}, this.configOptions, this.config.getAll());
+ this.configOptions.basePath = this.options['base-path'];
+ this.configOptions.entityName = this.options.entityName;
+ this.configOptions.entityVarName = _.camelCase(this.options.entityName);
+ this.configOptions.tableName = _.snakeCase(this.options.entityName)+'s';
+ this.configOptions.doesNotSupportDatabaseSequences =
+ this.configOptions.databaseType === 'mysql';
+ this.configOptions.formatCode = this.options.formatCode !== false
+ }
+
+ writing() {
+ this._generateAppCode(this.configOptions);
+ this._generateDbMigrationConfig(this.configOptions)
+ }
+
+ end() {
+ if(this.configOptions.formatCode !== false) {
+ this._formatCode(this.configOptions, null);
+ }
+ }
+
+ _generateAppCode(configOptions) {
+ const mainJavaTemplates = [
+ {src: 'exception/NotFoundException.java', dest: 'exception/'+configOptions.entityName+'NotFoundException.java'},
+ {src: 'mapper/Mapper.java', dest: 'mapper/'+configOptions.entityName+'Mapper.java'},
+ {src: 'model/query/FindQuery.java', dest: 'model/query/Find'+configOptions.entityName+'sQuery.java'},
+ {src: 'model/request/Request.java', dest: 'model/request/'+configOptions.entityName+'Request.java'},
+ {src: 'model/response/Response.java', dest: 'model/response/'+configOptions.entityName+'Response.java'},
+ {src: 'repositories/DocumentRepository.java', dest: 'repositories/'+configOptions.entityName+'Repository.java'},
+ {src: 'services/DocumentService.java', dest: 'services/'+configOptions.entityName+'Service.java'},
+ {src: 'web/controllers/Controller.java', dest: 'web/controllers/'+configOptions.entityName+'Controller.java'},
+ ];
+
+ if (configOptions.databaseType == 'mongo') {
+ mainJavaTemplates.push({src: 'entities/Document.java', dest: 'entities/'+configOptions.entityName+'.java'});
+ } else {
+ mainJavaTemplates.push({ src: 'entities/Entity.java', dest: 'entities/' + configOptions.entityName + '.java' });
+ }
+
+ this.generateMainJavaCode(configOptions, mainJavaTemplates);
+
+ const testJavaTemplates = [
+ {src: 'web/controllers/ControllerTest.java', dest: 'web/controllers/'+configOptions.entityName+'ControllerTest.java'},
+ {src: 'web/controllers/ControllerIT.java', dest: 'web/controllers/'+configOptions.entityName+'ControllerIT.java'},
+ {src: 'services/ServiceTest.java', dest: 'services/'+configOptions.entityName+'ServiceTest.java'},
+ ];
+ this.generateTestJavaCode(configOptions, testJavaTemplates);
+ }
+
+ _generateDbMigrationConfig(configOptions) {
+
+ if(configOptions.dbMigrationTool === 'flywaydb') {
+ this._generateFlywayMigration(configOptions)
+ }
+
+ if(configOptions.dbMigrationTool === 'liquibase') {
+ this._generateLiquibaseMigration(configOptions);
+ }
+ }
+
+ _generateFlywayMigration(configOptions) {
+ const counter = configOptions[constants.KEY_FLYWAY_MIGRATION_COUNTER] + 1;
+ let vendor = configOptions.databaseType;
+ const scriptTemplate = configOptions.doesNotSupportDatabaseSequences ?
+ "V1__new_table_no_seq.sql" : "V1__new_table_with_seq.sql";
+
+ this.fs.copyTpl(
+ this.templatePath('app/src/main/resources/db/migration/flyway/'+scriptTemplate),
+ this.destinationPath('src/main/resources/db/migration/'+vendor+
+ '/V'+counter+'__create_'+configOptions.tableName+'_table.sql'),
+ configOptions
+ );
+ const flywayMigrantCounter = {
+ [constants.KEY_FLYWAY_MIGRATION_COUNTER]: counter
+ };
+ this.config.set(flywayMigrantCounter);
+ }
+
+ _generateLiquibaseMigration(configOptions) {
+ const dbFmt = configOptions.dbMigrationFormat;
+ const counter = configOptions[constants.KEY_LIQUIBASE_MIGRATION_COUNTER] + 1;
+ const scriptTemplate = configOptions.doesNotSupportDatabaseSequences ?
+ `01-new_table_no_seq.${dbFmt}` : `01-new_table_with_seq.${dbFmt}`;
+ this.fs.copyTpl(
+ this.templatePath('app/src/main/resources/db/migration/liquibase/changelog/'+scriptTemplate),
+ this.destinationPath('src/main/resources/db/changelog/migration/0'+counter+'-create_'+configOptions.tableName+'_table.'+dbFmt),
+ configOptions
+ );
+ const liquibaseMigrantCounter = {
+ [constants.KEY_LIQUIBASE_MIGRATION_COUNTER]: counter
+ };
+ //const updatedConfig = Object.assign({}, this.config.getAll(), liquibaseMigrantCounter);
+ this.config.set(liquibaseMigrantCounter);
+ }
+};
diff --git a/generators/controller/prompts.js b/generators/controller/prompts.js
new file mode 100644
index 0000000..e69de29
diff --git a/generators/controller/templates/app/src/main/java/entities/Document.java b/generators/controller/templates/app/src/main/java/entities/Document.java
new file mode 100644
index 0000000..85dd3e5
--- /dev/null
+++ b/generators/controller/templates/app/src/main/java/entities/Document.java
@@ -0,0 +1,36 @@
+package <%= packageName %>.entities;
+
+import java.util.Objects;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.Table;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.hibernate.Hibernate;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@Document("<%= tableName %>")
+public class <%= entityName %>Document {
+
+ @Id
+ private String id;
+
+ @Version
+ private Long version;
+
+ @Field(name = "created_date")
+ @CreatedDate
+ private Date createdDate;
+
+ @Field(name = "last_modified_date")
+ @LastModifiedDate
+ private Date lastModifiedDate;
+}
diff --git a/generators/controller/templates/app/src/main/java/entities/Entity.java b/generators/controller/templates/app/src/main/java/entities/Entity.java
new file mode 100644
index 0000000..4b71981
--- /dev/null
+++ b/generators/controller/templates/app/src/main/java/entities/Entity.java
@@ -0,0 +1,48 @@
+package <%= packageName %>.entities;
+
+import java.util.Objects;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.Table;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.hibernate.Hibernate;
+
+@Entity
+@Table(name = "<%= tableName %>")
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+public class <%= entityName %> {
+
+ @Id
+<%_ if (!doesNotSupportDatabaseSequences) { _%>
+ @GeneratedValue(strategy = GenerationType.SEQUENCE)
+<%_ } _%>
+<%_ if (doesNotSupportDatabaseSequences) { _%>
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+<%_ } _%>
+ private Long id;
+
+ @Column(nullable = false)
+ private String text;
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
+ <%= entityName %> <%= entityVarName %> = (<%= entityName %>) o;
+ return id != null && Objects.equals(id, <%= entityVarName %>.id);
+ }
+
+ @Override
+ public int hashCode() {
+ return getClass().hashCode();
+ }
+}
diff --git a/generators/controller/templates/app/src/main/java/exception/NotFoundException.java b/generators/controller/templates/app/src/main/java/exception/NotFoundException.java
new file mode 100644
index 0000000..298211b
--- /dev/null
+++ b/generators/controller/templates/app/src/main/java/exception/NotFoundException.java
@@ -0,0 +1,9 @@
+package <%= packageName %>.exception;
+
+public class <%= entityName %>NotFoundException extends ResourceNotFoundException {
+
+ public <%= entityName %>NotFoundException(Long id) {
+ super("<%= entityName %> with Id '%d' not found".formatted(id));
+ }
+
+}
diff --git a/generators/controller/templates/app/src/main/java/mapper/Mapper.java b/generators/controller/templates/app/src/main/java/mapper/Mapper.java
new file mode 100644
index 0000000..6b4b5f0
--- /dev/null
+++ b/generators/controller/templates/app/src/main/java/mapper/Mapper.java
@@ -0,0 +1,29 @@
+package <%= packageName %>.mapper;
+
+import <%= packageName %>.entities.<%= entityName %>;
+import <%= packageName %>.model.request.<%= entityName %>Request;
+import <%= packageName %>.model.response.<%= entityName %>Response;
+import java.util.List;
+import org.springframework.stereotype.Service;
+
+@Service
+public class <%= entityName %>Mapper {
+
+ public <%= entityName %> toEntity(<%= entityName %>Request <%= entityVarName %>Request) {
+ <%= entityName %> <%= entityVarName %> = new <%= entityName %>();
+ <%= entityVarName %>.setText(<%= entityVarName %>Request.text());
+ return <%= entityVarName %>;
+ }
+
+ public void map<%= entityName %>WithRequest(<%= entityName %> <%= entityVarName %>, <%= entityName %>Request <%= entityVarName %>Request) {
+ <%= entityVarName %>.setText(<%= entityVarName %>Request.text());
+ }
+
+ public <%= entityName %>Response toResponse(<%= entityName %> <%= entityVarName %>) {
+ return new <%= entityName %>Response(<%= entityVarName %>.getId(), <%= entityVarName %>.getText());
+ }
+
+ public List<<%= entityName %>Response> toResponseList(List<<%= entityName %>> <%= entityVarName %>List) {
+ return <%= entityVarName %>List.stream().map(this::toResponse).toList();
+ }
+}
diff --git a/generators/controller/templates/app/src/main/java/model/query/FindQuery.java b/generators/controller/templates/app/src/main/java/model/query/FindQuery.java
new file mode 100644
index 0000000..56a26f9
--- /dev/null
+++ b/generators/controller/templates/app/src/main/java/model/query/FindQuery.java
@@ -0,0 +1,3 @@
+package <%= packageName %>.model.query;
+
+public record Find<%= entityName %>sQuery(int pageNo, int pageSize, String sortBy, String sortDir) {}
\ No newline at end of file
diff --git a/generators/controller/templates/app/src/main/java/model/request/Request.java b/generators/controller/templates/app/src/main/java/model/request/Request.java
new file mode 100644
index 0000000..2e9f19e
--- /dev/null
+++ b/generators/controller/templates/app/src/main/java/model/request/Request.java
@@ -0,0 +1,7 @@
+package <%= packageName %>.model.request;
+
+import jakarta.validation.constraints.NotEmpty;
+
+public record <%= entityName %>Request(@NotEmpty(message = "Text cannot be empty") String text) {
+
+}
diff --git a/generators/controller/templates/app/src/main/java/model/response/Response.java b/generators/controller/templates/app/src/main/java/model/response/Response.java
new file mode 100644
index 0000000..545b83c
--- /dev/null
+++ b/generators/controller/templates/app/src/main/java/model/response/Response.java
@@ -0,0 +1,3 @@
+package <%= packageName %>.model.response;
+
+public record <%= entityName %>Response(Long id, String text) {}
diff --git a/generators/controller/templates/app/src/main/java/repositories/DocumentRepository.java b/generators/controller/templates/app/src/main/java/repositories/DocumentRepository.java
new file mode 100644
index 0000000..610892a
--- /dev/null
+++ b/generators/controller/templates/app/src/main/java/repositories/DocumentRepository.java
@@ -0,0 +1,15 @@
+package <%= packageName %>.repositories;
+import <%= packageName %>.entities.<%= entityName %>;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.data.querydsl.QuerydslPredicateExecutor;
+import org.springframework.stereotype.Repository;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Optional;
+
+@Repository
+public interface <%= entityName %>Repository extends MongoRepository<<%= entityName %>Document, String> {
+
+ Optional<<%= entityName %>Document> findByExternalId(String externalId);
+}
diff --git a/generators/controller/templates/app/src/main/java/repositories/Repository.java b/generators/controller/templates/app/src/main/java/repositories/Repository.java
new file mode 100644
index 0000000..253112d
--- /dev/null
+++ b/generators/controller/templates/app/src/main/java/repositories/Repository.java
@@ -0,0 +1,6 @@
+package <%= packageName %>.repositories;
+
+import <%= packageName %>.entities.<%= entityName %>;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface <%= entityName %>Repository extends JpaRepository<<%= entityName %>, Long> {}
diff --git a/generators/controller/templates/app/src/main/java/services/DocumentService.java b/generators/controller/templates/app/src/main/java/services/DocumentService.java
new file mode 100644
index 0000000..ac2b5d0
--- /dev/null
+++ b/generators/controller/templates/app/src/main/java/services/DocumentService.java
@@ -0,0 +1,27 @@
+package <%= packageName %>.services;
+
+import <%= packageName %>.entities.<%= entityName %>;
+import <%= packageName %>.exception.<%= entityName %>NotFoundException;
+import <%= packageName %>.repositories.<%= entityName %>DocumentRepository;
+import java.util.List;
+import java.util.Optional;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class <%= entityName %>DocumentService {
+
+ private final <%= entityName %>Repository <%= entityVarName %>Repository;
+ public void get(String id) {
+ return <%= entityName %>Repository.findById(id)
+ .orElseThrow(NotFoundException::new);
+ }
+}
diff --git a/generators/controller/templates/app/src/main/java/services/Service.java b/generators/controller/templates/app/src/main/java/services/Service.java
new file mode 100644
index 0000000..b2c1b00
--- /dev/null
+++ b/generators/controller/templates/app/src/main/java/services/Service.java
@@ -0,0 +1,84 @@
+package <%= packageName %>.services;
+
+import <%= packageName %>.entities.<%= entityName %>;
+import <%= packageName %>.exception.<%= entityName %>NotFoundException;
+import <%= packageName %>.mapper.<%= entityName %>Mapper;
+import <%= packageName %>.model.query.Find<%= entityName %>sQuery;
+import <%= packageName %>.model.request.<%= entityName %>Request;
+import <%= packageName %>.model.response.<%= entityName %>Response;
+import <%= packageName %>.model.response.PagedResult;
+import <%= packageName %>.repositories.<%= entityName %>Repository;
+import java.util.List;
+import java.util.Optional;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@Transactional(readOnly = true)
+@RequiredArgsConstructor
+public class <%= entityName %>Service {
+
+ private final <%= entityName %>Repository <%= entityVarName %>Repository;
+ private final <%= entityName %>Mapper <%= entityVarName %>Mapper;
+
+ public PagedResult<<%= entityName %>Response> findAll<%= entityName %>s(
+ Find<%= entityName %>sQuery find<%= entityName %>sQuery) {
+
+ // create Pageable instance
+ Pageable pageable = createPageable(find<%= entityName %>sQuery);
+
+ Page<<%= entityName %>> <%= entityVarName %>sPage = <%= entityVarName %>Repository.findAll(pageable);
+
+ List<<%= entityName %>Response> <%= entityVarName %>ResponseList = <%= entityVarName %>Mapper.toResponseList(<%= entityVarName %>sPage.getContent());
+
+ return new PagedResult<>(<%= entityVarName %>sPage, <%= entityVarName %>ResponseList);
+ }
+
+ private Pageable createPageable(Find<%= entityName %>sQuery find<%= entityName %>sQuery) {
+ int pageNo = Math.max(find<%= entityName %>sQuery.pageNo() - 1, 0);
+ Sort sort =
+ Sort.by(
+ find<%= entityName %>sQuery.sortDir().equalsIgnoreCase(Sort.Direction.ASC.name())
+ ? Sort.Order.asc(find<%= entityName %>sQuery.sortBy())
+ : Sort.Order.desc(find<%= entityName %>sQuery.sortBy()));
+ return PageRequest.of(pageNo, find<%= entityName %>sQuery.pageSize(), sort);
+ }
+
+ public Optional<<%= entityName %>Response> find<%= entityName %>ById(Long id) {
+ return <%= entityVarName %>Repository.findById(id).map(<%= entityVarName %>Mapper::toResponse);
+ }
+
+ @Transactional
+ public <%= entityName %>Response save<%= entityName %>(<%= entityName %>Request <%= entityVarName %>Request) {
+ <%= entityName %> <%= entityVarName %> = <%= entityVarName %>Mapper.toEntity(<%= entityVarName %>Request);
+ <%= entityName %> saved<%= entityName %> = <%= entityVarName %>Repository.save(<%= entityVarName %>);
+ return <%= entityVarName %>Mapper.toResponse(saved<%= entityName %>);
+ }
+
+ @Transactional
+ public <%= entityName %>Response update<%= entityName %>(Long id, <%= entityName %>Request <%= entityVarName %>Request) {
+ <%= entityName %> <%= entityVarName %> =
+ <%= entityVarName %>Repository
+ .findById(id)
+ .orElseThrow(() -> new <%= entityName %>NotFoundException(id));
+
+ // Update the <%= entityVarName %> object with data from <%= entityVarName %>Request
+ <%= entityVarName %>Mapper.map<%= entityName %>WithRequest(<%= entityVarName %>, <%= entityVarName %>Request);
+
+ // Save the updated <%= entityVarName %> object
+ <%= entityName %> updated<%= entityName %> = <%= entityVarName %>Repository.save(<%= entityVarName %>);
+
+ return <%= entityVarName %>Mapper.toResponse(updated<%= entityName %>);
+ }
+
+ @Transactional
+ public void delete<%= entityName %>ById(Long id) {
+ <%= entityVarName %>Repository.deleteById(id);
+ }
+}
diff --git a/generators/controller/templates/app/src/main/java/web/controllers/Controller.java b/generators/controller/templates/app/src/main/java/web/controllers/Controller.java
new file mode 100644
index 0000000..10672b3
--- /dev/null
+++ b/generators/controller/templates/app/src/main/java/web/controllers/Controller.java
@@ -0,0 +1,101 @@
+package <%= packageName %>.web.controllers;
+
+import <%= packageName %>.exception.<%= entityName %>NotFoundException;
+import <%= packageName %>.model.query.Find<%= entityName %>sQuery;
+import <%= packageName %>.model.request.<%= entityName %>Request;
+import <%= packageName %>.model.response.<%= entityName %>Response;
+import <%= packageName %>.model.response.PagedResult;
+import <%= packageName %>.services.<%= entityName %>Service;
+import <%= packageName %>.utils.AppConstants;
+import java.util.List;
+import java.net.URI;
+import jakarta.validation.Valid;
+import lombok.extern.slf4j.Slf4j;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
+
+@RestController
+@RequestMapping("<%= basePath %>")
+@Slf4j
+@RequiredArgsConstructor
+public class <%= entityName %>Controller {
+
+ private final <%= entityName %>Service <%= entityVarName %>Service;
+
+ @GetMapping
+ public PagedResult<<%= entityName %>Response> getAll<%= entityName %>s(
+ @RequestParam(
+ value = "pageNo",
+ defaultValue = AppConstants.DEFAULT_PAGE_NUMBER,
+ required = false)
+ int pageNo,
+ @RequestParam(
+ value = "pageSize",
+ defaultValue = AppConstants.DEFAULT_PAGE_SIZE,
+ required = false)
+ int pageSize,
+ @RequestParam(
+ value = "sortBy",
+ defaultValue = AppConstants.DEFAULT_SORT_BY,
+ required = false)
+ String sortBy,
+ @RequestParam(
+ value = "sortDir",
+ defaultValue = AppConstants.DEFAULT_SORT_DIRECTION,
+ required = false)
+ String sortDir
+ ) {
+ Find<%= entityName %>sQuery find<%= entityName %>sQuery =
+ new Find<%= entityName %>sQuery(pageNo, pageSize, sortBy, sortDir);
+ return <%= entityVarName %>Service.findAll<%= entityName %>s(find<%= entityName %>sQuery);
+ }
+
+ @GetMapping("/{id}")
+ public ResponseEntity<<%= entityName %>Response> get<%= entityName %>ById(@PathVariable Long id) {
+ return <%= entityVarName %>Service
+ .find<%= entityName %>ById(id)
+ .map(ResponseEntity::ok)
+ .orElseThrow(() -> new <%= entityName %>NotFoundException(id));
+ }
+
+ @PostMapping
+ public ResponseEntity<<%= entityName %>Response> create<%= entityName %>(@RequestBody @Validated <%= entityName %>Request <%= entityVarName %>Request) {
+ <%= entityName %>Response response = <%= entityVarName %>Service.save<%= entityName %>(<%= entityVarName %>Request);
+ URI location =
+ ServletUriComponentsBuilder.fromCurrentRequest()
+ .path("<%= basePath %>/{id}")
+ .buildAndExpand(response.id())
+ .toUri();
+ return ResponseEntity.created(location).body(response);
+ }
+
+ @PutMapping("/{id}")
+ public ResponseEntity<<%= entityName %>Response> update<%= entityName %>(
+ @PathVariable Long id, @RequestBody @Valid <%= entityName %>Request <%= entityVarName %>Request) {
+ return ResponseEntity.ok(<%= entityVarName %>Service.update<%= entityName %>(id, <%= entityVarName %>Request));
+ }
+
+ @DeleteMapping("/{id}")
+ public ResponseEntity<<%= entityName %>Response> delete<%= entityName %>(@PathVariable Long id) {
+ return <%= entityVarName %>Service
+ .find<%= entityName %>ById(id)
+ .map(
+ <%= entityVarName %> -> {
+ <%= entityVarName %>Service.delete<%= entityName %>ById(id);
+ return ResponseEntity.ok(<%= entityVarName %>);
+ })
+ .orElseThrow(() -> new <%= entityName %>NotFoundException(id));
+ }
+}
diff --git a/generators/controller/templates/app/src/main/resources/db/migration/flyway/V1__new_table_no_seq.sql b/generators/controller/templates/app/src/main/resources/db/migration/flyway/V1__new_table_no_seq.sql
new file mode 100644
index 0000000..24a9dc1
--- /dev/null
+++ b/generators/controller/templates/app/src/main/resources/db/migration/flyway/V1__new_table_no_seq.sql
@@ -0,0 +1,10 @@
+create table <%= tableName %> (
+ id bigint not null auto_increment,
+ <%_ if (databaseType != 'postgresql') { _%>
+ text varchar(1024) not null,
+ <%_ } _%>
+ <%_ if (databaseType === 'postgresql') { _%>
+ text text not null,
+ <%_ } _%>
+ primary key (id)
+);
diff --git a/generators/controller/templates/app/src/main/resources/db/migration/flyway/V1__new_table_with_seq.sql b/generators/controller/templates/app/src/main/resources/db/migration/flyway/V1__new_table_with_seq.sql
new file mode 100644
index 0000000..c64aff6
--- /dev/null
+++ b/generators/controller/templates/app/src/main/resources/db/migration/flyway/V1__new_table_with_seq.sql
@@ -0,0 +1,17 @@
+create sequence <%= tableName %>_seq start with 1 increment by 50;
+
+create table <%= tableName %> (
+ <%_ if (databaseType != 'mariadb') { _%>
+ id bigint DEFAULT nextval('<%= tableName %>_seq') not null,
+ <%_ } _%>
+ <%_ if (databaseType === 'mariadb') { _%>
+ id bigint DEFAULT nextval(`<%= tableName %>_seq`) not null,
+ <%_ } _%>
+ <%_ if (databaseType != 'postgresql') { _%>
+ text varchar(1024) not null,
+ <%_ } _%>
+ <%_ if (databaseType === 'postgresql') { _%>
+ text text not null,
+ <%_ } _%>
+ primary key (id)
+);
diff --git a/generators/controller/templates/app/src/main/resources/db/migration/liquibase/changelog/01-new_table_no_seq.sql b/generators/controller/templates/app/src/main/resources/db/migration/liquibase/changelog/01-new_table_no_seq.sql
new file mode 100644
index 0000000..aeb86bf
--- /dev/null
+++ b/generators/controller/templates/app/src/main/resources/db/migration/liquibase/changelog/01-new_table_no_seq.sql
@@ -0,0 +1,14 @@
+-- liquibase formatted sql
+-- changeset author:app id:createTable-<% tableName %>
+-- see https://docs.liquibase.com/concepts/changelogs/sql-format.html
+
+create table <%= tableName %> (
+ id bigint not null auto_increment,
+ <%_ if (databaseType != 'postgresql') { _%>
+ text varchar(1024) not null,
+ <%_ } _%>
+ <%_ if (databaseType === 'postgresql') { _%>
+ text text not null,
+ <%_ } _%>
+ primary key (id)
+);
diff --git a/generators/controller/templates/app/src/main/resources/db/migration/liquibase/changelog/01-new_table_no_seq.xml b/generators/controller/templates/app/src/main/resources/db/migration/liquibase/changelog/01-new_table_no_seq.xml
new file mode 100644
index 0000000..4011e76
--- /dev/null
+++ b/generators/controller/templates/app/src/main/resources/db/migration/liquibase/changelog/01-new_table_no_seq.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/generators/controller/templates/app/src/main/resources/db/migration/liquibase/changelog/01-new_table_no_seq.yaml b/generators/controller/templates/app/src/main/resources/db/migration/liquibase/changelog/01-new_table_no_seq.yaml
new file mode 100644
index 0000000..dbe8736
--- /dev/null
+++ b/generators/controller/templates/app/src/main/resources/db/migration/liquibase/changelog/01-new_table_no_seq.yaml
@@ -0,0 +1,29 @@
+# https://docs.liquibase.com/concepts/changelogs/yaml-format.html
+databaseChangeLog:
+ - property:
+ dbms: postgresql
+ name: string.type
+ value: text
+ - property:
+ dbms: "!postgresql"
+ name: string.type
+ value: varchar(255)
+ - changeSet:
+ author: author
+ id: createTable-<%= tableName %>
+ changes:
+ - createTable:
+ tableName: <%= tableName %>
+ columns:
+ - column:
+ name: id
+ type: bigint
+ autoIncrement: true
+ constraints:
+ primaryKey: true
+ nullable: false
+ - column:
+ name: text
+ type: ${string.type}
+ constraints:
+ nullable: false
diff --git a/generators/controller/templates/app/src/main/resources/db/migration/liquibase/changelog/01-new_table_with_seq.sql b/generators/controller/templates/app/src/main/resources/db/migration/liquibase/changelog/01-new_table_with_seq.sql
new file mode 100644
index 0000000..2cc0565
--- /dev/null
+++ b/generators/controller/templates/app/src/main/resources/db/migration/liquibase/changelog/01-new_table_with_seq.sql
@@ -0,0 +1,21 @@
+-- liquibase formatted sql
+-- changeset author:app id:createTable-<% tableName %>
+-- see https://docs.liquibase.com/concepts/changelogs/sql-format.html
+
+create sequence <%= tableName %>_seq start with 1 increment by 50;
+
+create table <%= tableName %> (
+ <%_ if (databaseType != 'mariadb') { _%>
+ id bigint DEFAULT nextval('<%= tableName %>_seq') not null,
+ <%_ } _%>
+ <%_ if (databaseType === 'mariadb') { _%>
+ id bigint DEFAULT nextval(`<%= tableName %>_seq`) not null,
+ <%_ } _%>
+ <%_ if (databaseType != 'postgresql') { _%>
+ text varchar(1024) not null,
+ <%_ } _%>
+ <%_ if (databaseType === 'postgresql') { _%>
+ text text not null,
+ <%_ } _%>
+ primary key (id)
+);
diff --git a/generators/controller/templates/app/src/main/resources/db/migration/liquibase/changelog/01-new_table_with_seq.xml b/generators/controller/templates/app/src/main/resources/db/migration/liquibase/changelog/01-new_table_with_seq.xml
new file mode 100644
index 0000000..d97549b
--- /dev/null
+++ b/generators/controller/templates/app/src/main/resources/db/migration/liquibase/changelog/01-new_table_with_seq.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/generators/controller/templates/app/src/main/resources/db/migration/liquibase/changelog/01-new_table_with_seq.yaml b/generators/controller/templates/app/src/main/resources/db/migration/liquibase/changelog/01-new_table_with_seq.yaml
new file mode 100644
index 0000000..6f6a6a9
--- /dev/null
+++ b/generators/controller/templates/app/src/main/resources/db/migration/liquibase/changelog/01-new_table_with_seq.yaml
@@ -0,0 +1,33 @@
+# https://docs.liquibase.com/concepts/changelogs/yaml-format.html
+databaseChangeLog:
+ - property:
+ dbms: postgresql
+ name: string.type
+ value: text
+ - property:
+ dbms: "!postgresql"
+ name: string.type
+ value: varchar(255)
+ - changeSet:
+ author: author
+ id: createTable-<%= tableName %>
+ changes:
+ - createSequence:
+ sequenceName: <%= tableName %>_seq
+ incrementBy: 50
+ startValue: 1
+ - createTable:
+ tableName: <%= tableName %>
+ columns:
+ - column:
+ name: id
+ type: bigint
+ defaultValueSequenceNext: <%= tableName %>_seq
+ constraints:
+ primaryKey: true
+ nullable: false
+ - column:
+ name: text
+ type: ${string.type}
+ constraints:
+ nullable: false
diff --git a/generators/controller/templates/app/src/test/java/services/ServiceTest.java b/generators/controller/templates/app/src/test/java/services/ServiceTest.java
new file mode 100644
index 0000000..bd31f93
--- /dev/null
+++ b/generators/controller/templates/app/src/test/java/services/ServiceTest.java
@@ -0,0 +1,71 @@
+package <%= packageName %>.services;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.BDDMockito.times;
+import static org.mockito.BDDMockito.verify;
+import static org.mockito.BDDMockito.willDoNothing;
+
+import <%= packageName %>.entities.<%= entityName %>;
+import <%= packageName %>.mapper.<%= entityName %>Mapper;
+import <%= packageName %>.model.query.Find<%= entityName %>sQuery;
+import <%= packageName %>.model.response.<%= entityName %>Response;
+import <%= packageName %>.model.response.PagedResult;
+import <%= packageName %>.repositories.<%= entityName %>Repository;
+import java.util.List;
+import java.util.Optional;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+
+@ExtendWith(MockitoExtension.class)
+class <%= entityName %>ServiceTest {
+
+ @Mock private <%= entityName %>Repository <%= entityVarName %>Repository;
+ @Mock private <%= entityName %>Mapper <%= entityVarName %>Mapper;
+
+ @InjectMocks private <%= entityName %>Service <%= entityVarName %>Service;
+
+ @Test
+ void find<%= entityName %>ById() {
+ // given
+ given(<%= entityVarName %>Repository.findById(1L)).willReturn(Optional.of(get<%= entityName %>()));
+ given(<%= entityVarName %>Mapper.toResponse(any(<%= entityName %>.class))).willReturn(get<%= entityName %>Response());
+ // when
+ Optional<<%= entityName %>Response> optional<%= entityName %> = <%= entityVarName %>Service.find<%= entityName %>ById(1L);
+ // then
+ assertThat(optional<%= entityName %>).isPresent();
+ <%= entityName %>Response <%= entityVarName %> = optional<%= entityName %>.get();
+ assertThat(<%= entityVarName %>.id()).isEqualTo(1L);
+ assertThat(<%= entityVarName %>.text()).isEqualTo("junitTest");
+ }
+
+ @Test
+ void delete<%= entityName %>ById() {
+ // given
+ willDoNothing().given(<%= entityVarName %>Repository).deleteById(1L);
+ // when
+ <%= entityVarName %>Service.delete<%= entityName %>ById(1L);
+ // then
+ verify(<%= entityVarName %>Repository, times(1)).deleteById(1L);
+ }
+
+ private <%= entityName %> get<%= entityName %>() {
+ <%= entityName %> <%= entityVarName %> = new <%= entityName %>();
+ <%= entityVarName %>.setId(1L);
+ <%= entityVarName %>.setText("junitTest");
+ return <%= entityVarName %>;
+ }
+
+ private <%= entityName %>Response get<%= entityName %>Response() {
+ return new <%= entityName %>Response(1L, "junitTest");
+ }
+}
diff --git a/generators/controller/templates/app/src/test/java/web/controllers/ControllerIT.java b/generators/controller/templates/app/src/test/java/web/controllers/ControllerIT.java
new file mode 100644
index 0000000..241d6fd
--- /dev/null
+++ b/generators/controller/templates/app/src/test/java/web/controllers/ControllerIT.java
@@ -0,0 +1,131 @@
+package <%= packageName %>.web.controllers;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.Matchers.hasSize;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import <%= packageName %>.common.AbstractIntegrationTest;
+import <%= packageName %>.entities.<%= entityName %>;
+import <%= packageName %>.model.request.<%= entityName %>Request;
+import <%= packageName %>.repositories.<%= entityName %>Repository;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+
+class <%= entityName %>ControllerIT extends AbstractIntegrationTest {
+
+ @Autowired private <%= entityName %>Repository <%= entityVarName %>Repository;
+
+ private List<<%= entityName %>> <%= entityVarName %>List = null;
+
+ @BeforeEach
+ void setUp() {
+ <%= entityVarName %>Repository.deleteAllInBatch();
+
+ <%= entityVarName %>List = new ArrayList<>();
+ <%= entityVarName %>List.add(new <%= entityName %>(null, "First <%= entityName %>"));
+ <%= entityVarName %>List.add(new <%= entityName %>(null, "Second <%= entityName %>"));
+ <%= entityVarName %>List.add(new <%= entityName %>(null, "Third <%= entityName %>"));
+ <%= entityVarName %>List = <%= entityVarName %>Repository.saveAll(<%= entityVarName %>List);
+ }
+
+ @Test
+ void shouldFetchAll<%= entityName %>s() throws Exception {
+ this.mockMvc
+ .perform(get("<%= basePath %>"))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.data.size()", is(<%= entityVarName %>List.size())))
+ .andExpect(jsonPath("$.totalElements", is(3)))
+ .andExpect(jsonPath("$.pageNumber", is(1)))
+ .andExpect(jsonPath("$.totalPages", is(1)))
+ .andExpect(jsonPath("$.isFirst", is(true)))
+ .andExpect(jsonPath("$.isLast", is(true)))
+ .andExpect(jsonPath("$.hasNext", is(false)))
+ .andExpect(jsonPath("$.hasPrevious", is(false)));
+ }
+
+ @Test
+ void shouldFind<%= entityName %>ById() throws Exception {
+ <%= entityName %> <%= entityVarName %> = <%= entityVarName %>List.get(0);
+ Long <%= entityVarName %>Id = <%= entityVarName %>.getId();
+
+ this.mockMvc
+ .perform(get("<%= basePath %>/{id}", <%= entityVarName %>Id))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.id", is(<%= entityVarName %>.getId()), Long.class))
+ .andExpect(jsonPath("$.text", is(<%= entityVarName %>.getText())));
+ }
+
+ @Test
+ void shouldCreateNew<%= entityName %>() throws Exception {
+ <%= entityName %>Request <%= entityVarName %>Request = new <%= entityName %>Request("New <%= entityName %>");
+ this.mockMvc
+ .perform(
+ post("<%= basePath %>")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(<%= entityVarName %>Request)))
+ .andExpect(status().isCreated())
+ .andExpect(header().exists(HttpHeaders.LOCATION))
+ .andExpect(jsonPath("$.id", notNullValue()))
+ .andExpect(jsonPath("$.text", is(<%= entityVarName %>Request.text())));
+ }
+
+ @Test
+ void shouldReturn400WhenCreateNew<%= entityName %>WithoutText() throws Exception {
+ <%= entityName %>Request <%= entityVarName %>Request = new <%= entityName %>Request(null);
+
+ this.mockMvc
+ .perform(
+ post("<%= basePath %>")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(<%= entityVarName %>Request)))
+ .andExpect(status().isBadRequest())
+ .andExpect(header().string("Content-Type", is("application/problem+json")))
+ .andExpect(jsonPath("$.type", is("about:blank")))
+ .andExpect(jsonPath("$.title", is("Constraint Violation")))
+ .andExpect(jsonPath("$.status", is(400)))
+ .andExpect(jsonPath("$.detail", is("Invalid request content.")))
+ .andExpect(jsonPath("$.instance", is("<%= basePath %>")))
+ .andExpect(jsonPath("$.violations", hasSize(1)))
+ .andExpect(jsonPath("$.violations[0].field", is("text")))
+ .andExpect(jsonPath("$.violations[0].message", is("Text cannot be empty")))
+ .andReturn();
+ }
+
+ @Test
+ void shouldUpdate<%= entityName %>() throws Exception {
+ Long <%= entityVarName %>Id = <%= entityVarName %>List.get(0).getId();
+ <%= entityName %>Request <%= entityVarName %>Request = new <%= entityName %>Request("Updated <%= entityName %>");
+
+ this.mockMvc
+ .perform(
+ put("<%= basePath %>/{id}", <%= entityVarName %>Id)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(<%= entityVarName %>Request)))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.id", is(<%= entityVarName %>Id), Long.class))
+ .andExpect(jsonPath("$.text", is(<%= entityVarName %>Request.text())));
+ }
+
+ @Test
+ void shouldDelete<%= entityName %>() throws Exception {
+ <%= entityName %> <%= entityVarName %> = <%= entityVarName %>List.get(0);
+
+ this.mockMvc
+ .perform(delete("<%= basePath %>/{id}", <%= entityVarName %>.getId()))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.id", is(<%= entityVarName %>.getId()), Long.class))
+ .andExpect(jsonPath("$.text", is(<%= entityVarName %>.getText())));
+ }
+}
diff --git a/generators/controller/templates/app/src/test/java/web/controllers/ControllerTest.java b/generators/controller/templates/app/src/test/java/web/controllers/ControllerTest.java
new file mode 100644
index 0000000..3615048
--- /dev/null
+++ b/generators/controller/templates/app/src/test/java/web/controllers/ControllerTest.java
@@ -0,0 +1,226 @@
+package <%= packageName %>.web.controllers;
+
+import static <%= packageName %>.utils.AppConstants.PROFILE_TEST;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.Matchers.hasSize;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.doNothing;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import <%= packageName %>.entities.<%= entityName %>;
+import <%= packageName %>.exception.<%= entityName %>NotFoundException;
+import <%= packageName %>.model.query.Find<%= entityName %>sQuery;
+import <%= packageName %>.model.request.<%= entityName %>Request;
+import <%= packageName %>.model.response.<%= entityName %>Response;
+import <%= packageName %>.model.response.PagedResult;
+import <%= packageName %>.services.<%= entityName %>Service;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.web.servlet.MockMvc;
+
+@WebMvcTest(controllers = <%= entityName %>Controller.class)
+@ActiveProfiles(PROFILE_TEST)
+class <%= entityName %>ControllerTest {
+
+ @Autowired private MockMvc mockMvc;
+
+ @MockBean private <%= entityName %>Service <%= entityVarName %>Service;
+
+ @Autowired private ObjectMapper objectMapper;
+
+ private List<<%= entityName %>> <%= entityVarName %>List;
+
+ @BeforeEach
+ void setUp() {
+ this.<%= entityVarName %>List = new ArrayList<>();
+ this.<%= entityVarName %>List.add(new <%= entityName %>(1L, "text 1"));
+ this.<%= entityVarName %>List.add(new <%= entityName %>(2L, "text 2"));
+ this.<%= entityVarName %>List.add(new <%= entityName %>(3L, "text 3"));
+ }
+
+ @Test
+ void shouldFetchAll<%= entityName %>s() throws Exception {
+
+ Page<<%= entityName %>> page = new PageImpl<>(<%= entityVarName %>List);
+ PagedResult<<%= entityName %>Response> <%= entityVarName %>PagedResult = new PagedResult<>(page, get<%= entityName %>ResponseList());
+ Find<%= entityName %>sQuery find<%= entityName %>sQuery = new Find<%= entityName %>sQuery(0, 10, "id", "asc");
+ given(<%= entityVarName %>Service.findAll<%= entityName %>s(find<%= entityName %>sQuery)).willReturn(<%= entityVarName %>PagedResult);
+
+ this.mockMvc
+ .perform(get("<%= basePath %>"))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.data.size()", is(<%= entityVarName %>List.size())))
+ .andExpect(jsonPath("$.totalElements", is(3)))
+ .andExpect(jsonPath("$.pageNumber", is(1)))
+ .andExpect(jsonPath("$.totalPages", is(1)))
+ .andExpect(jsonPath("$.isFirst", is(true)))
+ .andExpect(jsonPath("$.isLast", is(true)))
+ .andExpect(jsonPath("$.hasNext", is(false)))
+ .andExpect(jsonPath("$.hasPrevious", is(false)));
+ }
+
+ @Test
+ void shouldFind<%= entityName %>ById() throws Exception {
+ Long <%= entityVarName %>Id = 1L;
+ <%= entityName %>Response <%= entityVarName %> = new <%= entityName %>Response(<%= entityVarName %>Id, "text 1");
+ given(<%= entityVarName %>Service.find<%= entityName %>ById(<%= entityVarName %>Id)).willReturn(Optional.of(<%= entityVarName %>));
+
+ this.mockMvc
+ .perform(get("<%= basePath %>/{id}", <%= entityVarName %>Id))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.text", is(<%= entityVarName %>.text())));
+ }
+
+ @Test
+ void shouldReturn404WhenFetchingNonExisting<%= entityName %>() throws Exception {
+ Long <%= entityVarName %>Id = 1L;
+ given(<%= entityVarName %>Service.find<%= entityName %>ById(<%= entityVarName %>Id)).willReturn(Optional.empty());
+
+ this.mockMvc.perform(get("<%= basePath %>/{id}", <%= entityVarName %>Id))
+ .andExpect(status().isNotFound())
+ .andExpect(header().string("Content-Type", is(MediaType.APPLICATION_PROBLEM_JSON_VALUE)))
+ .andExpect(jsonPath("$.type", is("http://api.<%= appName %>.com/errors/not-found")))
+ .andExpect(jsonPath("$.title", is("Not Found")))
+ .andExpect(jsonPath("$.status", is(404)))
+ .andExpect(
+ jsonPath("$.detail")
+ .value("<%= entityName %> with Id '%d' not found".formatted(<%= entityVarName %>Id)));
+ }
+
+ @Test
+ void shouldCreateNew<%= entityName %>() throws Exception {
+
+ <%= entityName %>Response <%= entityVarName %> = new <%= entityName %>Response(1L, "some text");
+ <%= entityName %>Request <%= entityVarName %>Request = new <%= entityName %>Request("some text");
+ given(<%= entityVarName %>Service.save<%= entityName %>(any(<%= entityName %>Request.class)))
+ .willReturn(<%= entityVarName %>);
+
+ this.mockMvc
+ .perform(
+ post("<%= basePath %>")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(<%= entityVarName %>Request)))
+ .andExpect(status().isCreated())
+ .andExpect(header().exists(HttpHeaders.LOCATION))
+ .andExpect(jsonPath("$.id", notNullValue()))
+ .andExpect(jsonPath("$.text", is(<%= entityVarName %>.text())));
+ }
+
+ @Test
+ void shouldReturn400WhenCreateNew<%= entityName %>WithoutText() throws Exception {
+ <%= entityName %>Request <%= entityVarName %>Request = new <%= entityName %>Request(null);
+
+ this.mockMvc
+ .perform(
+ post("<%= basePath %>")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(<%= entityVarName %>Request)))
+ .andExpect(status().isBadRequest())
+ .andExpect(header().string("Content-Type", is("application/problem+json")))
+ .andExpect(jsonPath("$.type", is("about:blank")))
+ .andExpect(jsonPath("$.title", is("Constraint Violation")))
+ .andExpect(jsonPath("$.status", is(400)))
+ .andExpect(jsonPath("$.detail", is("Invalid request content.")))
+ .andExpect(jsonPath("$.instance", is("<%= basePath %>")))
+ .andExpect(jsonPath("$.violations", hasSize(1)))
+ .andExpect(jsonPath("$.violations[0].field", is("text")))
+ .andExpect(jsonPath("$.violations[0].message", is("Text cannot be empty")))
+ .andReturn();
+ }
+
+ @Test
+ void shouldUpdate<%= entityName %>() throws Exception {
+ Long <%= entityVarName %>Id = 1L;
+ <%= entityName %>Response <%= entityVarName %> = new <%= entityName %>Response(<%= entityVarName %>Id, "Updated text");
+ <%= entityName %>Request <%= entityVarName %>Request = new <%= entityName %>Request("Updated text");
+ given(<%= entityVarName %>Service.update<%= entityName %>(eq(<%= entityVarName %>Id), any(<%= entityName %>Request.class)))
+ .willReturn(<%= entityVarName %>);
+
+ this.mockMvc
+ .perform(
+ put("<%= basePath %>/{id}", <%= entityVarName %>Id)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(<%= entityVarName %>Request)))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.id", is(<%= entityVarName %>Id), Long.class))
+ .andExpect(jsonPath("$.text", is(<%= entityVarName %>.text())));
+ }
+
+ @Test
+ void shouldReturn404WhenUpdatingNonExisting<%= entityName %>() throws Exception {
+ Long <%= entityVarName %>Id = 1L;
+ <%= entityName %>Request <%= entityVarName %>Request = new <%= entityName %>Request("Updated text");
+ given(<%= entityVarName %>Service.update<%= entityName %>(eq(<%= entityVarName %>Id), any(<%= entityName %>Request.class)))
+ .willThrow(new <%= entityName %>NotFoundException(<%= entityVarName %>Id));
+
+ this.mockMvc
+ .perform(
+ put("<%= basePath %>/{id}", <%= entityVarName %>Id)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(<%= entityVarName %>Request)))
+ .andExpect(status().isNotFound())
+ .andExpect(header().string("Content-Type", is(MediaType.APPLICATION_PROBLEM_JSON_VALUE)))
+ .andExpect(jsonPath("$.type", is("http://api.<%= appName %>.com/errors/not-found")))
+ .andExpect(jsonPath("$.title", is("Not Found")))
+ .andExpect(jsonPath("$.status", is(404)))
+ .andExpect(
+ jsonPath("$.detail")
+ .value("<%= entityName %> with Id '%d' not found".formatted(<%= entityVarName %>Id)));
+ }
+
+ @Test
+ void shouldDelete<%= entityName %>() throws Exception {
+ Long <%= entityVarName %>Id = 1L;
+ <%= entityName %>Response <%= entityVarName %> = new <%= entityName %>Response(<%= entityVarName %>Id, "Some text");
+ given(<%= entityVarName %>Service.find<%= entityName %>ById(<%= entityVarName %>Id)).willReturn(Optional.of(<%= entityVarName %>));
+ doNothing().when(<%= entityVarName %>Service).delete<%= entityName %>ById(<%= entityVarName %>Id);
+
+ this.mockMvc
+ .perform(delete("<%= basePath %>/{id}", <%= entityVarName %>Id))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.text", is(<%= entityVarName %>.text())));
+ }
+
+ @Test
+ void shouldReturn404WhenDeletingNonExisting<%= entityName %>() throws Exception {
+ Long <%= entityVarName %>Id = 1L;
+ given(<%= entityVarName %>Service.find<%= entityName %>ById(<%= entityVarName %>Id)).willReturn(Optional.empty());
+
+ this.mockMvc
+ .perform(delete("<%= basePath %>/{id}", <%= entityVarName %>Id))
+ .andExpect(header().string("Content-Type", is(MediaType.APPLICATION_PROBLEM_JSON_VALUE)))
+ .andExpect(jsonPath("$.type", is("http://api.<%= appName %>.com/errors/not-found")))
+ .andExpect(jsonPath("$.title", is("Not Found")))
+ .andExpect(jsonPath("$.status", is(404)))
+ .andExpect(
+ jsonPath("$.detail")
+ .value("<%= entityName %> with Id '%d' not found".formatted(<%= entityVarName %>Id)));
+ }
+
+ List<<%= entityName %>Response> get<%= entityName %>ResponseList() {
+ return <%= entityVarName %>List.stream()
+ .map(<%= entityVarName %> -> new <%= entityName %>Response(<%= entityVarName %>.getId(), <%= entityVarName %>.getText()))
+ .toList();
+ }
+}
diff --git a/generators/server/index.js b/generators/server/index.js
new file mode 100644
index 0000000..03c7235
--- /dev/null
+++ b/generators/server/index.js
@@ -0,0 +1,303 @@
+'use strict';
+const BaseGenerator = require('../base-generator');
+const constants = require('../constants');
+const prompts = require('./prompts');
+const path = require('path');
+
+module.exports = class extends BaseGenerator {
+
+ constructor(args, opts) {
+ super(args, opts);
+ this.configOptions = this.options.configOptions || {};
+ }
+
+ initializing() {
+ this.logSuccess('Generating SpringBoot Application')
+ }
+
+ get prompting() {
+ return prompts.prompting;
+ }
+
+ configuring() {
+ this.destinationRoot(path.join(this.destinationRoot(), '/'+this.configOptions.appName));
+ this.config.set(this.configOptions);
+ Object.assign(this.configOptions, constants);
+ this.configOptions.formatCode = this.options.formatCode !== false
+ }
+
+ writing() {
+ this._generateBuildToolConfig(this.configOptions);
+ this._generateDockerConfig(this.configOptions);
+ this._generateJenkinsFile(this.configOptions);
+ this._generateMiscFiles(this.configOptions);
+ this._generateGithubActionsFiles(this.configOptions);
+ this._generateDbMigrationConfig(this.configOptions);
+ this._generateDockerComposeFiles(this.configOptions);
+ this._generateLocalstackConfig(this.configOptions);
+ this._generateAppCode(this.configOptions);
+ }
+
+ end() {
+ if(this.configOptions.formatCode !== false) {
+ this._formatCode(this.configOptions, this.configOptions.appName);
+ }
+ this._printGenerationSummary(this.configOptions);
+ }
+
+ _printGenerationSummary(configOptions) {
+ this.logError("==========================================");
+ this.logSuccess("Your application is generated successfully");
+ this.logSuccess(` cd ${configOptions.appName}`);
+ if (configOptions.buildTool === 'maven') {
+ this.logSuccess(" > ./mvnw spring-boot:run");
+ } else {
+ this.logSuccess(" > ./gradlew bootRun");
+ }
+ this.logError("==========================================");
+ }
+
+ _generateBuildToolConfig(configOptions) {
+ if (configOptions.buildTool === 'maven') {
+ this._generateMavenConfig(configOptions);
+ } else {
+ this._generateGradleConfig(configOptions);
+ }
+ }
+
+ _generateDockerConfig(configOptions) {
+ this.fs.copyTpl(
+ this.templatePath('app/Dockerfile'),
+ this.destinationPath('Dockerfile'),
+ configOptions
+ );
+ }
+
+ _generateJenkinsFile(configOptions) {
+ this.fs.copyTpl(
+ this.templatePath('app/Jenkinsfile'),
+ this.destinationPath('Jenkinsfile'),
+ configOptions
+ );
+ }
+
+ _generateMiscFiles(configOptions) {
+ this.fs.copyTpl(this.templatePath('app/lombok.config'), this.destinationPath('lombok.config'), configOptions);
+ this.fs.copyTpl(this.templatePath('app/sonar-project.properties'), this.destinationPath('sonar-project.properties'), configOptions);
+ this.fs.copyTpl(this.templatePath('app/README.md'), this.destinationPath('README.md'), configOptions);
+ }
+
+ _generateGithubActionsFiles(configOptions) {
+ const ciFile = '.github/workflows/' + configOptions.buildTool + '.yml';
+
+ this.fs.copyTpl(
+ this.templatePath('app/' + ciFile),
+ this.destinationPath(ciFile),
+ configOptions
+ );
+ }
+
+ _generateMavenConfig(configOptions) {
+ this._copyMavenWrapper(configOptions);
+ this._generateMavenPOMXml(configOptions);
+ }
+
+ _generateGradleConfig(configOptions) {
+ this._copyGradleWrapper(configOptions);
+ this._generateGradleBuildScript(configOptions);
+ }
+
+ _copyMavenWrapper(configOptions) {
+ const commonMavenConfigDir = '../../common/files/maven/';
+
+ ['mvnw', 'mvnw.cmd'].forEach(tmpl => {
+ this.fs.copyTpl(
+ this.templatePath(commonMavenConfigDir + tmpl),
+ this.destinationPath(tmpl)
+ );
+ });
+
+ this.fs.copyTpl(
+ this.templatePath(commonMavenConfigDir + 'gitignore'),
+ this.destinationPath('.gitignore')
+ );
+
+ this.fs.copy(
+ this.templatePath(commonMavenConfigDir + '.mvn'),
+ this.destinationPath('.mvn')
+ );
+
+ }
+
+ _generateMavenPOMXml(configOptions) {
+ const mavenConfigDir = 'maven/';
+ this.fs.copyTpl(
+ this.templatePath(mavenConfigDir + 'pom.xml'),
+ this.destinationPath('pom.xml'),
+ configOptions
+ );
+ }
+
+ _copyGradleWrapper(configOptions) {
+ const commonGradleConfigDir = '../../common/files/gradle/';
+
+ ['gradlew', 'gradlew.bat'].forEach(tmpl => {
+ this.fs.copyTpl(
+ this.templatePath(commonGradleConfigDir + tmpl),
+ this.destinationPath(tmpl)
+ );
+ });
+
+ this.fs.copyTpl(
+ this.templatePath(commonGradleConfigDir + 'gitignore'),
+ this.destinationPath('.gitignore')
+ );
+
+ this.fs.copy(
+ this.templatePath(commonGradleConfigDir + 'gradle'),
+ this.destinationPath('gradle')
+ );
+ }
+
+ _generateGradleBuildScript(configOptions) {
+ const gradleConfigDir = 'gradle/';
+
+ ['build.gradle', 'settings.gradle', 'gradle.properties'].forEach(tmpl => {
+ this.fs.copyTpl(
+ this.templatePath(gradleConfigDir + tmpl),
+ this.destinationPath(tmpl),
+ configOptions
+ );
+ });
+ ['code-quality.gradle', 'owasp.gradle'].forEach(tmpl => {
+ this.fs.copyTpl(
+ this.templatePath(gradleConfigDir + tmpl),
+ this.destinationPath('gradle/' + tmpl),
+ configOptions
+ );
+ });
+ }
+
+ _generateAppCode(configOptions) {
+
+ const mainJavaTemplates = [
+ 'Application.java',
+ 'config/WebMvcConfig.java',
+ 'config/SwaggerConfig.java',
+ 'config/ApplicationProperties.java',
+ 'config/Initializer.java',
+ 'config/GlobalExceptionHandler.java',
+ 'config/logging/Loggable.java',
+ 'config/logging/LoggingAspect.java',
+ 'exception/ResourceNotFoundException.java',
+ 'model/response/PagedResult.java',
+ 'utils/AppConstants.java'
+ ];
+ this.generateMainJavaCode(configOptions, mainJavaTemplates);
+
+ const mainResTemplates = [
+ 'application.properties',
+ 'application-local.properties',
+ 'logback-spring.xml'
+ ];
+ this.generateMainResCode(configOptions, mainResTemplates);
+
+ const testJavaTemplates = [
+ 'ApplicationIntegrationTest.java',
+ 'SchemaValidationTest.java',
+ 'common/ContainersConfig.java',
+ 'common/AbstractIntegrationTest.java',
+ 'TestApplication.java'
+ ];
+ if(configOptions.features.includes("localstack")) {
+ testJavaTemplates.push('SqsListenerIntegrationTest.java');
+ }
+ this.generateTestJavaCode(configOptions, testJavaTemplates);
+
+ const testResTemplates = [
+ 'application-test.properties',
+ 'logback-test.xml'
+ ];
+ this.generateTestResCode(configOptions, testResTemplates);
+ }
+
+ _generateDbMigrationConfig(configOptions) {
+ if(configOptions.dbMigrationTool === 'flywaydb') {
+ let vendor = configOptions.databaseType;
+ const resTemplates = [
+ {src: 'db/migration/flyway/V1__01_init.sql', dest: 'db/migration/'+ vendor +'/V1__01_init.sql'},
+
+ ];
+ this.generateFiles(configOptions, resTemplates, 'app/','src/main/resources/');
+ const flywayMigrantCounter = {
+ [constants.KEY_FLYWAY_MIGRATION_COUNTER]: 1
+ };
+ Object.assign(configOptions, flywayMigrantCounter);
+ this.config.set(flywayMigrantCounter);
+ }
+
+ if(configOptions.dbMigrationTool === 'liquibase') {
+ const dbFmt = configOptions.dbMigrationFormat || 'xml';
+ const resTemplates = [
+ {src: 'db/migration/liquibase/changelog/db.changelog-master.yaml', dest: 'db/changelog/db.changelog-master.yaml'},
+ {src: `db/migration/liquibase/changelog/01-init.${dbFmt}`, dest: `db/changelog/migration/01-init.${dbFmt}`},
+
+ ];
+ this.generateFiles(configOptions, resTemplates, 'app/','src/main/resources/');
+ const liquibaseMigrantCounter = {
+ [constants.KEY_LIQUIBASE_MIGRATION_COUNTER]: 1
+ };
+ Object.assign(configOptions, liquibaseMigrantCounter);
+ this.config.set(liquibaseMigrantCounter);
+ }
+ }
+
+ _generateLocalstackConfig(configOptions) {
+ if(configOptions.features.includes('localstack')) {
+ this.fs.copy(
+ this.templatePath('app/.localstack'),
+ this.destinationPath('./.localstack')
+ );
+ }
+ }
+
+ _generateDockerComposeFiles(configOptions) {
+ this._generateAppDockerComposeFile(configOptions);
+ if(configOptions.features.includes('monitoring')) {
+ this._generateMonitoringConfig(configOptions);
+ }
+ if(configOptions.features.includes('elk')) {
+ this._generateELKConfig(configOptions);
+ }
+ }
+
+ _generateAppDockerComposeFile(configOptions) {
+ const resTemplates = [
+ 'docker-compose.yml',
+ 'docker-compose-app.yml',
+ ];
+ this.generateFiles(configOptions, resTemplates, 'app/','docker/');
+ }
+
+ _generateELKConfig(configOptions) {
+ const resTemplates = [
+ 'docker/docker-compose-elk.yml',
+ 'config/elk/logstash.conf',
+ ];
+ this.generateFiles(configOptions, resTemplates, 'app/','./');
+ }
+
+ _generateMonitoringConfig(configOptions) {
+ const resTemplates = [
+ 'docker/docker-compose-monitoring.yml',
+ 'config/prometheus/prometheus.yml',
+ ];
+ this.generateFiles(configOptions, resTemplates, 'app/','./');
+
+ this.fs.copy(
+ this.templatePath('app/config/grafana'),
+ this.destinationPath('config/grafana')
+ );
+ }
+
+};
diff --git a/generators/server/prompts.js b/generators/server/prompts.js
new file mode 100644
index 0000000..723eaea
--- /dev/null
+++ b/generators/server/prompts.js
@@ -0,0 +1,139 @@
+
+module.exports = {
+ prompting
+};
+
+function prompting() {
+
+ const done = this.async();
+
+ const prompts = [
+ {
+ type: 'string',
+ name: 'appName',
+ validate: input =>
+ /^([a-z_][a-z0-9_\-]*)$/.test(input)
+ ? true
+ : 'The application name you have provided is not valid',
+ message: 'What is the application name?',
+ default: 'myservice'
+ },
+ {
+ type: 'string',
+ name: 'packageName',
+ validate: input =>
+ /^([a-z_][a-z0-9_]*(\.[a-z_][a-z0-9_]*)*)$/.test(input)
+ ? true
+ : 'The package name you have provided is not a valid Java package name.',
+ message: 'What is the default package name?',
+ default: 'com.mycompany.myservice'
+ },
+ {
+ type: 'list',
+ name: 'databaseType',
+ message: 'Which type of database you want to use?',
+ choices: [
+ {
+ value: 'postgresql',
+ name: 'Postgresql'
+ },
+ {
+ value: 'mysql',
+ name: 'MySQL'
+ },
+ {
+ value: 'mariadb',
+ name: 'MariaDB'
+ },
+ {
+ value: 'mongo',
+ name: 'mongo'
+ }
+ ],
+ default: 'postgresql'
+ },
+ {
+ type: 'list',
+ name: 'dbMigrationTool',
+ message: 'Which type of database migration tool you want to use?',
+ choices: [
+ {
+ value: 'flywaydb',
+ name: 'FlywayDB'
+ },
+ {
+ value: 'liquibase',
+ name: 'Liquibase'
+ },
+ {
+ value: 'none',
+ name: 'None'
+ }
+ ],
+ default: 'flywaydb'
+ },
+ {
+ when: (answers) => answers.dbMigrationTool === 'liquibase',
+ type: 'list',
+ name: 'dbMigrationFormat',
+ message: 'Which format do you want to use for database migrations?',
+ choices: [
+ {
+ value: 'xml',
+ name: 'XML (like \'001-init.xml\')'
+ },
+ {
+ value: 'yaml',
+ name: 'YAML (like \'001-init.yaml\')'
+ },
+ {
+ value: 'sql',
+ name: 'SQL (like \'001-init.sql\')'
+ }
+ ],
+ default: 'xml'
+ },
+ {
+ type: 'checkbox',
+ name: 'features',
+ message: 'Select the features you want?',
+ choices: [
+ {
+ value: 'elk',
+ name: 'ELK Docker configuration'
+ },
+ {
+ value: 'monitoring',
+ name: 'Prometheus, Grafana Docker configuration'
+ },
+ {
+ value: 'localstack',
+ name: 'Localstack Docker configuration'
+ }
+ ]
+ },
+ {
+ type: 'list',
+ name: 'buildTool',
+ message: 'Which build tool do you want to use?',
+ choices: [
+ {
+ value: 'maven',
+ name: 'Maven'
+ },
+ {
+ value: 'gradle',
+ name: 'Gradle'
+ }
+ ],
+ default: 'maven'
+ }
+ ];
+
+ this.prompt(prompts).then(answers => {
+ Object.assign(this.configOptions, answers);
+ this.configOptions.packageFolder = this.configOptions.packageName.replace(/\./g, '/');
+ this.configOptions.features = this.configOptions.features || [];
+ done();
+ });
+}
diff --git a/generators/server/templates/app/.github/workflows/gradle.yml b/generators/server/templates/app/.github/workflows/gradle.yml
new file mode 100644
index 0000000..cf22fc7
--- /dev/null
+++ b/generators/server/templates/app/.github/workflows/gradle.yml
@@ -0,0 +1,43 @@
+name: CI Build
+
+on:
+ push:
+ branches:
+ - "**"
+
+jobs:
+ build:
+ name: Build
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ distribution: [ 'temurin' ]
+ java: [ '<%= JAVA_VERSION %>' ]
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Setup Java <%= JAVA_VERSION %>
+ uses: actions/setup-java@v3
+ with:
+ java-version: ${{ matrix.java }}
+ distribution: ${{ matrix.distribution }}
+ cache: 'gradle'
+
+ - name: Grant execute permission for gradlew
+ run: chmod +x gradlew
+
+ - name: Build with Gradle
+ run: ./gradlew build
+
+ - if: ${{ github.ref == 'refs/heads/main' }}
+ name: SonarQube Scan
+ run: ./gradlew sonarqube
+ env:
+ SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+
+ - if: ${{ github.ref == 'refs/heads/main' }}
+ name: Build and Publish Docker Image
+ run: |
+ docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
+ ./gradlew bootBuildImage --imageName=${{ secrets.DOCKER_USERNAME }}/<%= appName %>
+ docker push ${{ secrets.DOCKER_USERNAME }}/<%= appName %>
diff --git a/generators/server/templates/app/.github/workflows/maven.yml b/generators/server/templates/app/.github/workflows/maven.yml
new file mode 100644
index 0000000..6b7eaf8
--- /dev/null
+++ b/generators/server/templates/app/.github/workflows/maven.yml
@@ -0,0 +1,43 @@
+name: CI Build
+
+on:
+ push:
+ branches:
+ - "**"
+
+jobs:
+ build:
+ name: Build
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ distribution: [ 'temurin' ]
+ java: [ '<%= JAVA_VERSION %>' ]
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Setup Java <%= JAVA_VERSION %>
+ uses: actions/setup-java@v3
+ with:
+ java-version: ${{ matrix.java }}
+ distribution: ${{ matrix.distribution }}
+ cache: 'maven'
+
+ - name: Grant execute permission for mvnw
+ run: chmod +x mvnw
+
+ - name: Build with Maven
+ run: ./mvnw clean verify
+
+ - if: ${{ github.ref == 'refs/heads/main' }}
+ name: SonarQube Scan
+ run: ./mvnw compile sonar:sonar -Dsonar.login=${{ secrets.SONAR_TOKEN }}
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - if: ${{ github.ref == 'refs/heads/main' }}
+ name: Build and Publish Docker Image
+ run: |
+ docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
+ ./mvnw spring-boot:build-image -Dspring-boot.build-image.imageName=${{ secrets.DOCKER_USERNAME }}/<%= appName %>
+ docker push ${{ secrets.DOCKER_USERNAME }}/<%= appName %>
diff --git a/generators/server/templates/app/.localstack/01_init.sh b/generators/server/templates/app/.localstack/01_init.sh
new file mode 100644
index 0000000..828fd01
--- /dev/null
+++ b/generators/server/templates/app/.localstack/01_init.sh
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+
+awslocal s3 mb s3://testbucket
+echo "List of S3 buckets:"
+echo "-------------------------------"
+awslocal s3 ls
+
+awslocal sqs create-queue --queue-name test_queue
+echo "List of SQS Queues:"
+echo "-------------------------------"
+awslocal sqs list-queues
diff --git a/generators/server/templates/app/Dockerfile b/generators/server/templates/app/Dockerfile
new file mode 100644
index 0000000..8dd4e86
--- /dev/null
+++ b/generators/server/templates/app/Dockerfile
@@ -0,0 +1,19 @@
+FROM eclipse-temurin:17.0.9_9-jre-focal as builder
+WORKDIR application
+<%_ if (buildTool === 'maven') { _%>
+ARG JAR_FILE=target/<%= appName %>-<%= DEFAULT_APP_VERSION %>.jar
+<%_ } _%>
+<%_ if (buildTool === 'gradle') { _%>
+ARG JAR_FILE=build/libs/<%= appName %>-<%= DEFAULT_APP_VERSION %>.jar
+<%_ } _%>
+COPY ${JAR_FILE} application.jar
+RUN java -Djarmode=layertools -jar application.jar extract
+
+# the second stage of our build will copy the extracted layers
+FROM eclipse-temurin:17.0.9_9-jre-focal
+WORKDIR application
+COPY --from=builder application/dependencies/ ./
+COPY --from=builder application/spring-boot-loader/ ./
+COPY --from=builder application/snapshot-dependencies/ ./
+COPY --from=builder application/application/ ./
+ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]
diff --git a/generators/server/templates/app/Jenkinsfile b/generators/server/templates/app/Jenkinsfile
new file mode 100644
index 0000000..02c6c36
--- /dev/null
+++ b/generators/server/templates/app/Jenkinsfile
@@ -0,0 +1,24 @@
+pipeline {
+ agent any
+
+ triggers {
+ pollSCM('* * * * *')
+ }
+
+ environment {
+ APPLICATION_NAME = '<%= appName %>'
+ }
+
+ stages {
+ stage('Build') {
+ steps {
+ <%_ if (buildTool === 'maven') { _%>
+ sh './mvnw clean verify'
+ <%_ } _%>
+ <%_ if (buildTool === 'gradle') { _%>
+ sh './gradlew clean build'
+ <%_ } _%>
+ }
+ }
+ }
+}
diff --git a/generators/server/templates/app/README.md b/generators/server/templates/app/README.md
new file mode 100644
index 0000000..3e50721
--- /dev/null
+++ b/generators/server/templates/app/README.md
@@ -0,0 +1,68 @@
+# <%= appName %>
+
+<%_ if (buildTool === 'maven') { _%>
+### Format code
+
+```shell
+$ ./mvnw spotless:apply
+```
+
+### Run tests
+
+```shell
+$ ./mvnw clean verify
+```
+
+### Run locally
+
+```shell
+$ docker-compose -f docker/docker-compose.yml up -d
+$ ./mvnw spring-boot:run -Dspring-boot.run.profiles=local
+```
+
+### Using Testcontainers at Development Time
+You can run `TestApplication.java` from your IDE directly.
+You can also run the application using Maven as follows:
+
+```shell
+./mvnw spring-boot:test-run
+```
+<%_ } _%>
+
+<%_ if (buildTool === 'gradle') { _%>
+### Format code
+
+```shell
+$ ./gradlew spotlessApply
+```
+
+### Run tests
+
+```shell
+$ ./gradlew clean build
+```
+
+### Run locally
+
+```shell
+$ docker-compose -f docker/docker-compose.yml up -d
+$ ./gradlew bootRun -Plocal
+```
+
+### Using Testcontainers at Development Time
+You can run `TestApplication.java` from your IDE directly.
+You can also run the application using Gradle as follows:
+
+```shell
+$ ./gradlew bootTestRun
+```
+<%_ } _%>
+
+### Useful Links
+* Swagger UI: http://localhost:8080/swagger-ui.html
+* Actuator Endpoint: http://localhost:8080/actuator
+<%_ if (features.includes('elk')) { _%>
+* Prometheus: http://localhost:9090/
+* Grafana: http://localhost:3000/ (admin/admin)
+* Kibana: http://localhost:5601/
+<%_ } _%>
diff --git a/generators/server/templates/app/config/elk/logstash.conf b/generators/server/templates/app/config/elk/logstash.conf
new file mode 100644
index 0000000..97bc2fb
--- /dev/null
+++ b/generators/server/templates/app/config/elk/logstash.conf
@@ -0,0 +1,14 @@
+input {
+ tcp {
+ port => 5000
+ codec => json_lines
+ }
+}
+
+## Add your filters / logstash plugins configuration here
+
+output {
+ elasticsearch {
+ hosts => "elasticsearch:9200"
+ }
+}
diff --git a/generators/server/templates/app/config/grafana/provisioning/dashboards/basic-dashboard.json b/generators/server/templates/app/config/grafana/provisioning/dashboards/basic-dashboard.json
new file mode 100644
index 0000000..3d951fb
--- /dev/null
+++ b/generators/server/templates/app/config/grafana/provisioning/dashboards/basic-dashboard.json
@@ -0,0 +1,1037 @@
+{
+ "annotations": {
+ "list": [
+ {
+ "builtIn": 1,
+ "datasource": {
+ "type": "datasource",
+ "uid": "grafana"
+ },
+ "enable": true,
+ "hide": true,
+ "iconColor": "rgba(0, 211, 255, 1)",
+ "name": "Annotations & Alerts",
+ "type": "dashboard"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "enable": true,
+ "expr": "resets(process_uptime_seconds{application=\"$application\", instance=\"$instance\"}[1m]) > 0",
+ "hide": false,
+ "iconColor": "rgba(255, 96, 96, 1)",
+ "limit": 100,
+ "name": "Restart Detection",
+ "showIn": 0,
+ "step": "1m",
+ "tagKeys": "restart-tag",
+ "tags": [],
+ "textFormat": "uptime-restart",
+ "titleFormat": "Restart",
+ "type": "tags"
+ }
+ ]
+ },
+ "editable": true,
+ "fiscalYearStartMonth": 0,
+ "graphTooltip": 0,
+ "links": [],
+ "liveNow": false,
+ "panels": [
+ {
+ "collapsed": false,
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 0
+ },
+ "id": 8,
+ "panels": [],
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "refId": "A"
+ }
+ ],
+ "title": "Basic Stats",
+ "type": "row"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "decimals": 1,
+ "mappings": [
+ {
+ "options": {
+ "match": "null",
+ "result": {
+ "text": "N/A"
+ }
+ },
+ "type": "special"
+ }
+ ],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "s"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 3,
+ "w": 6,
+ "x": 0,
+ "y": 1
+ },
+ "id": 12,
+ "links": [],
+ "maxDataPoints": 100,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "none",
+ "justifyMode": "auto",
+ "orientation": "horizontal",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "10.0.0",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "process_uptime_seconds{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "intervalFactor": 1,
+ "refId": "A"
+ }
+ ],
+ "title": "Uptime",
+ "type": "stat"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [
+ {
+ "options": {
+ "match": "null",
+ "result": {
+ "color": "#d44a3a",
+ "text": "0"
+ }
+ },
+ "type": "special"
+ }
+ ],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "#d44a3a",
+ "value": null
+ },
+ {
+ "color": "rgba(237, 129, 40, 0.89)",
+ "value": 10
+ },
+ {
+ "color": "#299c46",
+ "value": 50
+ }
+ ]
+ },
+ "unit": "none"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 6,
+ "w": 4,
+ "x": 6,
+ "y": 1
+ },
+ "id": 20,
+ "links": [],
+ "maxDataPoints": 100,
+ "options": {
+ "colorMode": "background",
+ "graphMode": "none",
+ "justifyMode": "auto",
+ "orientation": "horizontal",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "10.0.0",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "sum(http_server_requests_seconds_count{instance=\"$instance\", application=\"$application\", status=~\"2..\"})",
+ "format": "time_series",
+ "intervalFactor": 1,
+ "legendFormat": "",
+ "refId": "A"
+ }
+ ],
+ "title": "2XX Success Count",
+ "type": "stat"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [
+ {
+ "options": {
+ "match": "null",
+ "result": {
+ "color": "#299c46",
+ "text": "0"
+ }
+ },
+ "type": "special"
+ }
+ ],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "#299c46",
+ "value": null
+ },
+ {
+ "color": "rgba(237, 129, 40, 0.89)",
+ "value": 10
+ },
+ {
+ "color": "#d44a3a",
+ "value": 50
+ }
+ ]
+ },
+ "unit": "none"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 6,
+ "w": 4,
+ "x": 10,
+ "y": 1
+ },
+ "id": 22,
+ "links": [],
+ "maxDataPoints": 100,
+ "options": {
+ "colorMode": "background",
+ "graphMode": "none",
+ "justifyMode": "auto",
+ "orientation": "horizontal",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "10.0.0",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "sum(http_server_requests_seconds_count{instance=\"$instance\", application=\"$application\", status=~\"4..\"})",
+ "format": "time_series",
+ "intervalFactor": 1,
+ "legendFormat": "",
+ "refId": "A"
+ }
+ ],
+ "title": "4XX Error Count",
+ "type": "stat"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [
+ {
+ "options": {
+ "match": "null",
+ "result": {
+ "color": "#299c46",
+ "text": "0"
+ }
+ },
+ "type": "special"
+ }
+ ],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "#299c46",
+ "value": null
+ },
+ {
+ "color": "rgba(237, 129, 40, 0.89)",
+ "value": 5
+ },
+ {
+ "color": "#d44a3a",
+ "value": 10
+ }
+ ]
+ },
+ "unit": "none"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 6,
+ "w": 4,
+ "x": 14,
+ "y": 1
+ },
+ "id": 21,
+ "links": [],
+ "maxDataPoints": 100,
+ "options": {
+ "colorMode": "background",
+ "graphMode": "none",
+ "justifyMode": "auto",
+ "orientation": "horizontal",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "10.0.0",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "sum(http_server_requests_seconds_count{instance=\"$instance\", application=\"$application\", status=~\"5..\"})",
+ "format": "time_series",
+ "intervalFactor": 1,
+ "legendFormat": "",
+ "refId": "A"
+ }
+ ],
+ "title": "5XX Error Count",
+ "type": "stat"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [
+ {
+ "options": {
+ "match": "null",
+ "result": {
+ "text": "N/A"
+ }
+ },
+ "type": "special"
+ }
+ ],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "dateTimeAsIso"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 3,
+ "w": 6,
+ "x": 0,
+ "y": 4
+ },
+ "id": 14,
+ "links": [],
+ "maxDataPoints": 100,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "none",
+ "justifyMode": "auto",
+ "orientation": "horizontal",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "10.0.0",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "process_start_time_seconds{application=\"$application\", instance=\"$instance\"}*1000",
+ "format": "time_series",
+ "intervalFactor": 1,
+ "refId": "A"
+ }
+ ],
+ "title": "Start Time",
+ "type": "stat"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "decimals": 1,
+ "mappings": [
+ {
+ "options": {
+ "match": "null",
+ "result": {
+ "text": "N/A"
+ }
+ },
+ "type": "special"
+ }
+ ],
+ "max": 100,
+ "min": 0,
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "#299c46",
+ "value": null
+ },
+ {
+ "color": "rgba(237, 129, 40, 0.89)",
+ "value": 70
+ },
+ {
+ "color": "#d44a3a",
+ "value": 90
+ }
+ ]
+ },
+ "unit": "percent"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 6,
+ "w": 5,
+ "x": 0,
+ "y": 7
+ },
+ "id": 4,
+ "links": [],
+ "maxDataPoints": 100,
+ "options": {
+ "orientation": "horizontal",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true
+ },
+ "pluginVersion": "10.0.0",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "sum(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"heap\"})*100/sum(jvm_memory_max_bytes{application=\"$application\",instance=\"$instance\", area=\"heap\"})",
+ "format": "time_series",
+ "intervalFactor": 1,
+ "refId": "A"
+ }
+ ],
+ "title": "Heap Used",
+ "type": "gauge"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [
+ {
+ "options": {
+ "match": "null",
+ "result": {
+ "text": "N/A"
+ }
+ },
+ "type": "special"
+ }
+ ],
+ "max": 100,
+ "min": 0,
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "#299c46",
+ "value": null
+ },
+ {
+ "color": "rgba(237, 129, 40, 0.89)",
+ "value": 70
+ },
+ {
+ "color": "#d44a3a",
+ "value": 90
+ }
+ ]
+ },
+ "unit": "percent"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 6,
+ "w": 5,
+ "x": 5,
+ "y": 7
+ },
+ "id": 10,
+ "links": [],
+ "maxDataPoints": 100,
+ "options": {
+ "orientation": "horizontal",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true
+ },
+ "pluginVersion": "10.0.0",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "sum(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"nonheap\"})*100/sum(jvm_memory_max_bytes{application=\"$application\",instance=\"$instance\", area=\"nonheap\"})",
+ "format": "time_series",
+ "intervalFactor": 1,
+ "refId": "A"
+ }
+ ],
+ "title": "Non Heap Used",
+ "type": "gauge"
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "fill": 1,
+ "fillGradient": 0,
+ "gridPos": {
+ "h": 7,
+ "w": 16,
+ "x": 0,
+ "y": 13
+ },
+ "hiddenSeries": false,
+ "id": 16,
+ "legend": {
+ "alignAsTable": true,
+ "avg": true,
+ "current": true,
+ "max": true,
+ "min": true,
+ "show": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "10.0.0",
+ "pointradius": 2,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "system_cpu_usage{instance=\"$instance\", application=\"$application\"}",
+ "format": "time_series",
+ "intervalFactor": 1,
+ "legendFormat": "System CPU Usage",
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "process_cpu_usage{instance=\"$instance\", application=\"$application\"}",
+ "format": "time_series",
+ "intervalFactor": 1,
+ "legendFormat": "Process CPU Usage",
+ "refId": "B"
+ }
+ ],
+ "thresholds": [],
+ "timeRegions": [],
+ "title": "CPU Usage",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "mode": "time",
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "logBase": 1,
+ "show": true
+ },
+ {
+ "format": "short",
+ "logBase": 1,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false
+ }
+ },
+ {
+ "collapsed": false,
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 20
+ },
+ "id": 6,
+ "panels": [],
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "refId": "A"
+ }
+ ],
+ "title": "HTTP Stats",
+ "type": "row"
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "fill": 1,
+ "fillGradient": 0,
+ "gridPos": {
+ "h": 7,
+ "w": 24,
+ "x": 0,
+ "y": 21
+ },
+ "hiddenSeries": false,
+ "id": 2,
+ "legend": {
+ "alignAsTable": true,
+ "avg": false,
+ "current": false,
+ "max": false,
+ "min": false,
+ "rightSide": true,
+ "show": true,
+ "total": false,
+ "values": false
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "10.0.0",
+ "pointradius": 2,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "irate(http_server_requests_seconds_count{instance=\"$instance\", application=\"$application\", uri!~\".*actuator.*\"}[5m])",
+ "format": "time_series",
+ "intervalFactor": 1,
+ "legendFormat": "{{method}} [{{status}}] - {{uri}}",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeRegions": [],
+ "title": "Request Count",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "mode": "time",
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "logBase": 1,
+ "show": true
+ },
+ {
+ "format": "short",
+ "logBase": 1,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false
+ }
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "fill": 1,
+ "fillGradient": 0,
+ "gridPos": {
+ "h": 8,
+ "w": 24,
+ "x": 0,
+ "y": 28
+ },
+ "hiddenSeries": false,
+ "id": 18,
+ "legend": {
+ "alignAsTable": true,
+ "avg": true,
+ "current": false,
+ "max": true,
+ "min": true,
+ "rightSide": true,
+ "show": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "10.0.0",
+ "pointradius": 2,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "irate(http_server_requests_seconds_sum{instance=\"$instance\", application=\"$application\", exception=\"None\", uri!~\".*actuator.*\"}[5m]) / irate(http_server_requests_seconds_count{instance=\"$instance\", application=\"$application\", exception=\"None\", uri!~\".*actuator.*\"}[5m])",
+ "format": "time_series",
+ "intervalFactor": 1,
+ "legendFormat": "{{method}} [{{status}}] - {{uri}}",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeRegions": [],
+ "title": "Response Time",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "mode": "time",
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "s",
+ "logBase": 1,
+ "show": true
+ },
+ {
+ "format": "short",
+ "logBase": 1,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false
+ }
+ }
+ ],
+ "refresh": "5s",
+ "schemaVersion": 38,
+ "style": "dark",
+ "tags": [],
+ "templating": {
+ "list": [
+ {
+ "current": {
+ "isNone": true,
+ "selected": false,
+ "text": "None",
+ "value": ""
+ },
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "definition": "label_values(application)",
+ "hide": 0,
+ "includeAll": false,
+ "label": "Application",
+ "multi": false,
+ "name": "application",
+ "options": [],
+ "query": "label_values(application)",
+ "refresh": 1,
+ "regex": "",
+ "skipUrlSync": false,
+ "sort": 0,
+ "tagValuesQuery": "",
+ "tagsQuery": "",
+ "type": "query",
+ "useTags": false
+ },
+ {
+ "current": {
+ "selected": false,
+ "text": "host.docker.internal:8080",
+ "value": "host.docker.internal:8080"
+ },
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "definition": "label_values(jvm_memory_used_bytes{application=\"$application\"}, instance)",
+ "hide": 0,
+ "includeAll": false,
+ "label": "Instance",
+ "multi": false,
+ "name": "instance",
+ "options": [],
+ "query": "label_values(jvm_memory_used_bytes{application=\"$application\"}, instance)",
+ "refresh": 1,
+ "regex": "",
+ "skipUrlSync": false,
+ "sort": 0,
+ "tagValuesQuery": "",
+ "tagsQuery": "",
+ "type": "query",
+ "useTags": false
+ }
+ ]
+ },
+ "time": {
+ "from": "now-15m",
+ "to": "now"
+ },
+ "timepicker": {
+ "refresh_intervals": [
+ "5s",
+ "10s",
+ "30s",
+ "1m",
+ "5m",
+ "15m",
+ "30m",
+ "1h",
+ "2h",
+ "1d"
+ ],
+ "time_options": [
+ "5m",
+ "15m",
+ "1h",
+ "6h",
+ "12h",
+ "24h",
+ "2d",
+ "7d",
+ "30d"
+ ]
+ },
+ "timezone": "browser",
+ "title": "Application Dashboard",
+ "uid": "LmyyfmTZkj",
+ "version": 2,
+ "weekStart": ""
+}
\ No newline at end of file
diff --git a/generators/server/templates/app/config/grafana/provisioning/dashboards/dashboard.yml b/generators/server/templates/app/config/grafana/provisioning/dashboards/dashboard.yml
new file mode 100755
index 0000000..14716ee
--- /dev/null
+++ b/generators/server/templates/app/config/grafana/provisioning/dashboards/dashboard.yml
@@ -0,0 +1,11 @@
+apiVersion: 1
+
+providers:
+- name: 'Prometheus'
+ orgId: 1
+ folder: ''
+ type: file
+ disableDeletion: false
+ editable: true
+ options:
+ path: /etc/grafana/provisioning/dashboards
diff --git a/generators/server/templates/app/config/grafana/provisioning/dashboards/jvm-micrometer_rev10.json b/generators/server/templates/app/config/grafana/provisioning/dashboards/jvm-micrometer_rev10.json
new file mode 100644
index 0000000..f2082da
--- /dev/null
+++ b/generators/server/templates/app/config/grafana/provisioning/dashboards/jvm-micrometer_rev10.json
@@ -0,0 +1,3490 @@
+{
+ "annotations": {
+ "list": [
+ {
+ "builtIn": 1,
+ "datasource": {
+ "type": "datasource",
+ "uid": "grafana"
+ },
+ "enable": true,
+ "hide": true,
+ "iconColor": "rgba(0, 211, 255, 1)",
+ "limit": 100,
+ "name": "Annotations & Alerts",
+ "showIn": 0,
+ "target": {
+ "limit": 100,
+ "matchAny": false,
+ "tags": [],
+ "type": "dashboard"
+ },
+ "type": "dashboard"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "enable": true,
+ "expr": "resets(process_uptime_seconds{application=\"$application\", instance=\"$instance\"}[1m]) > 0",
+ "iconColor": "rgba(255, 96, 96, 1)",
+ "name": "Restart Detection",
+ "showIn": 0,
+ "step": "1m",
+ "tagKeys": "restart-tag",
+ "textFormat": "uptime reset",
+ "titleFormat": "Restart"
+ }
+ ]
+ },
+ "description": "Dashboard for Micrometer instrumented applications (Java, Spring Boot, Micronaut)",
+ "editable": true,
+ "fiscalYearStartMonth": 0,
+ "gnetId": 4701,
+ "graphTooltip": 1,
+ "links": [],
+ "liveNow": false,
+ "panels": [
+ {
+ "collapsed": false,
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 0
+ },
+ "id": 139,
+ "panels": [],
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "refId": "A"
+ }
+ ],
+ "title": "Quick Facts",
+ "type": "row"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "decimals": 1,
+ "mappings": [
+ {
+ "options": {
+ "match": "null",
+ "result": {
+ "text": "N/A"
+ }
+ },
+ "type": "special"
+ }
+ ],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "s"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 3,
+ "w": 6,
+ "x": 0,
+ "y": 1
+ },
+ "id": 63,
+ "links": [],
+ "maxDataPoints": 100,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "none",
+ "justifyMode": "auto",
+ "orientation": "horizontal",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "10.0.0",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "process_uptime_seconds{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "intervalFactor": 2,
+ "legendFormat": "",
+ "metric": "",
+ "refId": "A",
+ "step": 14400
+ }
+ ],
+ "title": "Uptime",
+ "type": "stat"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [
+ {
+ "options": {
+ "match": "null",
+ "result": {
+ "text": "N/A"
+ }
+ },
+ "type": "special"
+ }
+ ],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "dateTimeAsIso"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 3,
+ "w": 6,
+ "x": 6,
+ "y": 1
+ },
+ "id": 92,
+ "links": [],
+ "maxDataPoints": 100,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "none",
+ "justifyMode": "auto",
+ "orientation": "horizontal",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "10.0.0",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "process_start_time_seconds{application=\"$application\", instance=\"$instance\"}*1000",
+ "format": "time_series",
+ "intervalFactor": 2,
+ "legendFormat": "",
+ "metric": "",
+ "refId": "A",
+ "step": 14400
+ }
+ ],
+ "title": "Start time",
+ "type": "stat"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "decimals": 2,
+ "mappings": [
+ {
+ "options": {
+ "match": "null",
+ "result": {
+ "text": "N/A"
+ }
+ },
+ "type": "special"
+ }
+ ],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "rgba(50, 172, 45, 0.97)",
+ "value": null
+ },
+ {
+ "color": "rgba(237, 129, 40, 0.89)",
+ "value": 70
+ },
+ {
+ "color": "rgba(245, 54, 54, 0.9)",
+ "value": 90
+ }
+ ]
+ },
+ "unit": "percent"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 3,
+ "w": 6,
+ "x": 12,
+ "y": 1
+ },
+ "id": 65,
+ "links": [],
+ "maxDataPoints": 100,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "none",
+ "justifyMode": "auto",
+ "orientation": "horizontal",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "10.0.0",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "sum(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"heap\"})*100/sum(jvm_memory_max_bytes{application=\"$application\",instance=\"$instance\", area=\"heap\"})",
+ "format": "time_series",
+ "intervalFactor": 2,
+ "legendFormat": "",
+ "refId": "A",
+ "step": 14400
+ }
+ ],
+ "title": "Heap used",
+ "type": "stat"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "decimals": 2,
+ "mappings": [
+ {
+ "options": {
+ "match": "null",
+ "result": {
+ "text": "N/A"
+ }
+ },
+ "type": "special"
+ },
+ {
+ "options": {
+ "from": -1e+32,
+ "result": {
+ "text": "N/A"
+ },
+ "to": 0
+ },
+ "type": "range"
+ }
+ ],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "rgba(50, 172, 45, 0.97)",
+ "value": null
+ },
+ {
+ "color": "rgba(237, 129, 40, 0.89)",
+ "value": 70
+ },
+ {
+ "color": "rgba(245, 54, 54, 0.9)",
+ "value": 90
+ }
+ ]
+ },
+ "unit": "percent"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 3,
+ "w": 6,
+ "x": 18,
+ "y": 1
+ },
+ "id": 75,
+ "links": [],
+ "maxDataPoints": 100,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "none",
+ "justifyMode": "auto",
+ "orientation": "horizontal",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "10.0.0",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "sum(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"nonheap\"})*100/sum(jvm_memory_max_bytes{application=\"$application\",instance=\"$instance\", area=\"nonheap\"})",
+ "format": "time_series",
+ "intervalFactor": 2,
+ "legendFormat": "",
+ "refId": "A",
+ "step": 14400
+ }
+ ],
+ "title": "Non-Heap used",
+ "type": "stat"
+ },
+ {
+ "collapsed": false,
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 4
+ },
+ "id": 140,
+ "panels": [],
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "refId": "A"
+ }
+ ],
+ "title": "I/O Overview",
+ "type": "row"
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "fill": 1,
+ "fillGradient": 0,
+ "gridPos": {
+ "h": 7,
+ "w": 6,
+ "x": 0,
+ "y": 5
+ },
+ "hiddenSeries": false,
+ "id": 111,
+ "legend": {
+ "avg": false,
+ "current": true,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "10.0.0",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "sum(rate(http_server_requests_seconds_count{application=\"$application\", instance=\"$instance\"}[1m]))",
+ "format": "time_series",
+ "intervalFactor": 1,
+ "legendFormat": "HTTP",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeRegions": [],
+ "title": "Rate",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "mode": "time",
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "ops",
+ "logBase": 1,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "logBase": 1,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false
+ }
+ },
+ {
+ "aliasColors": {
+ "HTTP": "#890f02",
+ "HTTP - 5xx": "#bf1b00"
+ },
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "fill": 1,
+ "fillGradient": 0,
+ "gridPos": {
+ "h": 7,
+ "w": 6,
+ "x": 6,
+ "y": 5
+ },
+ "hiddenSeries": false,
+ "id": 112,
+ "legend": {
+ "avg": false,
+ "current": true,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "10.0.0",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "sum(rate(http_server_requests_seconds_count{application=\"$application\", instance=\"$instance\", status=~\"5..\"}[1m]))",
+ "format": "time_series",
+ "intervalFactor": 1,
+ "legendFormat": "HTTP - 5xx",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeRegions": [],
+ "title": "Errors",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "mode": "time",
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "ops",
+ "logBase": 1,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "logBase": 1,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false
+ }
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "fill": 1,
+ "fillGradient": 0,
+ "gridPos": {
+ "h": 7,
+ "w": 6,
+ "x": 12,
+ "y": 5
+ },
+ "hiddenSeries": false,
+ "id": 113,
+ "legend": {
+ "avg": false,
+ "current": true,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "10.0.0",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "sum(rate(http_server_requests_seconds_sum{application=\"$application\", instance=\"$instance\", status!~\"5..\"}[1m]))/sum(rate(http_server_requests_seconds_count{application=\"$application\", instance=\"$instance\", status!~\"5..\"}[1m]))",
+ "format": "time_series",
+ "hide": false,
+ "intervalFactor": 1,
+ "legendFormat": "HTTP - AVG",
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "max(http_server_requests_seconds_max{application=\"$application\", instance=\"$instance\", status!~\"5..\"})",
+ "format": "time_series",
+ "hide": false,
+ "intervalFactor": 1,
+ "legendFormat": "HTTP - MAX",
+ "refId": "B"
+ }
+ ],
+ "thresholds": [],
+ "timeRegions": [],
+ "title": "Duration",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "mode": "time",
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "s",
+ "logBase": 1,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "logBase": 1,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false
+ }
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "description": "",
+ "fill": 1,
+ "fillGradient": 0,
+ "gridPos": {
+ "h": 7,
+ "w": 6,
+ "x": 18,
+ "y": 5
+ },
+ "hiddenSeries": false,
+ "id": 119,
+ "legend": {
+ "alignAsTable": false,
+ "avg": false,
+ "current": true,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "10.0.0",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "tomcat_threads_busy_threads{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "hide": false,
+ "intervalFactor": 2,
+ "legendFormat": "TOMCAT - BSY",
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "tomcat_threads_current_threads{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "hide": false,
+ "intervalFactor": 2,
+ "legendFormat": "TOMCAT - CUR",
+ "refId": "B"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "tomcat_threads_config_max_threads{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "hide": false,
+ "intervalFactor": 2,
+ "legendFormat": "TOMCAT - MAX",
+ "refId": "C"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "jetty_threads_busy{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "hide": false,
+ "intervalFactor": 2,
+ "legendFormat": "JETTY - BSY",
+ "refId": "D"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "jetty_threads_current{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "hide": false,
+ "intervalFactor": 2,
+ "legendFormat": "JETTY - CUR",
+ "refId": "E"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "jetty_threads_config_max{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "hide": false,
+ "intervalFactor": 2,
+ "legendFormat": "JETTY - MAX",
+ "refId": "F"
+ }
+ ],
+ "thresholds": [],
+ "timeRegions": [],
+ "title": "Utilisation",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "mode": "time",
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "logBase": 1,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "logBase": 1,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false
+ }
+ },
+ {
+ "collapsed": false,
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 12
+ },
+ "id": 141,
+ "panels": [],
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "refId": "A"
+ }
+ ],
+ "title": "JVM Memory",
+ "type": "row"
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "editable": true,
+ "error": false,
+ "fill": 1,
+ "fillGradient": 0,
+ "grid": {
+ "leftLogBase": 1,
+ "rightLogBase": 1
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 6,
+ "x": 0,
+ "y": 13
+ },
+ "hiddenSeries": false,
+ "id": 24,
+ "legend": {
+ "avg": false,
+ "current": true,
+ "max": true,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "10.0.0",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "sum(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"heap\"})",
+ "format": "time_series",
+ "intervalFactor": 2,
+ "legendFormat": "used",
+ "metric": "",
+ "refId": "A",
+ "step": 2400
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "sum(jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", area=\"heap\"})",
+ "format": "time_series",
+ "intervalFactor": 2,
+ "legendFormat": "committed",
+ "refId": "B",
+ "step": 2400
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "sum(jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", area=\"heap\"})",
+ "format": "time_series",
+ "intervalFactor": 2,
+ "legendFormat": "max",
+ "refId": "C",
+ "step": 2400
+ }
+ ],
+ "thresholds": [],
+ "timeRegions": [],
+ "title": "JVM Heap",
+ "tooltip": {
+ "msResolution": false,
+ "shared": true,
+ "sort": 0,
+ "value_type": "cumulative"
+ },
+ "type": "graph",
+ "x-axis": true,
+ "xaxis": {
+ "mode": "time",
+ "show": true,
+ "values": []
+ },
+ "y-axis": true,
+ "y_formats": [
+ "mbytes",
+ "short"
+ ],
+ "yaxes": [
+ {
+ "format": "bytes",
+ "logBase": 1,
+ "min": 0,
+ "show": true
+ },
+ {
+ "format": "short",
+ "logBase": 1,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false
+ }
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "editable": true,
+ "error": false,
+ "fill": 1,
+ "fillGradient": 0,
+ "grid": {
+ "leftLogBase": 1,
+ "rightLogBase": 1
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 6,
+ "x": 6,
+ "y": 13
+ },
+ "hiddenSeries": false,
+ "id": 25,
+ "legend": {
+ "avg": false,
+ "current": true,
+ "max": true,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "10.0.0",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "sum(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"nonheap\"})",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "used",
+ "metric": "",
+ "refId": "A",
+ "step": 2400
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "sum(jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", area=\"nonheap\"})",
+ "format": "time_series",
+ "intervalFactor": 2,
+ "legendFormat": "committed",
+ "refId": "B",
+ "step": 2400
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "sum(jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", area=\"nonheap\"})",
+ "format": "time_series",
+ "intervalFactor": 2,
+ "legendFormat": "max",
+ "refId": "C",
+ "step": 2400
+ }
+ ],
+ "thresholds": [],
+ "timeRegions": [],
+ "title": "JVM Non-Heap",
+ "tooltip": {
+ "msResolution": false,
+ "shared": true,
+ "sort": 0,
+ "value_type": "cumulative"
+ },
+ "type": "graph",
+ "x-axis": true,
+ "xaxis": {
+ "mode": "time",
+ "show": true,
+ "values": []
+ },
+ "y-axis": true,
+ "y_formats": [
+ "mbytes",
+ "short"
+ ],
+ "yaxes": [
+ {
+ "format": "bytes",
+ "logBase": 1,
+ "min": 0,
+ "show": true
+ },
+ {
+ "format": "short",
+ "logBase": 1,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false
+ }
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "editable": true,
+ "error": false,
+ "fill": 1,
+ "fillGradient": 0,
+ "grid": {
+ "leftLogBase": 1,
+ "rightLogBase": 1
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 6,
+ "x": 12,
+ "y": 13
+ },
+ "hiddenSeries": false,
+ "id": 26,
+ "legend": {
+ "alignAsTable": false,
+ "avg": false,
+ "current": true,
+ "max": true,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "10.0.0",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "sum(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\"})",
+ "format": "time_series",
+ "intervalFactor": 2,
+ "legendFormat": "used",
+ "metric": "",
+ "refId": "A",
+ "step": 2400
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "sum(jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\"})",
+ "format": "time_series",
+ "intervalFactor": 2,
+ "legendFormat": "committed",
+ "refId": "B",
+ "step": 2400
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "sum(jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\"})",
+ "format": "time_series",
+ "intervalFactor": 2,
+ "legendFormat": "max",
+ "refId": "C",
+ "step": 2400
+ }
+ ],
+ "thresholds": [],
+ "timeRegions": [],
+ "title": "JVM Total",
+ "tooltip": {
+ "msResolution": false,
+ "shared": true,
+ "sort": 0,
+ "value_type": "cumulative"
+ },
+ "type": "graph",
+ "x-axis": true,
+ "xaxis": {
+ "mode": "time",
+ "show": true,
+ "values": []
+ },
+ "y-axis": true,
+ "y_formats": [
+ "mbytes",
+ "short"
+ ],
+ "yaxes": [
+ {
+ "format": "bytes",
+ "label": "",
+ "logBase": 1,
+ "min": 0,
+ "show": true
+ },
+ {
+ "format": "short",
+ "logBase": 1,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false
+ }
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "editable": true,
+ "error": false,
+ "fill": 1,
+ "fillGradient": 0,
+ "grid": {
+ "leftLogBase": 1,
+ "rightLogBase": 1
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 6,
+ "x": 18,
+ "y": 13
+ },
+ "hiddenSeries": false,
+ "id": 86,
+ "legend": {
+ "avg": false,
+ "current": true,
+ "max": true,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "10.0.0",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "process_memory_vss_bytes{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "hide": true,
+ "intervalFactor": 2,
+ "legendFormat": "vss",
+ "metric": "",
+ "refId": "A",
+ "step": 2400
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "process_memory_rss_bytes{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "intervalFactor": 2,
+ "legendFormat": "rss",
+ "refId": "B"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "process_memory_swap_bytes{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "intervalFactor": 2,
+ "legendFormat": "swap",
+ "refId": "C"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "process_memory_rss_bytes{application=\"$application\", instance=\"$instance\"} + process_memory_swap_bytes{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "intervalFactor": 2,
+ "legendFormat": "total",
+ "refId": "D"
+ }
+ ],
+ "thresholds": [],
+ "timeRegions": [],
+ "title": "JVM Process Memory",
+ "tooltip": {
+ "msResolution": false,
+ "shared": true,
+ "sort": 0,
+ "value_type": "cumulative"
+ },
+ "type": "graph",
+ "x-axis": true,
+ "xaxis": {
+ "mode": "time",
+ "show": true,
+ "values": []
+ },
+ "y-axis": true,
+ "y_formats": [
+ "mbytes",
+ "short"
+ ],
+ "yaxes": [
+ {
+ "format": "bytes",
+ "label": "",
+ "logBase": 1,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "logBase": 1,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false
+ }
+ },
+ {
+ "collapsed": false,
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 20
+ },
+ "id": 142,
+ "panels": [],
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "refId": "A"
+ }
+ ],
+ "title": "JVM Misc",
+ "type": "row"
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "editable": true,
+ "error": false,
+ "fill": 1,
+ "fillGradient": 0,
+ "grid": {
+ "leftLogBase": 1,
+ "rightLogBase": 1
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 6,
+ "x": 0,
+ "y": 21
+ },
+ "hiddenSeries": false,
+ "id": 106,
+ "legend": {
+ "avg": false,
+ "current": true,
+ "max": true,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "10.0.0",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "system_cpu_usage{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "hide": false,
+ "intervalFactor": 1,
+ "legendFormat": "system",
+ "metric": "",
+ "refId": "A",
+ "step": 2400
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "process_cpu_usage{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "hide": false,
+ "intervalFactor": 1,
+ "legendFormat": "process",
+ "refId": "B"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "avg_over_time(process_cpu_usage{application=\"$application\", instance=\"$instance\"}[15m])",
+ "format": "time_series",
+ "hide": false,
+ "intervalFactor": 1,
+ "legendFormat": "process-15m",
+ "refId": "C"
+ }
+ ],
+ "thresholds": [],
+ "timeRegions": [],
+ "title": "CPU Usage",
+ "tooltip": {
+ "msResolution": false,
+ "shared": true,
+ "sort": 0,
+ "value_type": "cumulative"
+ },
+ "type": "graph",
+ "x-axis": true,
+ "xaxis": {
+ "mode": "time",
+ "show": true,
+ "values": []
+ },
+ "y-axis": true,
+ "y_formats": [
+ "short",
+ "short"
+ ],
+ "yaxes": [
+ {
+ "decimals": 1,
+ "format": "percentunit",
+ "label": "",
+ "logBase": 1,
+ "max": "1",
+ "min": 0,
+ "show": true
+ },
+ {
+ "format": "short",
+ "logBase": 1,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false
+ }
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "editable": true,
+ "error": false,
+ "fill": 1,
+ "fillGradient": 0,
+ "grid": {
+ "leftLogBase": 1,
+ "rightLogBase": 1
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 6,
+ "x": 6,
+ "y": 21
+ },
+ "hiddenSeries": false,
+ "id": 93,
+ "legend": {
+ "avg": false,
+ "current": true,
+ "max": true,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "10.0.0",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "system_load_average_1m{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "intervalFactor": 2,
+ "legendFormat": "system-1m",
+ "metric": "",
+ "refId": "A",
+ "step": 2400
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "system_cpu_count{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "intervalFactor": 2,
+ "legendFormat": "cpus",
+ "refId": "B"
+ }
+ ],
+ "thresholds": [],
+ "timeRegions": [],
+ "title": "Load",
+ "tooltip": {
+ "msResolution": false,
+ "shared": true,
+ "sort": 0,
+ "value_type": "cumulative"
+ },
+ "type": "graph",
+ "x-axis": true,
+ "xaxis": {
+ "mode": "time",
+ "show": true,
+ "values": []
+ },
+ "y-axis": true,
+ "y_formats": [
+ "short",
+ "short"
+ ],
+ "yaxes": [
+ {
+ "decimals": 1,
+ "format": "short",
+ "label": "",
+ "logBase": 1,
+ "min": 0,
+ "show": true
+ },
+ {
+ "format": "short",
+ "logBase": 1,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false
+ }
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "editable": true,
+ "error": false,
+ "fill": 1,
+ "fillGradient": 0,
+ "grid": {
+ "leftLogBase": 1,
+ "rightLogBase": 1
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 6,
+ "x": 12,
+ "y": 21
+ },
+ "hiddenSeries": false,
+ "id": 32,
+ "legend": {
+ "avg": false,
+ "current": true,
+ "max": true,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "10.0.0",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "jvm_threads_live_threads{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "intervalFactor": 2,
+ "legendFormat": "live",
+ "metric": "",
+ "refId": "A",
+ "step": 2400
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "jvm_threads_daemon_threads{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "intervalFactor": 2,
+ "legendFormat": "daemon",
+ "metric": "",
+ "refId": "B",
+ "step": 2400
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "jvm_threads_peak_threads{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "intervalFactor": 2,
+ "legendFormat": "peak",
+ "refId": "C",
+ "step": 2400
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "process_threads{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "process",
+ "refId": "D",
+ "step": 2400
+ }
+ ],
+ "thresholds": [],
+ "timeRegions": [],
+ "title": "Threads",
+ "tooltip": {
+ "msResolution": false,
+ "shared": true,
+ "sort": 0,
+ "value_type": "cumulative"
+ },
+ "type": "graph",
+ "x-axis": true,
+ "xaxis": {
+ "mode": "time",
+ "show": true,
+ "values": []
+ },
+ "y-axis": true,
+ "y_formats": [
+ "short",
+ "short"
+ ],
+ "yaxes": [
+ {
+ "decimals": 0,
+ "format": "short",
+ "logBase": 1,
+ "min": 0,
+ "show": true
+ },
+ {
+ "format": "short",
+ "logBase": 1,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false
+ }
+ },
+ {
+ "aliasColors": {
+ "blocked": "#bf1b00",
+ "new": "#fce2de",
+ "runnable": "#7eb26d",
+ "terminated": "#511749",
+ "timed-waiting": "#c15c17",
+ "waiting": "#eab839"
+ },
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "fill": 1,
+ "fillGradient": 0,
+ "gridPos": {
+ "h": 7,
+ "w": 6,
+ "x": 18,
+ "y": 21
+ },
+ "hiddenSeries": false,
+ "id": 124,
+ "legend": {
+ "alignAsTable": false,
+ "avg": false,
+ "current": true,
+ "max": true,
+ "min": false,
+ "rightSide": false,
+ "show": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "10.0.0",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "jvm_threads_states_threads{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "intervalFactor": 2,
+ "legendFormat": "{{state}}",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeRegions": [],
+ "title": "Thread States",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "mode": "time",
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "logBase": 1,
+ "show": true
+ },
+ {
+ "format": "short",
+ "logBase": 1,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false
+ }
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "description": "The percent of time spent on Garbage Collection over all CPUs assigned to the JVM process.",
+ "fill": 1,
+ "fillGradient": 0,
+ "gridPos": {
+ "h": 7,
+ "w": 6,
+ "x": 0,
+ "y": 28
+ },
+ "hiddenSeries": false,
+ "id": 138,
+ "legend": {
+ "avg": false,
+ "current": true,
+ "max": true,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "10.0.0",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "sum(rate(jvm_gc_pause_seconds_sum{application=\"$application\", instance=\"$instance\"}[1m])) by (application, instance) / on(application, instance) system_cpu_count",
+ "format": "time_series",
+ "intervalFactor": 1,
+ "legendFormat": "CPU time spent on GC",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeRegions": [],
+ "title": "GC Pressure",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "mode": "time",
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "decimals": 1,
+ "format": "percentunit",
+ "logBase": 1,
+ "max": "1",
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "logBase": 1,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false
+ }
+ },
+ {
+ "aliasColors": {
+ "debug": "#1F78C1",
+ "error": "#BF1B00",
+ "info": "#508642",
+ "trace": "#6ED0E0",
+ "warn": "#EAB839"
+ },
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "editable": true,
+ "error": false,
+ "fill": 1,
+ "fillGradient": 0,
+ "grid": {
+ "leftLogBase": 1,
+ "rightLogBase": 1
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 12,
+ "x": 6,
+ "y": 28
+ },
+ "height": "",
+ "hiddenSeries": false,
+ "id": 91,
+ "legend": {
+ "alignAsTable": false,
+ "avg": false,
+ "current": true,
+ "hideEmpty": false,
+ "hideZero": false,
+ "max": true,
+ "min": false,
+ "rightSide": false,
+ "show": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": true,
+ "pluginVersion": "10.0.0",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [
+ {
+ "alias": "error",
+ "yaxis": 1
+ },
+ {
+ "alias": "warn",
+ "yaxis": 1
+ }
+ ],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "increase(logback_events_total{application=\"$application\", instance=\"$instance\"}[1m])",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "{{level}}",
+ "metric": "",
+ "refId": "A",
+ "step": 1200
+ }
+ ],
+ "thresholds": [],
+ "timeRegions": [],
+ "title": "Log Events",
+ "tooltip": {
+ "msResolution": false,
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "x-axis": true,
+ "xaxis": {
+ "mode": "time",
+ "show": true,
+ "values": []
+ },
+ "y-axis": true,
+ "y_formats": [
+ "short",
+ "short"
+ ],
+ "yaxes": [
+ {
+ "decimals": 0,
+ "format": "opm",
+ "logBase": 1,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "logBase": 1,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false
+ }
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "editable": true,
+ "error": false,
+ "fill": 1,
+ "fillGradient": 0,
+ "grid": {
+ "leftLogBase": 1,
+ "rightLogBase": 1
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 6,
+ "x": 18,
+ "y": 28
+ },
+ "hiddenSeries": false,
+ "id": 61,
+ "legend": {
+ "avg": false,
+ "current": true,
+ "max": true,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "10.0.0",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "process_files_open_files{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "hide": false,
+ "intervalFactor": 2,
+ "legendFormat": "open",
+ "metric": "",
+ "refId": "A",
+ "step": 2400
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "process_files_max_files{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "hide": false,
+ "intervalFactor": 2,
+ "legendFormat": "max",
+ "metric": "",
+ "refId": "B",
+ "step": 2400
+ }
+ ],
+ "thresholds": [],
+ "timeRegions": [],
+ "title": "File Descriptors",
+ "tooltip": {
+ "msResolution": false,
+ "shared": true,
+ "sort": 0,
+ "value_type": "cumulative"
+ },
+ "type": "graph",
+ "x-axis": true,
+ "xaxis": {
+ "mode": "time",
+ "show": true,
+ "values": []
+ },
+ "y-axis": true,
+ "y_formats": [
+ "short",
+ "short"
+ ],
+ "yaxes": [
+ {
+ "decimals": 0,
+ "format": "short",
+ "logBase": 10,
+ "min": 0,
+ "show": true
+ },
+ {
+ "format": "short",
+ "logBase": 1,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false
+ }
+ },
+ {
+ "collapsed": false,
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 35
+ },
+ "id": 143,
+ "panels": [],
+ "repeat": "persistence_counts",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "refId": "A"
+ }
+ ],
+ "title": "JVM Memory Pools (Heap)",
+ "type": "row"
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "editable": true,
+ "error": false,
+ "fill": 1,
+ "fillGradient": 0,
+ "grid": {
+ "leftLogBase": 1,
+ "rightLogBase": 1
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 8,
+ "x": 0,
+ "y": 36
+ },
+ "hiddenSeries": false,
+ "id": 3,
+ "legend": {
+ "alignAsTable": false,
+ "avg": false,
+ "current": true,
+ "max": true,
+ "min": false,
+ "rightSide": false,
+ "show": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "maxPerRow": 3,
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "10.0.0",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "repeat": "jvm_memory_pool_heap",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_memory_pool_heap\"}",
+ "format": "time_series",
+ "hide": false,
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "used",
+ "metric": "",
+ "refId": "A",
+ "step": 1800
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_memory_pool_heap\"}",
+ "format": "time_series",
+ "hide": false,
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "commited",
+ "metric": "",
+ "refId": "B",
+ "step": 1800
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_memory_pool_heap\"}",
+ "format": "time_series",
+ "hide": false,
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "max",
+ "metric": "",
+ "refId": "C",
+ "step": 1800
+ }
+ ],
+ "thresholds": [],
+ "timeRegions": [],
+ "title": "$jvm_memory_pool_heap",
+ "tooltip": {
+ "msResolution": false,
+ "shared": true,
+ "sort": 0,
+ "value_type": "cumulative"
+ },
+ "type": "graph",
+ "x-axis": true,
+ "xaxis": {
+ "mode": "time",
+ "show": true,
+ "values": []
+ },
+ "y-axis": true,
+ "y_formats": [
+ "mbytes",
+ "short"
+ ],
+ "yaxes": [
+ {
+ "format": "bytes",
+ "logBase": 1,
+ "min": 0,
+ "show": true
+ },
+ {
+ "format": "short",
+ "logBase": 1,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false
+ }
+ },
+ {
+ "collapsed": false,
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 43
+ },
+ "id": 144,
+ "panels": [],
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "refId": "A"
+ }
+ ],
+ "title": "JVM Memory Pools (Non-Heap)",
+ "type": "row"
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "editable": true,
+ "error": false,
+ "fill": 1,
+ "fillGradient": 0,
+ "grid": {
+ "leftLogBase": 1,
+ "rightLogBase": 1
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 8,
+ "x": 0,
+ "y": 44
+ },
+ "hiddenSeries": false,
+ "id": 78,
+ "legend": {
+ "alignAsTable": false,
+ "avg": false,
+ "current": true,
+ "max": true,
+ "min": false,
+ "rightSide": false,
+ "show": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "maxPerRow": 3,
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "10.0.0",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "repeat": "jvm_memory_pool_nonheap",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_memory_pool_nonheap\"}",
+ "format": "time_series",
+ "hide": false,
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "used",
+ "metric": "",
+ "refId": "A",
+ "step": 1800
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_memory_pool_nonheap\"}",
+ "format": "time_series",
+ "hide": false,
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "commited",
+ "metric": "",
+ "refId": "B",
+ "step": 1800
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_memory_pool_nonheap\"}",
+ "format": "time_series",
+ "hide": false,
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "max",
+ "metric": "",
+ "refId": "C",
+ "step": 1800
+ }
+ ],
+ "thresholds": [],
+ "timeRegions": [],
+ "title": "$jvm_memory_pool_nonheap",
+ "tooltip": {
+ "msResolution": false,
+ "shared": true,
+ "sort": 0,
+ "value_type": "cumulative"
+ },
+ "type": "graph",
+ "x-axis": true,
+ "xaxis": {
+ "mode": "time",
+ "show": true,
+ "values": []
+ },
+ "y-axis": true,
+ "y_formats": [
+ "mbytes",
+ "short"
+ ],
+ "yaxes": [
+ {
+ "format": "bytes",
+ "logBase": 1,
+ "min": 0,
+ "show": true
+ },
+ {
+ "format": "short",
+ "logBase": 1,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false
+ }
+ },
+ {
+ "collapsed": false,
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 58
+ },
+ "id": 145,
+ "panels": [],
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "refId": "A"
+ }
+ ],
+ "title": "Garbage Collection",
+ "type": "row"
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "fill": 1,
+ "fillGradient": 0,
+ "gridPos": {
+ "h": 7,
+ "w": 8,
+ "x": 0,
+ "y": 59
+ },
+ "hiddenSeries": false,
+ "id": 98,
+ "legend": {
+ "avg": false,
+ "current": false,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": false
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "10.0.0",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "rate(jvm_gc_pause_seconds_count{application=\"$application\", instance=\"$instance\"}[1m])",
+ "format": "time_series",
+ "hide": false,
+ "intervalFactor": 1,
+ "legendFormat": "{{action}} ({{cause}})",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeRegions": [],
+ "title": "Collections",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "mode": "time",
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "ops",
+ "logBase": 1,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": "",
+ "logBase": 1,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false
+ }
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "fill": 1,
+ "fillGradient": 0,
+ "gridPos": {
+ "h": 7,
+ "w": 8,
+ "x": 8,
+ "y": 59
+ },
+ "hiddenSeries": false,
+ "id": 101,
+ "legend": {
+ "avg": false,
+ "current": false,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": false
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "10.0.0",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "rate(jvm_gc_pause_seconds_sum{application=\"$application\", instance=\"$instance\"}[1m])/rate(jvm_gc_pause_seconds_count{application=\"$application\", instance=\"$instance\"}[1m])",
+ "format": "time_series",
+ "hide": false,
+ "instant": false,
+ "intervalFactor": 1,
+ "legendFormat": "avg {{action}} ({{cause}})",
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "jvm_gc_pause_seconds_max{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "hide": false,
+ "instant": false,
+ "intervalFactor": 1,
+ "legendFormat": "max {{action}} ({{cause}})",
+ "refId": "B"
+ }
+ ],
+ "thresholds": [],
+ "timeRegions": [],
+ "title": "Pause Durations",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "mode": "time",
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "s",
+ "logBase": 1,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": "",
+ "logBase": 1,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false
+ }
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "fill": 1,
+ "fillGradient": 0,
+ "gridPos": {
+ "h": 7,
+ "w": 8,
+ "x": 16,
+ "y": 59
+ },
+ "hiddenSeries": false,
+ "id": 99,
+ "legend": {
+ "avg": false,
+ "current": false,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": false
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "10.0.0",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "rate(jvm_gc_memory_allocated_bytes_total{application=\"$application\", instance=\"$instance\"}[1m])",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "allocated",
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "rate(jvm_gc_memory_promoted_bytes_total{application=\"$application\", instance=\"$instance\"}[1m])",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "promoted",
+ "refId": "B"
+ }
+ ],
+ "thresholds": [],
+ "timeRegions": [],
+ "title": "Allocated/Promoted",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "mode": "time",
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "Bps",
+ "logBase": 1,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "logBase": 1,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false
+ }
+ },
+ {
+ "collapsed": false,
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 66
+ },
+ "id": 146,
+ "panels": [],
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "refId": "A"
+ }
+ ],
+ "title": "Classloading",
+ "type": "row"
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "editable": true,
+ "error": false,
+ "fill": 1,
+ "fillGradient": 0,
+ "grid": {
+ "leftLogBase": 1,
+ "rightLogBase": 1
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 12,
+ "x": 0,
+ "y": 67
+ },
+ "hiddenSeries": false,
+ "id": 37,
+ "legend": {
+ "avg": false,
+ "current": false,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": false
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "10.0.0",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "jvm_classes_loaded_classes{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "intervalFactor": 2,
+ "legendFormat": "loaded",
+ "metric": "",
+ "refId": "A",
+ "step": 1200
+ }
+ ],
+ "thresholds": [],
+ "timeRegions": [],
+ "title": "Classes loaded",
+ "tooltip": {
+ "msResolution": false,
+ "shared": true,
+ "sort": 0,
+ "value_type": "cumulative"
+ },
+ "type": "graph",
+ "x-axis": true,
+ "xaxis": {
+ "mode": "time",
+ "show": true,
+ "values": []
+ },
+ "y-axis": true,
+ "y_formats": [
+ "short",
+ "short"
+ ],
+ "yaxes": [
+ {
+ "format": "short",
+ "logBase": 1,
+ "min": 0,
+ "show": true
+ },
+ {
+ "format": "short",
+ "logBase": 1,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false
+ }
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "editable": true,
+ "error": false,
+ "fill": 1,
+ "fillGradient": 0,
+ "grid": {
+ "leftLogBase": 1,
+ "rightLogBase": 1
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 12,
+ "x": 12,
+ "y": 67
+ },
+ "hiddenSeries": false,
+ "id": 38,
+ "legend": {
+ "avg": false,
+ "current": false,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": false
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "10.0.0",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "delta(jvm_classes_loaded_classes{application=\"$application\",instance=\"$instance\"}[1m])",
+ "format": "time_series",
+ "hide": false,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "delta-1m",
+ "metric": "",
+ "refId": "A",
+ "step": 1200
+ }
+ ],
+ "thresholds": [],
+ "timeRegions": [],
+ "title": "Class delta",
+ "tooltip": {
+ "msResolution": false,
+ "shared": true,
+ "sort": 0,
+ "value_type": "cumulative"
+ },
+ "type": "graph",
+ "x-axis": true,
+ "xaxis": {
+ "mode": "time",
+ "show": true,
+ "values": []
+ },
+ "y-axis": true,
+ "y_formats": [
+ "ops",
+ "short"
+ ],
+ "yaxes": [
+ {
+ "format": "short",
+ "label": "",
+ "logBase": 1,
+ "show": true
+ },
+ {
+ "format": "short",
+ "logBase": 1,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false
+ }
+ },
+ {
+ "collapsed": false,
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 74
+ },
+ "id": 147,
+ "panels": [],
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "refId": "A"
+ }
+ ],
+ "title": "Buffer Pools",
+ "type": "row"
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "fill": 1,
+ "fillGradient": 0,
+ "gridPos": {
+ "h": 7,
+ "w": 8,
+ "x": 0,
+ "y": 75
+ },
+ "hiddenSeries": false,
+ "id": 131,
+ "legend": {
+ "avg": false,
+ "current": false,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": false
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "maxPerRow": 3,
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "10.0.0",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "repeat": "jvm_buffer_pool",
+ "seriesOverrides": [
+ {
+ "alias": "count",
+ "yaxis": 2
+ },
+ {
+ "alias": "buffers",
+ "yaxis": 2
+ }
+ ],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "jvm_buffer_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_buffer_pool\"}",
+ "format": "time_series",
+ "intervalFactor": 2,
+ "legendFormat": "used",
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "jvm_buffer_total_capacity_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_buffer_pool\"}",
+ "format": "time_series",
+ "intervalFactor": 2,
+ "legendFormat": "capacity",
+ "refId": "B"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "expr": "jvm_buffer_count_buffers{application=\"$application\", instance=\"$instance\", id=~\"$jvm_buffer_pool\"}",
+ "format": "time_series",
+ "hide": false,
+ "intervalFactor": 2,
+ "legendFormat": "buffers",
+ "refId": "C"
+ }
+ ],
+ "thresholds": [],
+ "timeRegions": [],
+ "title": "$jvm_buffer_pool",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "mode": "time",
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "decbytes",
+ "logBase": 1,
+ "min": "0",
+ "show": true
+ },
+ {
+ "decimals": 0,
+ "format": "short",
+ "label": "",
+ "logBase": 1,
+ "min": "0",
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false
+ }
+ }
+ ],
+ "refresh": "30s",
+ "schemaVersion": 38,
+ "style": "dark",
+ "tags": [],
+ "templating": {
+ "list": [
+ {
+ "current": {
+ "isNone": true,
+ "selected": false,
+ "text": "None",
+ "value": ""
+ },
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "definition": "",
+ "hide": 0,
+ "includeAll": false,
+ "label": "Application",
+ "multi": false,
+ "name": "application",
+ "options": [],
+ "query": "label_values(application)",
+ "refresh": 2,
+ "regex": "",
+ "skipUrlSync": false,
+ "sort": 0,
+ "tagValuesQuery": "",
+ "tagsQuery": "",
+ "type": "query",
+ "useTags": false
+ },
+ {
+ "allFormat": "glob",
+ "current": {
+ "selected": false,
+ "text": "host.docker.internal:8080",
+ "value": "host.docker.internal:8080"
+ },
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "definition": "",
+ "hide": 0,
+ "includeAll": false,
+ "label": "Instance",
+ "multi": false,
+ "multiFormat": "glob",
+ "name": "instance",
+ "options": [],
+ "query": "label_values(jvm_memory_used_bytes{application=\"$application\"}, instance)",
+ "refresh": 2,
+ "regex": "",
+ "skipUrlSync": false,
+ "sort": 0,
+ "tagValuesQuery": "",
+ "tagsQuery": "",
+ "type": "query",
+ "useTags": false
+ },
+ {
+ "allFormat": "glob",
+ "current": {
+ "selected": false,
+ "text": "All",
+ "value": "$__all"
+ },
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "definition": "",
+ "hide": 2,
+ "includeAll": true,
+ "label": "JVM Memory Pools Heap",
+ "multi": false,
+ "multiFormat": "glob",
+ "name": "jvm_memory_pool_heap",
+ "options": [],
+ "query": "label_values(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"heap\"},id)",
+ "refresh": 1,
+ "regex": "",
+ "skipUrlSync": false,
+ "sort": 1,
+ "tagValuesQuery": "",
+ "tagsQuery": "",
+ "type": "query",
+ "useTags": false
+ },
+ {
+ "allFormat": "glob",
+ "current": {
+ "selected": false,
+ "text": "All",
+ "value": "$__all"
+ },
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "definition": "",
+ "hide": 2,
+ "includeAll": true,
+ "label": "JVM Memory Pools Non-Heap",
+ "multi": false,
+ "multiFormat": "glob",
+ "name": "jvm_memory_pool_nonheap",
+ "options": [],
+ "query": "label_values(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"nonheap\"},id)",
+ "refresh": 1,
+ "regex": "",
+ "skipUrlSync": false,
+ "sort": 2,
+ "tagValuesQuery": "",
+ "tagsQuery": "",
+ "type": "query",
+ "useTags": false
+ },
+ {
+ "allFormat": "glob",
+ "current": {
+ "selected": false,
+ "text": "All",
+ "value": "$__all"
+ },
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "definition": "",
+ "hide": 2,
+ "includeAll": true,
+ "label": "JVM Buffer Pools",
+ "multi": false,
+ "multiFormat": "glob",
+ "name": "jvm_buffer_pool",
+ "options": [],
+ "query": "label_values(jvm_buffer_memory_used_bytes{application=\"$application\", instance=\"$instance\"},id)",
+ "refresh": 1,
+ "regex": "",
+ "skipUrlSync": false,
+ "sort": 1,
+ "tagValuesQuery": "",
+ "tagsQuery": "",
+ "type": "query",
+ "useTags": false
+ }
+ ]
+ },
+ "time": {
+ "from": "now-24h",
+ "to": "now"
+ },
+ "timepicker": {
+ "now": true,
+ "refresh_intervals": [
+ "5s",
+ "10s",
+ "30s",
+ "1m",
+ "5m",
+ "15m",
+ "30m",
+ "1h",
+ "2h",
+ "1d"
+ ],
+ "time_options": [
+ "5m",
+ "15m",
+ "1h",
+ "6h",
+ "12h",
+ "24h",
+ "2d",
+ "7d",
+ "30d"
+ ]
+ },
+ "timezone": "browser",
+ "title": "JVM (Micrometer)",
+ "uid": "ec114e98-67b3-48c7-b0a9-154920608c67",
+ "version": 19,
+ "weekStart": ""
+}
\ No newline at end of file
diff --git a/generators/server/templates/app/config/grafana/provisioning/datasources/datasource.yml b/generators/server/templates/app/config/grafana/provisioning/datasources/datasource.yml
new file mode 100755
index 0000000..4401117
--- /dev/null
+++ b/generators/server/templates/app/config/grafana/provisioning/datasources/datasource.yml
@@ -0,0 +1,51 @@
+# config file version
+apiVersion: 1
+
+# list of datasources that should be deleted from the database
+deleteDatasources:
+ - name: Prometheus
+ orgId: 1
+
+# list of datasources to insert/update depending
+# whats available in the database
+datasources:
+ # name of the datasource. Required
+- name: Prometheus
+ # datasource type. Required
+ type: prometheus
+ uid : PBFA97CFB590B2093
+ # access mode. direct or proxy. Required
+ access: proxy
+ # org id. will default to orgId 1 if not specified
+ orgId: 1
+ # url
+ url: http://prometheus:9090
+ # database password, if used
+ password:
+ # database user, if used
+ user:
+ # database name, if used
+ database:
+ # enable/disable basic auth
+ basicAuth: true
+ # basic auth username
+ basicAuthUser: admin
+ # basic auth password
+ basicAuthPassword: foobar
+ # enable/disable with credentials headers
+ withCredentials:
+ # mark as default datasource. Max one per org
+ isDefault: true
+ #