├── .dockerignore
├── .gitattributes
├── .github
├── FUNDING.yml
├── dependabot.yml
└── workflows
│ └── build.yml
├── .gitignore
├── .idea
└── icon.png
├── .mvn
└── wrapper
│ ├── .gitignore
│ ├── MavenWrapperDownloader.java
│ ├── maven-wrapper.jar
│ └── maven-wrapper.properties
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── lombok.config
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
├── main
├── docker
│ ├── Dockerfile.jvm
│ ├── Dockerfile.native
│ └── Dockerfile.native-micro
├── java
│ └── com
│ │ └── melloware
│ │ └── quarkus
│ │ ├── panache
│ │ ├── Car.java
│ │ └── CarResource.java
│ │ ├── socket
│ │ ├── PushWebSocket.java
│ │ ├── SocketMessage.java
│ │ ├── SocketMessageType.java
│ │ └── SocketResource.java
│ │ └── support
│ │ ├── QueryRequest.java
│ │ └── QueryResponse.java
├── resources
│ ├── application.properties
│ └── db
│ │ ├── changeLog.xml
│ │ └── changes
│ │ ├── 00100_create_schema.xml
│ │ └── 00101_load_data.xml
└── webui
│ ├── .editorconfig
│ ├── .env.development
│ ├── .env.production
│ ├── .eslintrc.json
│ ├── .prettierrc.json
│ ├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ └── settings.json
│ ├── farm.config.ts
│ ├── index.html
│ ├── openapi.json
│ ├── openapi.yaml
│ ├── orval.config.ts
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ └── static
│ │ └── images
│ │ ├── favicon.ico
│ │ ├── plus-sign.svg
│ │ ├── primereact-dark.svg
│ │ ├── primereact.svg
│ │ ├── quarkus.svg
│ │ └── react.svg
│ ├── src
│ ├── App.css
│ ├── App.tsx
│ ├── AppMenu.tsx
│ ├── AppMenuItem.ts
│ ├── CrudPage.tsx
│ ├── assets
│ │ └── layout
│ │ │ ├── _overrides.scss
│ │ │ ├── _variables.scss
│ │ │ ├── layout.scss
│ │ │ └── sass
│ │ │ ├── _config.scss
│ │ │ ├── _content.scss
│ │ │ ├── _footer.scss
│ │ │ ├── _layout.scss
│ │ │ ├── _main.scss
│ │ │ ├── _menu.scss
│ │ │ ├── _mixins.scss
│ │ │ ├── _responsive.scss
│ │ │ ├── _splash.scss
│ │ │ ├── _topbar.scss
│ │ │ ├── _typography.scss
│ │ │ └── _utils.scss
│ ├── index.tsx
│ └── service
│ │ ├── AxiosMutator.ts
│ │ ├── CarService.ts
│ │ └── CarService.zod.ts
│ ├── tsconfig.json
│ └── tsconfig.node.json
└── test
└── resources
├── dev-flow.excalidraw
├── dev-flow.png
└── quarkus-primereact-screen.png
/.dockerignore:
--------------------------------------------------------------------------------
1 | *
2 | !target/*-runner
3 | !target/*-runner.jar
4 | !target/lib/*
5 | !target/quarkus-app/
6 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # New SPACE Developers Using Windows:
2 |
3 |
4 | # This file exists to prevent Windows users from having to manually convert the end of line sequence (EOL) for each text file after initially cloning this repo. This is NOT applicable to Linux users, only to Windows users.
5 | # After cloning the repo and ensuring the working tree is clean, run the following commands in order to auto-update the EOL on text files:
6 | # git rm --cached -r .
7 | # git reset --hard
8 |
9 | # Text files to be modified from EOL = Carriage Return Line Feed (CRLF) to EOL = Line Feed (LF):
10 |
11 | *.js eol=lf
12 | *.jsx eol=lf
13 | *.json eol=lf
14 | *.ts eol=lf
15 | *.tsx eol=lf
16 |
17 | # Binary files NOT to be modified:
18 |
19 | *.ico -text diff
20 | *.png -text diff
21 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [melloware]
4 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "maven"
4 | directory: "/"
5 | schedule:
6 | interval: "monthly"
7 | - package-ecosystem: "npm"
8 | directory: "/src/main/webui"
9 | schedule:
10 | interval: "monthly"
11 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
3 |
4 | name: CI
5 |
6 | on:
7 | push:
8 | branches: [ "main" ]
9 | pull_request:
10 | branches: [ "main" ]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | steps:
18 | - uses: actions/checkout@v3
19 | - name: Set up JDK 17
20 | uses: actions/setup-java@v3
21 | with:
22 | java-version: '17'
23 | distribution: 'temurin'
24 | cache: maven
25 | - name: Build with Maven
26 | run: mvn -B package --file pom.xml -Dnative
27 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Eclipse
2 | .project
3 | .classpath
4 | .settings/
5 | bin/
6 | dist/
7 |
8 | # IntelliJ
9 | .idea
10 | *.ipr
11 | *.iml
12 | *.iws
13 |
14 | # NetBeans
15 | nb-configuration.xml
16 |
17 | # VS Code
18 | .vscode
19 |
20 | # OSX
21 | .DS_Store
22 |
23 | # SASS
24 | .sass-cache
25 | sassdoc
26 |
27 | # NODE
28 | node_modules/
29 | .quinoa
30 |
31 | # Vim
32 | *.swp
33 | *.swo
34 |
35 | # patch
36 | *.orig
37 | *.rej
38 |
39 | # Maven
40 | target/
41 | pom.xml.tag
42 | pom.xml.releaseBackup
43 | pom.xml.versionsBackup
44 | release.properties
--------------------------------------------------------------------------------
/.idea/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/melloware/quarkus-primereact/HEAD/.idea/icon.png
--------------------------------------------------------------------------------
/.mvn/wrapper/.gitignore:
--------------------------------------------------------------------------------
1 | maven-wrapper.jar
2 |
--------------------------------------------------------------------------------
/.mvn/wrapper/MavenWrapperDownloader.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one
3 | * or more contributor license agreements. See the NOTICE file
4 | * distributed with this work for additional information
5 | * regarding copyright ownership. The ASF licenses this file
6 | * to you under the Apache License, Version 2.0 (the
7 | * "License"); you may not use this file except in compliance
8 | * with the License. You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing,
13 | * software distributed under the License is distributed on an
14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | * KIND, either express or implied. See the License for the
16 | * specific language governing permissions and limitations
17 | * under the License.
18 | */
19 |
20 | import java.io.IOException;
21 | import java.io.InputStream;
22 | import java.net.Authenticator;
23 | import java.net.PasswordAuthentication;
24 | import java.net.URI;
25 | import java.net.URL;
26 | import java.nio.file.Files;
27 | import java.nio.file.Path;
28 | import java.nio.file.Paths;
29 | import java.nio.file.StandardCopyOption;
30 | import java.util.concurrent.ThreadLocalRandom;
31 |
32 | public final class MavenWrapperDownloader {
33 | private static final String WRAPPER_VERSION = "3.3.2";
34 |
35 | private static final boolean VERBOSE = Boolean.parseBoolean(System.getenv("MVNW_VERBOSE"));
36 |
37 | public static void main(String[] args) {
38 | log("Apache Maven Wrapper Downloader " + WRAPPER_VERSION);
39 |
40 | if (args.length != 2) {
41 | System.err.println(" - ERROR wrapperUrl or wrapperJarPath parameter missing");
42 | System.exit(1);
43 | }
44 |
45 | try {
46 | log(" - Downloader started");
47 | final URL wrapperUrl = URI.create(args[0]).toURL();
48 | final String jarPath = args[1].replace("..", ""); // Sanitize path
49 | final Path wrapperJarPath = Paths.get(jarPath).toAbsolutePath().normalize();
50 | downloadFileFromURL(wrapperUrl, wrapperJarPath);
51 | log("Done");
52 | } catch (IOException e) {
53 | System.err.println("- Error downloading: " + e.getMessage());
54 | if (VERBOSE) {
55 | e.printStackTrace();
56 | }
57 | System.exit(1);
58 | }
59 | }
60 |
61 | private static void downloadFileFromURL(URL wrapperUrl, Path wrapperJarPath)
62 | throws IOException {
63 | log(" - Downloading to: " + wrapperJarPath);
64 | if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
65 | final String username = System.getenv("MVNW_USERNAME");
66 | final char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
67 | Authenticator.setDefault(new Authenticator() {
68 | @Override
69 | protected PasswordAuthentication getPasswordAuthentication() {
70 | return new PasswordAuthentication(username, password);
71 | }
72 | });
73 | }
74 | Path temp = wrapperJarPath
75 | .getParent()
76 | .resolve(wrapperJarPath.getFileName() + "."
77 | + Long.toUnsignedString(ThreadLocalRandom.current().nextLong()) + ".tmp");
78 | try (InputStream inStream = wrapperUrl.openStream()) {
79 | Files.copy(inStream, temp, StandardCopyOption.REPLACE_EXISTING);
80 | Files.move(temp, wrapperJarPath, StandardCopyOption.REPLACE_EXISTING);
81 | } finally {
82 | Files.deleteIfExists(temp);
83 | }
84 | log(" - Downloader complete");
85 | }
86 |
87 | private static void log(String msg) {
88 | if (VERBOSE) {
89 | System.out.println(msg);
90 | }
91 | }
92 |
93 | }
94 |
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/melloware/quarkus-primereact/HEAD/.mvn/wrapper/maven-wrapper.jar
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.properties:
--------------------------------------------------------------------------------
1 | # Licensed to the Apache Software Foundation (ASF) under one
2 | # or more contributor license agreements. See the NOTICE file
3 | # distributed with this work for additional information
4 | # regarding copyright ownership. The ASF licenses this file
5 | # to you under the Apache License, Version 2.0 (the
6 | # "License"); you may not use this file except in compliance
7 | # with the License. You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 | wrapperVersion=3.3.2
18 | distributionType=source
19 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
20 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | mellowaredev@gmail.com.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Melloware
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
Quarkus PrimeReact
9 |
10 |
11 |
12 | [](https://opensource.org/licenses/MIT)
13 | [](https://github.com/melloware/quarkus-primereact/actions/workflows/build.yml)
14 | [](https://github.com/quarkusio/quarkus)
15 | 
16 | 
17 |
18 | **If you like this project, please consider supporting me ❤️**
19 |
20 | [](https://github.com/sponsors/melloware)
21 | [](https://www.paypal.me/mellowareinc)
22 |
23 | This [monorepo](https://en.wikipedia.org/wiki/Monorepo) is a minimal CRUD service exposing a couple of endpoints over REST,
24 | with a front-end based on React so you can play with it from your browser.
25 |
26 | While the code is surprisingly simple, under the hood this is using:
27 |
28 | - [Quarkus REST](https://quarkus.io/guides/rest) for REST API endpoints with OpenAPI documentation
29 | - [Quarkus REST Problem](https://github.com/quarkiverse/quarkus-resteasy-problem) for consistent REST API error handling
30 | - [Quarkus WebSockets Next](https://quarkus.io/guides/websockets-next-tutorial) for real-time WebSocket communication
31 | - [Quarkus Quinoa](https://github.com/quarkiverse/quarkus-quinoa) to handle allowing this monorepo to serve React and Java code
32 | - [Hibernate ORM with Panache](https://quarkus.io/guides/hibernate-orm-panache) to perform the CRUD operations on the database
33 | - [PostgreSQL](https://www.postgresql.org/) database; automatically starts an embedded DB
34 | - [Liquibase](https://www.liquibase.com/) to automatically update database
35 | - [React + PrimeReact](https://primereact.org/) for a top notch user interface including lazy datatable
36 | - [React Websocket](https://github.com/robtaussig/react-use-websocket) to handle websocket connections
37 | - [TanStack Form](https://tanstack.com/form/latest) to validate user input data
38 | - [TanStack Query](https://tanstack.com/query/latest) for powerful asynchronous state management for TypeScript
39 | - [Orval](https://orval.dev/) to generate TanStack Query client Typescript from the OpenAPI definition
40 | - [Zod](https://zod.dev/) for TypeScript-first schema validation
41 |
42 | ## Requirements
43 |
44 | To compile and run this demo you will need:
45 |
46 | - JDK 17+
47 | - Apache Maven
48 |
49 | ## Code Generation
50 |
51 | This project uses [Orval](https://orval.dev/) to generate the [TanStack Query](https://tanstack.com/query/latest) client Typescript from the OpenAPI definition.
52 |
53 | [](https://github.com/melloware/quarkus-primereact)
54 |
55 |
56 | ## Developing
57 |
58 | ### Live coding with Quarkus
59 |
60 | The Maven Quarkus plugin provides a development mode that supports
61 | live coding. To try this out:
62 |
63 | ```bash
64 | $ ./mvnw quarkus:dev
65 | ```
66 |
67 | Watch as it starts up a temporary PostreSQL database just for this session. In this mode you can make changes to the code and have the changes immediately applied, by just refreshing your browser.
68 |
69 | > :bulb:
70 | Hot reload works add a new REST endpoint and see it update in realtime. Try it!
71 |
72 | Now open your web browser to http://localhost:8080/ to see it in action.
73 |
74 | [](https://github.com/melloware/quarkus-primereact)
75 |
76 | ## Building
77 |
78 | ### Run Quarkus PrimeReact in JVM mode
79 |
80 | When you're done iterating in developer mode, you can run the application as a
81 | conventional jar file.
82 |
83 | First compile it:
84 |
85 | ```bash
86 | $ ./mvnw clean package
87 | ```
88 |
89 | Then run it with:
90 |
91 | ```bash
92 | $ java -jar ./target/quarkus-app/quarkus-run.jar
93 | ```
94 |
95 | Or build it as a single executable JAR file (known as an uber-jar):
96 |
97 | ```bash
98 | $ ./mvnw clean package -Dquarkus.package.type=uber-jar
99 | ```
100 |
101 | Then run it with:
102 |
103 | ```bash
104 | $ java -jar ./target/quarkus-primereact-{version}-runner.jar
105 | ```
106 |
107 | Navigate to:
108 |
109 |
110 |
111 | ### Run Quarkus PrimeReact in Docker
112 |
113 | You can easily build a Docker image of this application with the following command:
114 |
115 | ```bash
116 | $ ./mvnw -Pdocker
117 | ```
118 |
119 | You will be able to run this binary directly where ${version} is the current project version:
120 |
121 | ```bash
122 | $ docker run -i --rm -p 8000:8000 melloware/quarkus-primereact:latest
123 | ```
124 |
125 | > :bulb:
126 | Now observe the time it took to boot, and remember: that time was mostly spent to generate the tables in your database and import the initial data.
127 |
128 | ## See it in your browser
129 |
130 | Navigate to:
131 |
132 |
--------------------------------------------------------------------------------
/lombok.config:
--------------------------------------------------------------------------------
1 | lombok.addLombokGeneratedAnnotation=true
2 | lombok.log.fieldName=LOG
3 |
--------------------------------------------------------------------------------
/mvnw:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # ----------------------------------------------------------------------------
3 | # Licensed to the Apache Software Foundation (ASF) under one
4 | # or more contributor license agreements. See the NOTICE file
5 | # distributed with this work for additional information
6 | # regarding copyright ownership. The ASF licenses this file
7 | # to you under the Apache License, Version 2.0 (the
8 | # "License"); you may not use this file except in compliance
9 | # with the License. You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing,
14 | # software distributed under the License is distributed on an
15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 | # KIND, either express or implied. See the License for the
17 | # specific language governing permissions and limitations
18 | # under the License.
19 | # ----------------------------------------------------------------------------
20 |
21 | # ----------------------------------------------------------------------------
22 | # Apache Maven Wrapper startup batch script, version 3.3.2
23 | #
24 | # Required ENV vars:
25 | # ------------------
26 | # JAVA_HOME - location of a JDK home dir
27 | #
28 | # Optional ENV vars
29 | # -----------------
30 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven
31 | # e.g. to debug Maven itself, use
32 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
33 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files
34 | # ----------------------------------------------------------------------------
35 |
36 | if [ -z "$MAVEN_SKIP_RC" ]; then
37 |
38 | if [ -f /usr/local/etc/mavenrc ]; then
39 | . /usr/local/etc/mavenrc
40 | fi
41 |
42 | if [ -f /etc/mavenrc ]; then
43 | . /etc/mavenrc
44 | fi
45 |
46 | if [ -f "$HOME/.mavenrc" ]; then
47 | . "$HOME/.mavenrc"
48 | fi
49 |
50 | fi
51 |
52 | # OS specific support. $var _must_ be set to either true or false.
53 | cygwin=false
54 | darwin=false
55 | mingw=false
56 | case "$(uname)" in
57 | CYGWIN*) cygwin=true ;;
58 | MINGW*) mingw=true ;;
59 | Darwin*)
60 | darwin=true
61 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
62 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
63 | if [ -z "$JAVA_HOME" ]; then
64 | if [ -x "/usr/libexec/java_home" ]; then
65 | JAVA_HOME="$(/usr/libexec/java_home)"
66 | export JAVA_HOME
67 | else
68 | JAVA_HOME="/Library/Java/Home"
69 | export JAVA_HOME
70 | fi
71 | fi
72 | ;;
73 | esac
74 |
75 | if [ -z "$JAVA_HOME" ]; then
76 | if [ -r /etc/gentoo-release ]; then
77 | JAVA_HOME=$(java-config --jre-home)
78 | fi
79 | fi
80 |
81 | # For Cygwin, ensure paths are in UNIX format before anything is touched
82 | if $cygwin; then
83 | [ -n "$JAVA_HOME" ] \
84 | && JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
85 | [ -n "$CLASSPATH" ] \
86 | && CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
87 | fi
88 |
89 | # For Mingw, ensure paths are in UNIX format before anything is touched
90 | if $mingw; then
91 | [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] \
92 | && JAVA_HOME="$(
93 | cd "$JAVA_HOME" || (
94 | echo "cannot cd into $JAVA_HOME." >&2
95 | exit 1
96 | )
97 | pwd
98 | )"
99 | fi
100 |
101 | if [ -z "$JAVA_HOME" ]; then
102 | javaExecutable="$(which javac)"
103 | if [ -n "$javaExecutable" ] && ! [ "$(expr "$javaExecutable" : '\([^ ]*\)')" = "no" ]; then
104 | # readlink(1) is not available as standard on Solaris 10.
105 | readLink=$(which readlink)
106 | if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
107 | if $darwin; then
108 | javaHome="$(dirname "$javaExecutable")"
109 | javaExecutable="$(cd "$javaHome" && pwd -P)/javac"
110 | else
111 | javaExecutable="$(readlink -f "$javaExecutable")"
112 | fi
113 | javaHome="$(dirname "$javaExecutable")"
114 | javaHome=$(expr "$javaHome" : '\(.*\)/bin')
115 | JAVA_HOME="$javaHome"
116 | export JAVA_HOME
117 | fi
118 | fi
119 | fi
120 |
121 | if [ -z "$JAVACMD" ]; then
122 | if [ -n "$JAVA_HOME" ]; then
123 | if [ -x "$JAVA_HOME/jre/sh/java" ]; then
124 | # IBM's JDK on AIX uses strange locations for the executables
125 | JAVACMD="$JAVA_HOME/jre/sh/java"
126 | else
127 | JAVACMD="$JAVA_HOME/bin/java"
128 | fi
129 | else
130 | JAVACMD="$(
131 | \unset -f command 2>/dev/null
132 | \command -v java
133 | )"
134 | fi
135 | fi
136 |
137 | if [ ! -x "$JAVACMD" ]; then
138 | echo "Error: JAVA_HOME is not defined correctly." >&2
139 | echo " We cannot execute $JAVACMD" >&2
140 | exit 1
141 | fi
142 |
143 | if [ -z "$JAVA_HOME" ]; then
144 | echo "Warning: JAVA_HOME environment variable is not set." >&2
145 | fi
146 |
147 | # traverses directory structure from process work directory to filesystem root
148 | # first directory with .mvn subdirectory is considered project base directory
149 | find_maven_basedir() {
150 | if [ -z "$1" ]; then
151 | echo "Path not specified to find_maven_basedir" >&2
152 | return 1
153 | fi
154 |
155 | basedir="$1"
156 | wdir="$1"
157 | while [ "$wdir" != '/' ]; do
158 | if [ -d "$wdir"/.mvn ]; then
159 | basedir=$wdir
160 | break
161 | fi
162 | # workaround for JBEAP-8937 (on Solaris 10/Sparc)
163 | if [ -d "${wdir}" ]; then
164 | wdir=$(
165 | cd "$wdir/.." || exit 1
166 | pwd
167 | )
168 | fi
169 | # end of workaround
170 | done
171 | printf '%s' "$(
172 | cd "$basedir" || exit 1
173 | pwd
174 | )"
175 | }
176 |
177 | # concatenates all lines of a file
178 | concat_lines() {
179 | if [ -f "$1" ]; then
180 | # Remove \r in case we run on Windows within Git Bash
181 | # and check out the repository with auto CRLF management
182 | # enabled. Otherwise, we may read lines that are delimited with
183 | # \r\n and produce $'-Xarg\r' rather than -Xarg due to word
184 | # splitting rules.
185 | tr -s '\r\n' ' ' <"$1"
186 | fi
187 | }
188 |
189 | log() {
190 | if [ "$MVNW_VERBOSE" = true ]; then
191 | printf '%s\n' "$1"
192 | fi
193 | }
194 |
195 | BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
196 | if [ -z "$BASE_DIR" ]; then
197 | exit 1
198 | fi
199 |
200 | MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
201 | export MAVEN_PROJECTBASEDIR
202 | log "$MAVEN_PROJECTBASEDIR"
203 |
204 | ##########################################################################################
205 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
206 | # This allows using the maven wrapper in projects that prohibit checking in binary data.
207 | ##########################################################################################
208 | wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
209 | if [ -r "$wrapperJarPath" ]; then
210 | log "Found $wrapperJarPath"
211 | else
212 | log "Couldn't find $wrapperJarPath, downloading it ..."
213 |
214 | if [ -n "$MVNW_REPOURL" ]; then
215 | wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar"
216 | else
217 | wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar"
218 | fi
219 | while IFS="=" read -r key value; do
220 | # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
221 | safeValue=$(echo "$value" | tr -d '\r')
222 | case "$key" in wrapperUrl)
223 | wrapperUrl="$safeValue"
224 | break
225 | ;;
226 | esac
227 | done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
228 | log "Downloading from: $wrapperUrl"
229 |
230 | if $cygwin; then
231 | wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
232 | fi
233 |
234 | if command -v wget >/dev/null; then
235 | log "Found wget ... using wget"
236 | [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
237 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
238 | wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
239 | else
240 | wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
241 | fi
242 | elif command -v curl >/dev/null; then
243 | log "Found curl ... using curl"
244 | [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
245 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
246 | curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
247 | else
248 | curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
249 | fi
250 | else
251 | log "Falling back to using Java to download"
252 | javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
253 | javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
254 | # For Cygwin, switch paths to Windows format before running javac
255 | if $cygwin; then
256 | javaSource=$(cygpath --path --windows "$javaSource")
257 | javaClass=$(cygpath --path --windows "$javaClass")
258 | fi
259 | if [ -e "$javaSource" ]; then
260 | if [ ! -e "$javaClass" ]; then
261 | log " - Compiling MavenWrapperDownloader.java ..."
262 | ("$JAVA_HOME/bin/javac" "$javaSource")
263 | fi
264 | if [ -e "$javaClass" ]; then
265 | log " - Running MavenWrapperDownloader.java ..."
266 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
267 | fi
268 | fi
269 | fi
270 | fi
271 | ##########################################################################################
272 | # End of extension
273 | ##########################################################################################
274 |
275 | # If specified, validate the SHA-256 sum of the Maven wrapper jar file
276 | wrapperSha256Sum=""
277 | while IFS="=" read -r key value; do
278 | case "$key" in wrapperSha256Sum)
279 | wrapperSha256Sum=$value
280 | break
281 | ;;
282 | esac
283 | done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
284 | if [ -n "$wrapperSha256Sum" ]; then
285 | wrapperSha256Result=false
286 | if command -v sha256sum >/dev/null; then
287 | if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c >/dev/null 2>&1; then
288 | wrapperSha256Result=true
289 | fi
290 | elif command -v shasum >/dev/null; then
291 | if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c >/dev/null 2>&1; then
292 | wrapperSha256Result=true
293 | fi
294 | else
295 | echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
296 | echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." >&2
297 | exit 1
298 | fi
299 | if [ $wrapperSha256Result = false ]; then
300 | echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
301 | echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
302 | echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
303 | exit 1
304 | fi
305 | fi
306 |
307 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
308 |
309 | # For Cygwin, switch paths to Windows format before running java
310 | if $cygwin; then
311 | [ -n "$JAVA_HOME" ] \
312 | && JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
313 | [ -n "$CLASSPATH" ] \
314 | && CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
315 | [ -n "$MAVEN_PROJECTBASEDIR" ] \
316 | && MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
317 | fi
318 |
319 | # Provide a "standardized" way to retrieve the CLI args that will
320 | # work with both Windows and non-Windows executions.
321 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
322 | export MAVEN_CMD_LINE_ARGS
323 |
324 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
325 |
326 | # shellcheck disable=SC2086 # safe args
327 | exec "$JAVACMD" \
328 | $MAVEN_OPTS \
329 | $MAVEN_DEBUG_OPTS \
330 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
331 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
332 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
333 |
--------------------------------------------------------------------------------
/mvnw.cmd:
--------------------------------------------------------------------------------
1 | @REM ----------------------------------------------------------------------------
2 | @REM Licensed to the Apache Software Foundation (ASF) under one
3 | @REM or more contributor license agreements. See the NOTICE file
4 | @REM distributed with this work for additional information
5 | @REM regarding copyright ownership. The ASF licenses this file
6 | @REM to you under the Apache License, Version 2.0 (the
7 | @REM "License"); you may not use this file except in compliance
8 | @REM with the License. You may obtain a copy of the License at
9 | @REM
10 | @REM http://www.apache.org/licenses/LICENSE-2.0
11 | @REM
12 | @REM Unless required by applicable law or agreed to in writing,
13 | @REM software distributed under the License is distributed on an
14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | @REM KIND, either express or implied. See the License for the
16 | @REM specific language governing permissions and limitations
17 | @REM under the License.
18 | @REM ----------------------------------------------------------------------------
19 |
20 | @REM ----------------------------------------------------------------------------
21 | @REM Apache Maven Wrapper startup batch script, version 3.3.2
22 | @REM
23 | @REM Required ENV vars:
24 | @REM JAVA_HOME - location of a JDK home dir
25 | @REM
26 | @REM Optional ENV vars
27 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
28 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
29 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
30 | @REM e.g. to debug Maven itself, use
31 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
32 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
33 | @REM ----------------------------------------------------------------------------
34 |
35 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
36 | @echo off
37 | @REM set title of command window
38 | title %0
39 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
40 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
41 |
42 | @REM set %HOME% to equivalent of $HOME
43 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
44 |
45 | @REM Execute a user defined script before this one
46 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
47 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending
48 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
49 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
50 | :skipRcPre
51 |
52 | @setlocal
53 |
54 | set ERROR_CODE=0
55 |
56 | @REM To isolate internal variables from possible post scripts, we use another setlocal
57 | @setlocal
58 |
59 | @REM ==== START VALIDATION ====
60 | if not "%JAVA_HOME%" == "" goto OkJHome
61 |
62 | echo. >&2
63 | echo Error: JAVA_HOME not found in your environment. >&2
64 | echo Please set the JAVA_HOME variable in your environment to match the >&2
65 | echo location of your Java installation. >&2
66 | echo. >&2
67 | goto error
68 |
69 | :OkJHome
70 | if exist "%JAVA_HOME%\bin\java.exe" goto init
71 |
72 | echo. >&2
73 | echo Error: JAVA_HOME is set to an invalid directory. >&2
74 | echo JAVA_HOME = "%JAVA_HOME%" >&2
75 | echo Please set the JAVA_HOME variable in your environment to match the >&2
76 | echo location of your Java installation. >&2
77 | echo. >&2
78 | goto error
79 |
80 | @REM ==== END VALIDATION ====
81 |
82 | :init
83 |
84 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
85 | @REM Fallback to current working directory if not found.
86 |
87 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
88 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
89 |
90 | set EXEC_DIR=%CD%
91 | set WDIR=%EXEC_DIR%
92 | :findBaseDir
93 | IF EXIST "%WDIR%"\.mvn goto baseDirFound
94 | cd ..
95 | IF "%WDIR%"=="%CD%" goto baseDirNotFound
96 | set WDIR=%CD%
97 | goto findBaseDir
98 |
99 | :baseDirFound
100 | set MAVEN_PROJECTBASEDIR=%WDIR%
101 | cd "%EXEC_DIR%"
102 | goto endDetectBaseDir
103 |
104 | :baseDirNotFound
105 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
106 | cd "%EXEC_DIR%"
107 |
108 | :endDetectBaseDir
109 |
110 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
111 |
112 | @setlocal EnableExtensions EnableDelayedExpansion
113 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
114 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
115 |
116 | :endReadAdditionalConfig
117 |
118 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
121 |
122 | set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar"
123 |
124 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
125 | IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
126 | )
127 |
128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data.
130 | if exist %WRAPPER_JAR% (
131 | if "%MVNW_VERBOSE%" == "true" (
132 | echo Found %WRAPPER_JAR%
133 | )
134 | ) else (
135 | if not "%MVNW_REPOURL%" == "" (
136 | SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar"
137 | )
138 | if "%MVNW_VERBOSE%" == "true" (
139 | echo Couldn't find %WRAPPER_JAR%, downloading it ...
140 | echo Downloading from: %WRAPPER_URL%
141 | )
142 |
143 | powershell -Command "&{"^
144 | "$webclient = new-object System.Net.WebClient;"^
145 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
146 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
147 | "}"^
148 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
149 | "}"
150 | if "%MVNW_VERBOSE%" == "true" (
151 | echo Finished downloading %WRAPPER_JAR%
152 | )
153 | )
154 | @REM End of extension
155 |
156 | @REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
157 | SET WRAPPER_SHA_256_SUM=""
158 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
159 | IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
160 | )
161 | IF NOT %WRAPPER_SHA_256_SUM%=="" (
162 | powershell -Command "&{"^
163 | "Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash;"^
164 | "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
165 | "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
166 | " Write-Error 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
167 | " Write-Error 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
168 | " Write-Error 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
169 | " exit 1;"^
170 | "}"^
171 | "}"
172 | if ERRORLEVEL 1 goto error
173 | )
174 |
175 | @REM Provide a "standardized" way to retrieve the CLI args that will
176 | @REM work with both Windows and non-Windows executions.
177 | set MAVEN_CMD_LINE_ARGS=%*
178 |
179 | %MAVEN_JAVA_EXE% ^
180 | %JVM_CONFIG_MAVEN_PROPS% ^
181 | %MAVEN_OPTS% ^
182 | %MAVEN_DEBUG_OPTS% ^
183 | -classpath %WRAPPER_JAR% ^
184 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
185 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
186 | if ERRORLEVEL 1 goto error
187 | goto end
188 |
189 | :error
190 | set ERROR_CODE=1
191 |
192 | :end
193 | @endlocal & set ERROR_CODE=%ERROR_CODE%
194 |
195 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
196 | @REM check for post script, once with legacy .bat ending and once with .cmd ending
197 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
198 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
199 | :skipRcPost
200 |
201 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
202 | if "%MAVEN_BATCH_PAUSE%"=="on" pause
203 |
204 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
205 |
206 | cmd /C exit /B %ERROR_CODE%
207 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 | com.melloware
6 | quarkus-primereact
7 | 10.9.7
8 | Quarkus PrimeReact
9 | Quarkus monorepo demonstrating Panache REST server with PrimeReact UI client
10 | https://github.com/melloware/quarkus-monorepo
11 |
12 | 3.30.2
13 | 3.4.1
14 | 1.8.0
15 | 0.7.2
16 | 3.21.0
17 | 2.7.0
18 | 1.18.42
19 |
20 | 3.5.4
21 | 17
22 | UTF-8
23 | UTF-8
24 |
25 |
26 |
27 |
28 | io.quarkus
29 | quarkus-bom
30 | ${quarkus.platform.version}
31 | import
32 | pom
33 |
34 |
35 |
36 |
37 |
38 |
39 | io.quarkus
40 | quarkus-rest-jackson
41 |
42 |
43 | io.quarkus
44 | quarkus-websockets-next
45 |
46 |
47 | io.quarkus
48 | quarkus-smallrye-openapi
49 |
50 |
51 | io.quarkus
52 | quarkus-smallrye-health
53 |
54 |
55 | io.quarkus
56 | quarkus-info
57 |
58 |
59 | io.quarkus
60 | quarkus-cache
61 |
62 |
63 | io.quarkus
64 | quarkus-hibernate-orm-panache
65 |
66 |
67 | io.quarkus
68 | quarkus-hibernate-validator
69 |
70 |
71 | io.quarkus
72 | quarkus-liquibase
73 |
74 |
75 | io.quarkus
76 | quarkus-jdbc-postgresql
77 |
78 |
79 | io.quarkus
80 | quarkus-jdbc-postgresql-deployment
81 | provided
82 |
83 |
84 |
85 | io.quarkiverse.quinoa
86 | quarkus-quinoa
87 | ${quarkus.quinoa.version}
88 |
89 |
90 | io.quarkiverse.embedded.postgresql
91 | quarkus-embedded-postgresql
92 | ${quarkus.postgresql.version}
93 |
94 |
95 | io.quarkiverse.ngrok
96 | quarkus-ngrok
97 | ${quarkus.ngrok.version}
98 |
99 |
100 | io.quarkiverse.loggingmanager
101 | quarkus-logging-manager
102 | ${quarkus.logmanager.version}
103 | runtime
104 |
105 |
106 | io.quarkiverse.resteasy-problem
107 | quarkus-resteasy-problem
108 | ${quarkus.rest-problem.version}
109 |
110 |
111 |
112 | com.aayushatharva.brotli4j
113 | native-linux-x86_64
114 |
115 |
116 |
117 | org.projectlombok
118 | lombok
119 | ${lombok.version}
120 | provided
121 |
122 |
123 |
124 | io.quarkus
125 | quarkus-junit5
126 | test
127 |
128 |
129 |
130 |
131 |
132 |
133 | org.codehaus.mojo
134 | exec-maven-plugin
135 | 3.6.2
136 |
137 | docker
138 | ${project.basedir}
139 |
140 | build
141 | -f
142 | src/main/docker/Dockerfile.jvm
143 | -t
144 | melloware/${project.artifactId}:latest
145 | .
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 | io.quarkus
154 | quarkus-maven-plugin
155 | ${quarkus.platform.version}
156 | true
157 |
158 |
159 |
160 | build
161 | generate-code
162 | generate-code-tests
163 | native-image-agent
164 |
165 |
166 |
167 |
168 |
169 | org.apache.maven.plugins
170 | maven-compiler-plugin
171 | 3.14.1
172 |
173 | ${project.build.sourceEncoding}
174 |
175 |
176 | org.projectlombok
177 | lombok
178 | ${lombok.version}
179 |
180 |
181 | io.quarkus
182 | quarkus-extension-processor
183 | ${quarkus.platform.version}
184 |
185 |
186 |
187 |
188 |
189 | maven-surefire-plugin
190 | ${surefire-plugin.version}
191 |
192 |
193 | org.jboss.logmanager.LogManager
194 | ${maven.home}
195 |
196 |
197 |
198 |
199 | maven-failsafe-plugin
200 | ${surefire-plugin.version}
201 |
202 |
203 |
204 | integration-test
205 | verify
206 |
207 |
208 |
209 | ${project.build.directory}/${project.build.finalName}-runner
210 | org.jboss.logmanager.LogManager
211 | ${maven.home}
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 | docker
223 |
224 |
225 | docker
226 |
227 |
228 |
229 | clean package
230 |
231 |
232 | org.codehaus.mojo
233 | exec-maven-plugin
234 |
235 |
236 | docker-build
237 | package
238 |
239 | exec
240 |
241 |
242 |
243 | build
244 | -f
245 | src/main/docker/Dockerfile.jvm
246 | -t
247 | melloware/${project.artifactId}:latest
248 | .
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 | native
259 |
260 |
261 | native
262 |
263 |
264 |
265 | true
266 | true
267 |
268 |
269 | clean package
270 |
271 |
272 | org.codehaus.mojo
273 | exec-maven-plugin
274 |
275 |
276 | docker-build
277 | package
278 |
279 | exec
280 |
281 |
282 |
283 | build
284 | -f
285 | src/main/docker/Dockerfile.native-micro
286 | -t
287 | melloware/${project.artifactId}:${project.version}
288 | .
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
--------------------------------------------------------------------------------
/src/main/docker/Dockerfile.jvm:
--------------------------------------------------------------------------------
1 | ####
2 | # This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
3 | #
4 | # Before building the container image run:
5 | #
6 | # ./mvnw package
7 | #
8 | # Then, build the image with:
9 | #
10 | # docker build -f src/main/docker/Dockerfile.jvm -t melloware/quarkus-primereact .
11 | #
12 | # Then run the container using:
13 | #
14 | # docker run -i --rm -p 8080:8080 melloware/quarkus-primereact
15 | #
16 | # If you want to include the debug port into your docker image
17 | # you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005.
18 | # Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005
19 | # when running the container
20 | #
21 | # Then run the container using :
22 | #
23 | # docker run -i --rm -p 8080:8080 melloware/quarkus-primereact
24 | #
25 | # This image uses the `run-java.sh` script to run the application.
26 | # This scripts computes the command line to execute your Java application, and
27 | # includes memory/GC tuning.
28 | # You can configure the behavior using the following environment properties:
29 | # - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class")
30 | # - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
31 | # in JAVA_OPTS (example: "-Dsome.property=foo")
32 | # - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
33 | # used to calculate a default maximal heap memory based on a containers restriction.
34 | # If used in a container without any memory constraints for the container then this
35 | # option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
36 | # of the container available memory as set here. The default is `50` which means 50%
37 | # of the available memory is used as an upper boundary. You can skip this mechanism by
38 | # setting this value to `0` in which case no `-Xmx` option is added.
39 | # - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
40 | # is used to calculate a default initial heap memory based on the maximum heap memory.
41 | # If used in a container without any memory constraints for the container then this
42 | # option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
43 | # of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
44 | # is used as the initial heap size. You can skip this mechanism by setting this value
45 | # to `0` in which case no `-Xms` option is added (example: "25")
46 | # - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
47 | # This is used to calculate the maximum value of the initial heap memory. If used in
48 | # a container without any memory constraints for the container then this option has
49 | # no effect. If there is a memory constraint then `-Xms` is limited to the value set
50 | # here. The default is 4096MB which means the calculated value of `-Xms` never will
51 | # be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
52 | # - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
53 | # when things are happening. This option, if set to true, will set
54 | # `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
55 | # - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
56 | # true").
57 | # - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
58 | # - CONTAINER_CORE_LIMIT: A calculated core limit as described in
59 | # https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
60 | # - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
61 | # - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
62 | # (example: "20")
63 | # - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
64 | # (example: "40")
65 | # - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
66 | # (example: "4")
67 | # - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
68 | # previous GC times. (example: "90")
69 | # - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
70 | # - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
71 | # - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
72 | # contain the necessary JRE command-line options to specify the required GC, which
73 | # will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
74 | # - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
75 | # - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
76 | # - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
77 | # accessed directly. (example: "foo.example.com,bar.example.com")
78 | #
79 | ###
80 | FROM registry.access.redhat.com/ubi8/openjdk-21:1.20
81 |
82 | ENV LANGUAGE='en_US:en'
83 |
84 | # We make four distinct layers so if there are application changes the library layers can be re-used
85 | COPY --chown=185 target/quarkus-app/lib/ /deployments/lib/
86 | COPY --chown=185 target/quarkus-app/*.jar /deployments/
87 | COPY --chown=185 target/quarkus-app/app/ /deployments/app/
88 | COPY --chown=185 target/quarkus-app/quarkus/ /deployments/quarkus/
89 |
90 | EXPOSE 8080
91 | USER 185
92 | ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
93 | ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
94 |
95 | ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ]
96 |
97 |
--------------------------------------------------------------------------------
/src/main/docker/Dockerfile.native:
--------------------------------------------------------------------------------
1 | ####
2 | # This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
3 | #
4 | # Before building the container image run:
5 | #
6 | # ./mvnw package -Dnative
7 | #
8 | # Then, build the image with:
9 | #
10 | # docker build -f src/main/docker/Dockerfile.native -t melloware/quarkus-primereact .
11 | #
12 | # Then run the container using:
13 | #
14 | # docker run -i --rm -p 8080:8080 melloware/quarkus-primereact
15 | #
16 | ###
17 | FROM registry.access.redhat.com/ubi8/ubi-minimal:8.10
18 | WORKDIR /work/
19 | RUN chown 1001 /work \
20 | && chmod "g+rwX" /work \
21 | && chown 1001:root /work
22 | COPY --chown=1001:root target/*-runner /work/application
23 |
24 | EXPOSE 8080
25 | USER 1001
26 |
27 | ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"]
28 |
--------------------------------------------------------------------------------
/src/main/docker/Dockerfile.native-micro:
--------------------------------------------------------------------------------
1 | ####
2 | # This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
3 | # It uses a micro base image, tuned for Quarkus native executables.
4 | # It reduces the size of the resulting container image.
5 | # Check https://quarkus.io/guides/quarkus-runtime-base-image for further information about this image.
6 | #
7 | # Before building the container image run:
8 | #
9 | # ./mvnw package -Dnative
10 | #
11 | # Then, build the image with:
12 | #
13 | # docker build -f src/main/docker/Dockerfile.native-micro -t melloware/quarkus-primereact .
14 | #
15 | # Then run the container using:
16 | #
17 | # docker run -i --rm -p 8080:8080 melloware/quarkus-primereact
18 | #
19 | ###
20 | FROM quay.io/quarkus/quarkus-micro-image:2.0
21 | WORKDIR /work/
22 | RUN chown 1001 /work \
23 | && chmod "g+rwX" /work \
24 | && chown 1001:root /work
25 | COPY --chown=1001:root target/*-runner /work/application
26 |
27 | EXPOSE 8080
28 | USER 1001
29 |
30 | ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"]
31 |
--------------------------------------------------------------------------------
/src/main/java/com/melloware/quarkus/panache/Car.java:
--------------------------------------------------------------------------------
1 | package com.melloware.quarkus.panache;
2 |
3 | import java.math.BigDecimal;
4 | import java.time.Instant;
5 |
6 | import org.eclipse.microprofile.openapi.annotations.media.Schema;
7 |
8 | import io.quarkus.hibernate.orm.panache.PanacheEntity;
9 | import jakarta.persistence.Cacheable;
10 | import jakarta.persistence.Column;
11 | import jakarta.persistence.Entity;
12 | import jakarta.persistence.Table;
13 | import jakarta.persistence.Version;
14 | import jakarta.validation.constraints.DecimalMax;
15 | import jakarta.validation.constraints.DecimalMin;
16 | import jakarta.validation.constraints.Max;
17 | import jakarta.validation.constraints.Min;
18 | import jakarta.validation.constraints.NotBlank;
19 | import jakarta.validation.constraints.Size;
20 |
21 | import lombok.AllArgsConstructor;
22 | import lombok.NoArgsConstructor;
23 |
24 | @Entity
25 | @Table(name = "CAR")
26 | @Cacheable
27 | @AllArgsConstructor
28 | @NoArgsConstructor
29 | @Schema(name = "Car", description = "Entity that represents a car.")
30 | public class Car extends PanacheEntity {
31 |
32 | @Column(unique = true)
33 | @NotBlank(message = "VIN may not be blank")
34 | @Size(max=17, message = "VIN may not be more than 17 characters")
35 | @Schema(required = true, examples = {"WVGEF9BP4DD085048"}, description = "VIN number")
36 | public String vin;
37 | @NotBlank(message = "Make may not be blank")
38 | @Size(max=255, message = "Make may not be more than 255 characters")
39 | @Schema(required = true, examples = {"BMW"}, description = "Manufacturer")
40 | public String make;
41 | @NotBlank(message = "Model may not be blank")
42 | @Size(max=255, message = "Model may not be more than 255 characters")
43 | @Schema(required = true, examples = {"330ix"}, description = "Model Number")
44 | public String model;
45 | @Min(value = 1960)
46 | @Max(value = 2050)
47 | @Schema(required = true, examples = {"1974"}, description = "Year of manufacture")
48 | public int year;
49 | @NotBlank(message = "Color may not be blank")
50 | @Size(max=20, message = "Color may not be more than 20 characters")
51 | @Schema(required = true, examples = {"891d4c"}, description = "HTML color of the car")
52 | public String color;
53 | @DecimalMin(value = "0.00")
54 | @DecimalMax(value = "250000.00")
55 | @Schema(required = true, examples = {"9999.99"}, description = "Price")
56 | public BigDecimal price;
57 |
58 | @Version
59 | @Column(name = "modified_time")
60 | @Schema(description = "Modified time of the record")
61 | public Instant modifiedTime;
62 |
63 | }
--------------------------------------------------------------------------------
/src/main/java/com/melloware/quarkus/panache/CarResource.java:
--------------------------------------------------------------------------------
1 | package com.melloware.quarkus.panache;
2 |
3 | import java.util.List;
4 | import java.util.Map;
5 |
6 | import org.eclipse.microprofile.openapi.annotations.Operation;
7 | import org.eclipse.microprofile.openapi.annotations.media.Content;
8 | import org.eclipse.microprofile.openapi.annotations.media.Schema;
9 | import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
10 | import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
11 | import org.eclipse.microprofile.openapi.annotations.tags.Tag;
12 |
13 | import com.fasterxml.jackson.core.JsonProcessingException;
14 | import com.fasterxml.jackson.databind.ObjectMapper;
15 | import com.melloware.quarkus.support.QueryRequest;
16 | import com.melloware.quarkus.support.QueryResponse;
17 |
18 | import io.quarkus.hibernate.orm.panache.PanacheQuery;
19 | import io.quarkus.panache.common.Sort;
20 | import io.quarkus.runtime.annotations.RegisterForReflection;
21 | import jakarta.enterprise.context.ApplicationScoped;
22 | import jakarta.inject.Inject;
23 | import jakarta.transaction.Transactional;
24 | import jakarta.validation.Valid;
25 | import jakarta.validation.constraints.Min;
26 | import jakarta.ws.rs.Consumes;
27 | import jakarta.ws.rs.DELETE;
28 | import jakarta.ws.rs.GET;
29 | import jakarta.ws.rs.NotFoundException;
30 | import jakarta.ws.rs.POST;
31 | import jakarta.ws.rs.PUT;
32 | import jakarta.ws.rs.Path;
33 | import jakarta.ws.rs.PathParam;
34 | import jakarta.ws.rs.Produces;
35 | import jakarta.ws.rs.QueryParam;
36 | import jakarta.ws.rs.WebApplicationException;
37 | import jakarta.ws.rs.core.MediaType;
38 | import jakarta.ws.rs.core.Response;
39 | import jakarta.ws.rs.core.Response.Status;
40 | import lombok.extern.jbosslog.JBossLog;
41 |
42 | @Path("entity/cars")
43 | @ApplicationScoped
44 | @Produces(MediaType.APPLICATION_JSON)
45 | @Consumes(MediaType.APPLICATION_JSON)
46 | @JBossLog
47 | @RegisterForReflection
48 | @Tag(name = "Car Resource", description = "CRUD operations for the Car entity.")
49 | public class CarResource {
50 |
51 | @Inject
52 | ObjectMapper objectMapper;
53 |
54 | @GET
55 | @Path("{id}")
56 | @Operation(summary = "Get a car by ID", description = "Returns a car based on the provided ID")
57 | @APIResponses({
58 | @APIResponse(responseCode = "200", description = "Success"),
59 | @APIResponse(responseCode = "404", description = "Car not found")
60 | })
61 | public Car getSingle(@PathParam("id") @Min(value = 0) Long id) {
62 | LOG.infof("Get Car: %s", id);
63 | Car entity = Car.findById(id);
64 | if (entity == null) {
65 | throw new NotFoundException("Car with id of " + id + " does not exist.");
66 | }
67 | return entity;
68 | }
69 |
70 | @GET
71 | @Path("/manufacturers")
72 | @Operation(summary = "Get all manufacturers", description = "Returns a list of distinct car manufacturers")
73 | @APIResponse(responseCode = "200", description = "Success")
74 | public List getManufacturers() {
75 | LOG.infof("Get Unique Manufacturers...");
76 | return Car.find("select distinct make from Car order by make").project(String.class).list();
77 | }
78 |
79 |
80 | @POST
81 | @Transactional
82 | @Operation(summary = "Create a new car", description = "Creates a new car entry")
83 | @APIResponses({
84 | @APIResponse(responseCode = "201", description = "Car created successfully" ,content = @Content(mediaType = "application/json", schema = @Schema(implementation = Car.class))),
85 | @APIResponse(responseCode = "400", description = "Invalid request format"),
86 | @APIResponse(responseCode = "422", description = "Invalid car data provided")
87 | })
88 | public Response create(@Valid Car car) {
89 | LOG.infof("Create Car: %s", car);
90 | if (car.id != null) {
91 | // 422 Unprocessable Entity
92 | throw new WebApplicationException("Id was invalidly set on request.", 422);
93 | }
94 | car.persist();
95 | return Response.ok(car).status(Status.CREATED).build();
96 | }
97 |
98 | @PUT
99 | @Path("{id}")
100 | @Transactional
101 | @Operation(summary = "Update a car", description = "Updates an existing car based on ID")
102 | @APIResponses({
103 | @APIResponse(responseCode = "200", description = "Car updated successfully"),
104 | @APIResponse(responseCode = "400", description = "Invalid request format"),
105 | @APIResponse(responseCode = "404", description = "Car not found"),
106 | @APIResponse(responseCode = "422", description = "Invalid car data provided")
107 | })
108 | public Car update(@PathParam("id") @Min(value = 0) Long id, @Valid Car car) {
109 | LOG.infof("Update Car: %s", id);
110 | Car entity = Car.findById(id);
111 | if (entity == null) {
112 | throw new NotFoundException("Car with id of " + id + " does not exist.");
113 |
114 | }
115 |
116 | // would normally use ModelMapper here: https://modelmapper.org/
117 | entity.make = car.make;
118 | entity.model = car.model;
119 | entity.year = car.year;
120 | entity.vin = car.vin;
121 | entity.color = car.color;
122 | entity.price = car.price;
123 |
124 | return entity;
125 | }
126 |
127 | @DELETE
128 | @Path("{id}")
129 | @Transactional
130 | @Operation(summary = "Delete a car", description = "Deletes a car based on ID")
131 | @APIResponses({
132 | @APIResponse(responseCode = "204", description = "Car successfully deleted"),
133 | @APIResponse(responseCode = "404", description = "Car not found")
134 | })
135 | public Response delete(@PathParam("id") @Min(value = 0) Long id) {
136 | LOG.infof("Delete Car: %s", id);
137 | Car entity = Car.findById(id);
138 | if (entity == null) {
139 | throw new NotFoundException("Car with id of " + id + " does not exist.");
140 | }
141 | entity.delete();
142 | return Response.noContent().build();
143 | }
144 |
145 | @GET
146 | @Operation(summary = "List cars", description = "Returns a paginated list of cars with optional filtering and sorting")
147 | @APIResponses({
148 | @APIResponse(responseCode = "200", description = "Success"),
149 | @APIResponse(responseCode = "400", description = "Invalid request format")
150 | })
151 | public QueryResponse list(@QueryParam("request") String lazyRequest) throws JsonProcessingException {
152 | LOG.debugf("List Cars: %s", lazyRequest);
153 | try {
154 | // add a delay to simulate a slow response
155 | Thread.sleep(250);
156 | } catch (InterruptedException e) {
157 | // do nothing
158 | }
159 | final QueryResponse response = new QueryResponse<>();
160 | if (lazyRequest == null || lazyRequest.isEmpty()) {
161 | List results = Car.listAll(Sort.by("make"));
162 | response.setTotalRecords(results.size());
163 | response.setRecords(results);
164 | return response;
165 | }
166 |
167 | final QueryRequest request = objectMapper.readValue(lazyRequest, QueryRequest.class);
168 | LOG.debug(request);
169 |
170 | // sorts
171 | final Sort sort = request.calculateSort();
172 |
173 | // filters
174 | final QueryRequest.FilterCriteria filterMeta = request.calculateFilters(QueryRequest.FilterOperator.AND);
175 | final String filterQuery = filterMeta.getQuery();
176 | final Map filters = filterMeta.getParameters();
177 |
178 | PanacheQuery query = Car.findAll(sort);
179 | if (!filters.isEmpty()) {
180 | Map map = request.calculateFilterParameters();
181 | query = Car.find(filterQuery, sort, map);
182 | }
183 |
184 | // range
185 | query.range(request.getFirst(), request.getFirst() + request.getRows());
186 |
187 | // response
188 | response.setTotalRecords(query.count());
189 | response.setRecords(query.list());
190 | return response;
191 | }
192 | }
--------------------------------------------------------------------------------
/src/main/java/com/melloware/quarkus/socket/PushWebSocket.java:
--------------------------------------------------------------------------------
1 | package com.melloware.quarkus.socket;
2 |
3 | import org.apache.commons.lang3.exception.ExceptionUtils;
4 |
5 | import io.quarkus.websockets.next.OnClose;
6 | import io.quarkus.websockets.next.OnError;
7 | import io.quarkus.websockets.next.OnOpen;
8 | import io.quarkus.websockets.next.OnPingMessage;
9 | import io.quarkus.websockets.next.OnPongMessage;
10 | import io.quarkus.websockets.next.OnTextMessage;
11 | import io.quarkus.websockets.next.WebSocket;
12 | import io.quarkus.websockets.next.WebSocketConnection;
13 | import io.smallrye.common.annotation.RunOnVirtualThread;
14 | import io.vertx.core.buffer.Buffer;
15 | import jakarta.inject.Inject;
16 | import lombok.extern.jbosslog.JBossLog;
17 |
18 | /**
19 | * WebSocket endpoint for push notifications.
20 | * Handles user connections, disconnections and message broadcasting.
21 | */
22 | @WebSocket(path = "/push")
23 | @JBossLog
24 | public class PushWebSocket {
25 |
26 | /**
27 | * The WebSocket connection instance.
28 | */
29 | @Inject
30 | WebSocketConnection connection;
31 |
32 | /**
33 | * Handles new WebSocket connections.
34 | */
35 | @OnOpen
36 | @RunOnVirtualThread
37 | public void onOpen(WebSocketConnection connection) {
38 | LOG.infof("Websocket connection opened: %s", connection.id());
39 | }
40 |
41 | /**
42 | * Handles WebSocket disconnections.
43 | */
44 | @OnClose
45 | @RunOnVirtualThread
46 | public void onClose(WebSocketConnection connection) {
47 | LOG.infof("Websocket connection closed: %s", connection.id());
48 | }
49 |
50 | /**
51 | * Handles incoming text messages from WebSocket clients.
52 | * Processes JS "ping" messages and returns appropriate "pong" responses.
53 | *
54 | * @param message The text message received from the client
55 | * @return A response message to be sent back to the client
56 | */
57 | @OnTextMessage(broadcast = false)
58 | @RunOnVirtualThread
59 | public String onMessage(String message) {
60 | if ("ping".equals(message)) {
61 | LOG.debugf("Websocket Ping message received: %s", message);
62 | return "pong";
63 | }
64 | LOG.infof("Websocket message received: %s", message);
65 | return message;
66 | }
67 |
68 | /**
69 | * Handles incoming ping messages from WebSocket clients.
70 | * Automatically responds with a pong message.
71 | *
72 | * @param data The ping message data received from the client
73 | * @see https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.2
74 | */
75 | @OnPingMessage
76 | @RunOnVirtualThread
77 | void ping(Buffer data) {
78 | // an incoming ping data frame that will automatically receive a pong
79 | LOG.debugf("Websocket Ping received: %s", data);
80 | }
81 |
82 | /**
83 | * Handles incoming pong data frame messages from WebSocket clients.
84 | * These are responses to previously sent ping messages.
85 | *
86 | * @param data The pong message data received from the client
87 | * @see https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.2
88 | */
89 | @OnPongMessage
90 | @RunOnVirtualThread
91 | void pong(Buffer data) {
92 | // an incoming pong data frame in response to the last ping sent
93 | LOG.debugf("Websocket Pong received: %s", data);
94 | }
95 |
96 | /**
97 | * Handles WebSocket errors and exceptions.
98 | * Logs the root cause of the exception and returns an error message.
99 | *
100 | * @param e The exception that occurred
101 | * @return An error message to be sent back to the client
102 | */
103 | @OnError
104 | @RunOnVirtualThread
105 | public String onException(Exception e) {
106 | // Handles Exception and all subclasses except for IOException.
107 | LOG.errorf("Websocket Exception: %s", ExceptionUtils.getRootCauseMessage(e));
108 | return "Error";
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/main/java/com/melloware/quarkus/socket/SocketMessage.java:
--------------------------------------------------------------------------------
1 | package com.melloware.quarkus.socket;
2 |
3 | import java.time.ZoneOffset;
4 | import java.time.ZonedDateTime;
5 | import java.time.temporal.ChronoUnit;
6 | import java.util.Map;
7 |
8 | import org.eclipse.microprofile.openapi.annotations.media.Schema;
9 |
10 | import io.quarkus.runtime.annotations.RegisterForReflection;
11 | import lombok.Builder;
12 | import lombok.Data;
13 |
14 |
15 | @Data
16 | @Builder
17 | @RegisterForReflection
18 | @Schema(description = "WebSocket message for real-time updates")
19 | public class SocketMessage {
20 |
21 | @Schema(description = "Unique identifier for the message", examples = "03001000c0020000")
22 | private String id;
23 |
24 | @Builder.Default
25 | @Schema(description = "UTC timestamp of when the message was created", examples = "2024-03-20T10:30:00Z")
26 | private ZonedDateTime timestamp = ZonedDateTime.now().withZoneSameInstant(ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS);
27 |
28 | @Schema(required = true, description = "Type of socket message", examples = {"USER_JOINED", "USER_LEFT", "REFRESH_DATA", "NOTIFICATION"})
29 | SocketMessageType type;
30 |
31 | @Schema(description = "Optional message payload", examples = {"User connected", "Please refresh your data"})
32 | String message;
33 |
34 | @Schema(description = "Additional context information for the message", examples = "{\"source\": \"system\", \"priority\": \"high\"}")
35 | private Map context;
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/com/melloware/quarkus/socket/SocketMessageType.java:
--------------------------------------------------------------------------------
1 | package com.melloware.quarkus.socket;
2 |
3 | /**
4 | * Enum representing different types of WebSocket messages that can be sent.
5 | */
6 | public enum SocketMessageType {
7 | /**
8 | * Indicates that connected clients should refresh their data
9 | */
10 | REFRESH_DATA,
11 |
12 | /**
13 | * Indicates a notification message should be shown to the user
14 | */
15 | NOTIFICATION;
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/com/melloware/quarkus/socket/SocketResource.java:
--------------------------------------------------------------------------------
1 | package com.melloware.quarkus.socket;
2 |
3 | import java.util.UUID;
4 |
5 | import org.apache.commons.lang3.StringUtils;
6 | import org.eclipse.microprofile.openapi.annotations.Operation;
7 | import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
8 | import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
9 | import org.eclipse.microprofile.openapi.annotations.tags.Tag;
10 | import org.slf4j.MDC;
11 |
12 | import io.quarkus.runtime.annotations.RegisterForReflection;
13 | import io.quarkus.websockets.next.OpenConnections;
14 | import jakarta.enterprise.context.ApplicationScoped;
15 | import jakarta.inject.Inject;
16 | import jakarta.ws.rs.Consumes;
17 | import jakarta.ws.rs.POST;
18 | import jakarta.ws.rs.Path;
19 | import jakarta.ws.rs.Produces;
20 | import jakarta.ws.rs.QueryParam;
21 | import jakarta.ws.rs.WebApplicationException;
22 | import jakarta.ws.rs.core.MediaType;
23 | import jakarta.ws.rs.core.Response;
24 | import jakarta.ws.rs.core.Response.Status;
25 | import lombok.extern.jbosslog.JBossLog;
26 |
27 | /**
28 | * REST resource for WebSocket operations.
29 | * Provides endpoints for sending notifications and refresh signals to connected
30 | * clients.
31 | */
32 | @Path("socket")
33 | @ApplicationScoped
34 | @Produces(MediaType.APPLICATION_JSON)
35 | @Consumes(MediaType.APPLICATION_JSON)
36 | @JBossLog
37 | @RegisterForReflection
38 | @Tag(name = "WebSocket Resource", description = "WebSocket operations.")
39 | public class SocketResource {
40 |
41 | /** Connection manager for WebSocket clients */
42 | @Inject
43 | OpenConnections connections;
44 |
45 | /**
46 | * Pushes a notification message to all connected WebSocket clients.
47 | *
48 | * @param message The notification message to send
49 | * @return Response with status 201 if successful
50 | * @throws WebApplicationException with status 422 if message is blank
51 | */
52 | @Path("notify")
53 | @POST
54 | @Operation(summary = "Push notification message", description = "Pushes a notification message to all connected clients")
55 | @APIResponses({
56 | @APIResponse(responseCode = "201", description = "Notification message sent successfully"),
57 | @APIResponse(responseCode = "422", description = "Message cannot be null or blank")
58 | })
59 | public Response notify(@QueryParam("message") String message) {
60 | if (StringUtils.isBlank(message)) {
61 | // 422 Unprocessable Entity
62 | throw new WebApplicationException("Id was invalidly set on request.", 422);
63 | }
64 | SocketMessage pushMessage = SocketMessage.builder().id(UUID.randomUUID().toString())
65 | .type(SocketMessageType.NOTIFICATION).message(message).context(MDC.getCopyOfContextMap()).build();
66 | sendMessage(pushMessage);
67 | return Response.ok(pushMessage).status(Status.CREATED).build();
68 | }
69 |
70 | /**
71 | * Pushes a refresh signal to all connected WebSocket clients.
72 | * This will trigger clients to refresh their UI data.
73 | *
74 | * @return Response with status 201 if successful
75 | */
76 | @Path("refresh")
77 | @POST
78 | @Operation(summary = "Push a UI refresh signal", description = "Pushes a UI refresh signal to all connected clients")
79 | @APIResponses({
80 | @APIResponse(responseCode = "201", description = "Refresh UI message sent successfully"),
81 | })
82 | public Response refresh() {
83 | SocketMessage pushMessage = SocketMessage.builder().id(UUID.randomUUID().toString())
84 | .type(SocketMessageType.REFRESH_DATA).context(MDC.getCopyOfContextMap()).build();
85 | sendMessage(pushMessage);
86 | return Response.ok(pushMessage).status(Status.CREATED).build();
87 | }
88 |
89 | /**
90 | * Helper method to send a message to all connected WebSocket clients.
91 | * Includes a simulated processing delay of 2 seconds.
92 | *
93 | * @param message The SocketMessage to send to all clients
94 | */
95 | private void sendMessage(SocketMessage message) {
96 | LOG.infof("WebSocket: %s", message);
97 | try {
98 | // sleep to simulate time processing an event before publishing
99 | Thread.sleep(2000);
100 | } catch (InterruptedException e) {
101 | LOG.error("Error sending websocketmessage", e);
102 | }
103 | connections.forEach(connection -> connection.sendTextAndAwait(message));
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/main/java/com/melloware/quarkus/support/QueryRequest.java:
--------------------------------------------------------------------------------
1 | package com.melloware.quarkus.support;
2 |
3 | import java.time.Instant;
4 | import java.util.ArrayList;
5 | import java.util.HashMap;
6 | import java.util.List;
7 | import java.util.Map;
8 | import java.util.Objects;
9 | import java.util.concurrent.atomic.AtomicInteger;
10 | import java.util.stream.Collectors;
11 |
12 | import org.apache.commons.lang3.StringUtils;
13 | import org.eclipse.microprofile.openapi.annotations.media.Schema;
14 |
15 | import com.fasterxml.jackson.annotation.JsonIgnore;
16 |
17 | import io.quarkus.panache.common.Sort;
18 | import io.quarkus.runtime.annotations.RegisterForReflection;
19 | import lombok.AllArgsConstructor;
20 | import lombok.Data;
21 | import lombok.EqualsAndHashCode;
22 | import lombok.NoArgsConstructor;
23 |
24 | /**
25 | * Represents a PrimeReact query request from the UI for a complex datatable with multiple sorts, multiple filters, and
26 | * pagination.
27 | *
28 | * This class handles:
29 | *
30 | *
Pagination - first record, rows per page, page number
31 | *
Sorting - both single field and multi-field sorting
32 | *
Filtering - complex filtering with multiple constraints and operators
33 | *
34 | *
35 | * The class provides methods to:
36 | *
37 | *
Calculate sort criteria for Panache queries
38 | *
Build filter criteria with AND/OR operators
39 | *
Generate SQL parameters for filters
40 | *
41 | */
42 | @Data
43 | @NoArgsConstructor
44 | @AllArgsConstructor
45 | @RegisterForReflection
46 | @Schema(description = "Represents a PrimeReact query request from the UI for a complex datatable with multiple sorts, multiple filters, and pagination.")
47 | public class QueryRequest {
48 |
49 | /**
50 | * The index of the first record to return
51 | */
52 | @Schema(examples = {"1"}, description = "First record")
53 | private int first;
54 |
55 | /**
56 | * Number of rows to return per page
57 | */
58 | @Schema(examples = {"10"}, description = "Number of rows")
59 | private int rows;
60 |
61 | /**
62 | * Current page number
63 | */
64 | @Schema(examples = {"1"}, description = "Page number")
65 | private int page;
66 |
67 | /**
68 | * Field name to sort by when using single field sorting
69 | */
70 | @Schema(examples = {"firstName"}, description = "Sort field if single field sorting")
71 | private String sortField;
72 |
73 | /**
74 | * Sort direction for single field sorting (-1 desc, 0 none, 1 asc)
75 | */
76 | @Schema(examples = {"1"}, description = "Sort order if single field sorting either -1 desc, 0 none, 1 asc")
77 | private int sortOrder;
78 |
79 | /**
80 | * List of sort criteria for multiple field sorting
81 | */
82 | @Schema(description = "Multiple sorting list of columns to sort and in which order")
83 | private List multiSortMeta = new ArrayList<>();
84 |
85 | /**
86 | * Map of column filters and their criteria
87 | */
88 | @Schema(description = "Map of columns being filtered and their filter criteria")
89 | private Map filters = new HashMap<>();
90 |
91 | /**
92 | * Map of column name overrides to map UI column names to database column names
93 | * e.g. "codeListName" to "codelist.name"
94 | */
95 | @JsonIgnore
96 | private Map overrides = new HashMap<>();
97 |
98 | /**
99 | * Determines if the request uses single field sorting
100 | *
101 | * @return true if using single field sort, false otherwise
102 | */
103 | @JsonIgnore
104 | public boolean isSingleSort() {
105 | return sortField != null && !sortField.isEmpty();
106 | }
107 |
108 | /**
109 | * Determines if the request uses multiple field sorting
110 | *
111 | * @return true if using multiple field sort, false otherwise
112 | */
113 | @JsonIgnore
114 | public boolean isMultipleSort() {
115 | return !getMultiSortMeta().isEmpty();
116 | }
117 |
118 | /**
119 | * Determines if the table is using filterDisplay="menu" vs filterDisplay="row"
120 | *
121 | * @return true if filtering by menu, false if filtering by row
122 | */
123 | @JsonIgnore
124 | public boolean isFilterMenu() {
125 | return getFilters().entrySet().stream().anyMatch(e -> StringUtils.isNotBlank(e.getValue().getOperator()));
126 | }
127 |
128 | /**
129 | * Calculates the Panache Sort criteria based on single or multiple field sorting
130 | *
131 | * @return Sort object configured with the requested sort criteria
132 | */
133 | @JsonIgnore
134 | public Sort calculateSort() {
135 | Sort sort = null;
136 | if (isSingleSort()) {
137 | sort = Sort.by(checkOverride(getSortField()),
138 | getSortOrder() == 1 ? Sort.Direction.Ascending : Sort.Direction.Descending);
139 | } else if (isMultipleSort()) {
140 | for (final QueryRequest.MultiSortMeta multiSortMeta : getMultiSortMeta()) {
141 | if (sort == null) {
142 | sort = Sort.by(checkOverride(multiSortMeta.getField()), multiSortMeta.getSqlOrder());
143 | } else {
144 | sort.and(checkOverride(multiSortMeta.getField()), multiSortMeta.getSqlOrder());
145 | }
146 | }
147 | }
148 | return sort;
149 | }
150 |
151 | /**
152 | * Checks if a column name has an override mapping and returns the mapped name if it exists
153 | *
154 | * @param column The original column name
155 | * @return The mapped column name if an override exists, otherwise the original name
156 | */
157 | private String checkOverride(final String column) {
158 | return overrides.getOrDefault(column, column);
159 | }
160 |
161 | /**
162 | * Calculates the filter criteria and builds the filter query string
163 | *
164 | * @param operator The operator (AND/OR) to use when joining multiple filters
165 | * @return FilterCriteria containing the query string and filter parameters
166 | */
167 | public FilterCriteria calculateFilters(final FilterOperator operator) {
168 | final Map filters = getFilters();
169 |
170 | final StringBuilder filterQuery = new StringBuilder(1024);
171 | if (this.isFilterMenu()) {
172 | for (final Map.Entry entry : filters.entrySet()) {
173 | final String column = checkOverride(entry.getKey());
174 | final MultiFilterMeta filterConstraints = entry.getValue();
175 | final List constraints = filterConstraints.getConstraints();
176 |
177 | constraints.removeIf(e -> StringUtils.isBlank(Objects.toString(e.getValue(), null)));
178 | if (constraints.isEmpty()) {
179 | continue;
180 | }
181 |
182 | final AtomicInteger i = new AtomicInteger();
183 | final String result = filterConstraints.getConstraints().stream()
184 | .map(constraint -> column + constraint.getSqlClause() + " :" +
185 | createColumnVariable(column, i.getAndIncrement()))
186 | .collect(Collectors.joining(" " + filterConstraints.operator + " "));
187 | if (StringUtils.isNotEmpty(result)) {
188 | if (!filterQuery.isEmpty()) {
189 | filterQuery.append(StringUtils.SPACE).append(operator.name()).append(" (");
190 | } else {
191 | filterQuery.append("(");
192 | }
193 | filterQuery.append(result).append(")");
194 | }
195 | }
196 | } else {
197 | filters.entrySet()
198 | .removeIf(e -> StringUtils.isBlank(Objects.toString(e.getValue().getValue(), null)));
199 | final String result = filters.entrySet().stream()
200 | .map(entry -> checkOverride(entry.getKey()) + entry.getValue().getSqlClause() + " :" +
201 | entry.getKey())
202 | .collect(Collectors.joining(" " + operator.name() + " "));
203 | filterQuery.append(result);
204 | }
205 |
206 | return new FilterCriteria(filterQuery.toString(), filters);
207 | }
208 |
209 | /**
210 | * Builds a map of filter parameters and their values for SQL query execution
211 | *
212 | * @return Map of parameter names to their values
213 | */
214 | public Map calculateFilterParameters() {
215 | Map params = new HashMap<>();
216 | if (this.isFilterMenu()) {
217 | for (final Map.Entry entry : filters.entrySet()) {
218 | final String column = checkOverride(entry.getKey());
219 |
220 | int i = 0;
221 | for (final QueryRequest.FilterConstraint constraint : entry.getValue().getConstraints()) {
222 | params.put(createColumnVariable(column, i++), constraint.getSqlValue());
223 | }
224 | }
225 | } else {
226 | params = filters.entrySet().stream()
227 | .collect(Collectors.toMap(e -> checkOverride(e.getKey()), e -> e.getValue().getSqlValue()));
228 | }
229 | return params;
230 | }
231 |
232 | /**
233 | * Creates a unique parameter name for a column and counter
234 | *
235 | * @param column The column name
236 | * @param counter The counter value
237 | * @return A unique parameter name
238 | */
239 | private String createColumnVariable(final String column, final int counter) {
240 | return StringUtils.remove(column, '.') + counter;
241 | }
242 |
243 | /**
244 | * Enum defining filter operators for joining multiple filters
245 | */
246 | @RegisterForReflection
247 | public enum FilterOperator {
248 | AND,
249 | OR
250 | }
251 |
252 | /**
253 | * Class representing multiple sort criteria for a single field
254 | */
255 | @Data
256 | @NoArgsConstructor
257 | @RegisterForReflection
258 | public static class MultiSortMeta {
259 | @Schema(examples = {"lastName"}, description = "Sort field for this multiple sort")
260 | private String field;
261 | @Schema(examples = {"1"}, description = "Sort order for this field either -1 desc, 0 none, 1 asc")
262 | private int order;
263 |
264 | /**
265 | * Converts the numeric order to a Panache Sort.Direction
266 | *
267 | * @return Sort.Direction.Ascending for 1, Sort.Direction.Descending otherwise
268 | */
269 | @JsonIgnore
270 | public Sort.Direction getSqlOrder() {
271 | return order == 1 ? Sort.Direction.Ascending : Sort.Direction.Descending;
272 | }
273 | }
274 |
275 | /**
276 | * Class representing a single filter constraint
277 | */
278 | @Data
279 | @NoArgsConstructor
280 | @RegisterForReflection
281 | public static class FilterConstraint {
282 | @Schema(description = "Value to filter this column by")
283 | private Object value;
284 | @Schema(examples = {"equals"}, description = "Filter match mode e.g. equals, notEquals, contains, notContains, gt, gte, lt, lte")
285 | private String matchMode;
286 |
287 | /**
288 | * Gets the SQL operator for the filter match mode
289 | *
290 | * @return The SQL operator string
291 | */
292 | @JsonIgnore
293 | public String getSqlClause() {
294 | return switch (matchMode) {
295 | case "equals", "dateIs" -> "=";
296 | case "notEquals", "dateIsNot" -> "!=";
297 | case "notContains" -> " not like ";
298 | case "gt", "dateAfter" -> ">";
299 | case "gte" -> ">=";
300 | case "lt", "dateBefore" -> "<";
301 | case "lte" -> "<=";
302 | default -> " like ";
303 | };
304 | }
305 |
306 | /**
307 | * Converts the filter value to the appropriate SQL value based on match mode
308 | *
309 | * @return The converted value for SQL
310 | */
311 | @JsonIgnore
312 | public Object getSqlValue() {
313 | final Object value = getValue();
314 | return switch (matchMode) {
315 | case "contains", "notContains" -> "%" + value + "%";
316 | case "startsWith" -> value + "%";
317 | case "endsWith" -> "%" + value;
318 | case "gt", "gte", "lt", "lte" ->
319 | Integer.parseInt((String) value);
320 | case "dateAfter", "dateBefore", "dateIs", "dateIsNot" -> Instant.parse((String) value);
321 | default -> value;
322 | };
323 | }
324 | }
325 |
326 | /**
327 | * Class representing multiple filter constraints for a single field
328 | */
329 | @Data
330 | @NoArgsConstructor
331 | @EqualsAndHashCode(callSuper = true)
332 | @RegisterForReflection
333 | public static class MultiFilterMeta extends FilterConstraint {
334 |
335 | @Schema(description = "Filter operator either 'and' or 'or'")
336 | private String operator;
337 |
338 | @Schema(description = "List of filter constraints for this filter")
339 | private List constraints = new ArrayList<>();
340 |
341 | }
342 |
343 | /**
344 | * Class representing the complete filter criteria including query and parameters
345 | */
346 | @Data
347 | @NoArgsConstructor
348 | @AllArgsConstructor
349 | @RegisterForReflection
350 | public static class FilterCriteria {
351 | private String query;
352 | private Map parameters;
353 | }
354 | }
--------------------------------------------------------------------------------
/src/main/java/com/melloware/quarkus/support/QueryResponse.java:
--------------------------------------------------------------------------------
1 | package com.melloware.quarkus.support;
2 |
3 | import java.util.List;
4 |
5 | import org.eclipse.microprofile.openapi.annotations.media.Schema;
6 |
7 | import io.quarkus.runtime.annotations.RegisterForReflection;
8 | import lombok.Data;
9 | import lombok.NoArgsConstructor;
10 |
11 | /**
12 | * Represents a PrimeReact query response to the UI for a complex datatable with multiple sorts, multiple filters, and
13 | * pagination.
14 | *
15 | * This class handles the response format expected by PrimeReact datatables including:
16 | *