├── .github ├── dependabot.yml └── workflows │ ├── manual-build.yml │ └── maven.yml ├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── CODEOWNERS ├── CONTRIBUTING.adoc ├── LICENSE ├── README.adoc ├── async-job-service ├── README.adoc ├── pom.xml └── src │ ├── main │ ├── java │ │ └── dev │ │ │ └── resteasy │ │ │ └── examples │ │ │ └── asyncjob │ │ │ ├── AsyncResource.java │ │ │ └── RestApplication.java │ └── webapp │ │ └── WEB-INF │ │ └── beans.xml │ └── test │ ├── java │ └── dev │ │ └── resteasy │ │ └── examples │ │ └── asyncjob │ │ └── AsyncJobTest.java │ └── resources │ └── arquillian.xml ├── bootable-jar ├── README.adoc ├── pom.xml └── src │ ├── main │ └── java │ │ └── dev │ │ └── resteasy │ │ └── examples │ │ └── bootablejar │ │ ├── Product.java │ │ ├── ProductApplication.java │ │ └── ProductResource.java │ └── test │ ├── java │ └── dev │ │ └── resteasy │ │ └── examples │ │ └── bootablejar │ │ └── ProductTestIT.java │ └── resources │ └── arquillian.xml ├── bootstrap-cdi ├── README.adoc ├── pom.xml └── src │ ├── main │ ├── java │ │ └── dev │ │ │ └── resteasy │ │ │ └── quickstart │ │ │ └── bootstrap │ │ │ ├── Greeter.java │ │ │ ├── GreetingResource.java │ │ │ ├── Main.java │ │ │ └── RestActivator.java │ └── resources │ │ ├── META-INF │ │ └── beans.xml │ │ └── logging.properties │ └── test │ └── java │ └── dev │ └── resteasy │ └── quickstart │ └── bootstrap │ └── GreeterTestCase.java ├── contacts ├── README.adoc ├── pom.xml └── src │ ├── main │ ├── java │ │ └── dev │ │ │ └── resteasy │ │ │ └── examples │ │ │ ├── data │ │ │ └── ContactRegistry.java │ │ │ ├── model │ │ │ └── Contact.java │ │ │ └── resources │ │ │ ├── ContactListener.java │ │ │ ├── ContactResource.java │ │ │ ├── Producers.java │ │ │ └── RestActivator.java │ ├── resources │ │ └── META-INF │ │ │ └── persistence.xml │ └── webapp │ │ ├── WEB-INF │ │ └── beans.xml │ │ ├── events.html │ │ ├── index.html │ │ └── resources │ │ ├── components.js │ │ └── main.js │ └── test │ ├── java │ └── dev │ │ └── resteasy │ │ └── examples │ │ └── resources │ │ └── ContactResourceTest.java │ └── resources │ └── arquillian.xml ├── dco.txt ├── examples-jsapi ├── .externalToolBuilders │ └── org.maven.ide.eclipse.maven2Builder.launch ├── README.adoc ├── pom.xml └── src │ ├── main │ ├── java │ │ └── dev │ │ │ └── resteasy │ │ │ └── examples │ │ │ ├── Order.java │ │ │ ├── Orders.java │ │ │ └── OrdersApplication.java │ └── webapp │ │ ├── WEB-INF │ │ └── web.xml │ │ └── index.html │ └── test │ ├── java │ └── dev │ │ └── resteasy │ │ └── examples │ │ └── OrdersTest.java │ └── resources │ └── arquillian.xml ├── file-upload ├── README.adoc ├── pom.xml └── src │ ├── main │ ├── java │ │ └── dev │ │ │ └── resteasy │ │ │ └── examples │ │ │ ├── Environment.java │ │ │ ├── SizeUnit.java │ │ │ └── resources │ │ │ ├── FileResource.java │ │ │ └── RestActivator.java │ └── webapp │ │ ├── WEB-INF │ │ └── beans.xml │ │ ├── index.html │ │ └── resources │ │ └── main.js │ └── test │ ├── java │ └── dev │ │ └── resteasy │ │ └── examples │ │ └── resources │ │ └── FileResourceTest.java │ └── resources │ └── arquillian.xml ├── grpc-bridge-example ├── README.md ├── pom.xml └── src │ └── main │ ├── java │ └── dev │ │ └── resteasy │ │ └── example │ │ └── grpc │ │ └── greet │ │ ├── GeneralGreeting.java │ │ ├── Greeter.java │ │ └── Greeting.java │ └── resources │ └── GreetingTest ├── json-binding ├── README.adoc ├── pom.xml └── src │ ├── main │ └── java │ │ └── dev │ │ └── resteasy │ │ └── examples │ │ ├── data │ │ ├── Book.java │ │ ├── BookListing.java │ │ └── BookListingDeserializer.java │ │ └── service │ │ ├── Library.java │ │ └── LibraryApplication.java │ └── test │ ├── java │ └── dev │ │ └── resteasy │ │ └── examples │ │ └── JsonBindingTest.java │ └── resources │ └── arquillian.xml ├── microprofile-openapi ├── README.adoc ├── pom.xml └── src │ ├── main │ └── java │ │ └── dev │ │ └── resteasy │ │ └── examples │ │ └── openapi │ │ ├── Product.java │ │ ├── ProductApplication.java │ │ └── ProductResource.java │ └── test │ └── java │ └── dev │ └── resteasy │ └── examples │ └── openapi │ └── OpenApiTestIT.java ├── mvnw ├── mvnw.cmd ├── pom.xml ├── security.txt ├── servlet-example ├── README.md ├── pom.xml └── src │ └── main │ └── java │ └── dev │ └── resteasy │ └── examples │ └── servlet │ ├── GreetingResource.java │ └── MyApplication.java ├── smime ├── Pipfile ├── README.md ├── getEncrypted.py ├── getEncryptedSigned.py ├── getSigned.py ├── pom.xml ├── postEncrypted.py ├── postSigned.py └── src │ ├── main │ ├── java │ │ └── dev │ │ │ └── resteasy │ │ │ └── example │ │ │ └── smime │ │ │ ├── Customer.java │ │ │ ├── SMIMEApplication.java │ │ │ └── SMIMEResource.java │ └── resources │ │ ├── cert.pem │ │ └── private.pem │ └── test │ ├── java │ └── dev │ │ └── resteasy │ │ └── example │ │ └── smime │ │ └── SMIMETest.java │ └── resources │ └── arquillian.xml ├── standalone-multipart ├── README.adoc ├── pom.xml └── src │ ├── main │ ├── java │ │ └── dev │ │ │ └── resteasy │ │ │ └── examples │ │ │ └── multipart │ │ │ ├── Main.java │ │ │ ├── RestActivator.java │ │ │ └── UploadResource.java │ └── resources │ │ ├── META-INF │ │ └── beans.xml │ │ └── logging.properties │ └── test │ └── java │ └── dev │ └── resteasy │ └── examples │ └── multipart │ └── UploadTestCase.java └── tracing-example ├── README.adoc ├── pom.xml └── src ├── main ├── java │ └── dev │ │ └── resteasy │ │ └── examples │ │ └── tracing │ │ ├── TraceMethodResource.java │ │ ├── TracingApp.java │ │ └── TracingConfigResource.java └── webapp │ ├── WEB-INF │ ├── beans.xml │ └── web.xml │ ├── index.html │ └── resources │ └── main.js └── test └── java └── dev └── resteasy └── examples └── tracing └── TracingTest.java /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "maven" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | open-pull-requests-limit: 10 13 | - package-ecosystem: "github-actions" 14 | # Workflow files stored in the 15 | # default location of `.github/workflows` 16 | directory: "/" 17 | schedule: 18 | interval: "daily" 19 | -------------------------------------------------------------------------------- /.github/workflows/manual-build.yml: -------------------------------------------------------------------------------- 1 | # This workflow is for a nightly run against WildFly upstream 2 | 3 | name: Manual Build 4 | 5 | on: 6 | workflow_dispatch: 7 | inputs: 8 | os: 9 | description: "Runner OS" 10 | required: true 11 | default: "ubuntu-latest" 12 | type: string 13 | javaVersion: 14 | description: "Java Version" 15 | required: true 16 | default: "11" 17 | type: choice 18 | options: 19 | - "11" 20 | - "17" 21 | 22 | jobs: 23 | build: 24 | runs-on: ${{ inputs.os }} 25 | 26 | steps: 27 | - uses: actions/checkout@v4 28 | - name: Set up JDK ${{ inputs.javaVersion }} 29 | uses: actions/setup-java@v4 30 | with: 31 | java-version: ${{ inputs.javaVersion }} 32 | distribution: 'temurin' 33 | cache: 'maven' 34 | - name: Build on ${{ inputs.os }} with Java ${{ inputs.javaVersion }} 35 | run: mvn -B clean install 36 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Java CI with Maven 5 | 6 | on: 7 | push: 8 | branches-ignore: 9 | - 'dependabot/**' 10 | paths-ignore: 11 | - ".github/workflows/manual-*.yml" 12 | pull_request: 13 | branches: 14 | - '**' 15 | 16 | jobs: 17 | format-check: 18 | runs-on: ubuntu-latest 19 | timeout-minutes: 30 20 | 21 | steps: 22 | - uses: actions/checkout@v4 23 | - name: Set up JDK 11 24 | uses: actions/setup-java@v4 25 | with: 26 | java-version: 11 27 | distribution: 'temurin' 28 | cache: 'maven' 29 | - name: Validate Formatting 30 | run: | 31 | mvn -B validate formatter:validate -Denforcer.skip=true 32 | mvn -B validate impsort:check -Denforcer.skip=true 33 | build: 34 | runs-on: ${{ matrix.os }} 35 | needs: format-check 36 | timeout-minutes: 90 37 | strategy: 38 | fail-fast: false 39 | matrix: 40 | os: [ubuntu-latest, windows-latest ] 41 | java: ['11', '17', '21', '24-ea'] 42 | 43 | steps: 44 | - uses: actions/checkout@v4 45 | - name: Set up JDK ${{ matrix.java }} 46 | uses: actions/setup-java@v4 47 | with: 48 | java-version: ${{ matrix.java }} 49 | distribution: 'temurin' 50 | cache: 'maven' 51 | - name: Build with Maven - ${{ matrix.os }} - JDK ${{ matrix.java }} 52 | run: mvn -B -ntp -fae clean install 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore Maven target folder 2 | target/ 3 | dependency-reduced-pom.xml 4 | 5 | # Formatting cache 6 | .cache/ 7 | 8 | # Intellij 9 | *.iml 10 | *.ipr 11 | *.iws 12 | .idea 13 | 14 | # Eclipse 15 | .settings/ 16 | .classpath 17 | .project 18 | .factorypath 19 | 20 | # ignore NetBeans files 21 | nbactions.xml 22 | nb-configuration.xml 23 | catalog.xml 24 | nbproject 25 | 26 | # VS Code 27 | **/.vscode/ 28 | 29 | # macOS Files 30 | .DS_Store 31 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resteasy/resteasy-examples/272b3987c906439d8c7e5254f1649c4da9ab5b25/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip 18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar 19 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @committers -------------------------------------------------------------------------------- /CONTRIBUTING.adoc: -------------------------------------------------------------------------------- 1 | :toc: 2 | 3 | = Contributing Guide 4 | 5 | Want to contribute to the RESTEasy Examples? We try to make it easy, and all contributions, even the smaller ones, 6 | are more than welcome. This includes bug reports, fixes, documentation, etc. First though, please read this page 7 | (including the small print at the end). 8 | 9 | == Legal 10 | 11 | All original contributions to RESTEasy Examples are licensed under the 12 | https://www.apache.org/licenses/LICENSE-2.0[ASL - Apache License], 13 | version 2.0 or later, or, if another license is specified as governing the file or directory being 14 | modified, such other license. 15 | 16 | All contributions are subject to the https://developercertificate.org/[Developer Certificate of Origin (DCO)]. 17 | The DCO text is also included verbatim in the [dco.txt](dco.txt) file in the root directory of the repository. 18 | 19 | == Reporting an issue 20 | 21 | This project uses GitHub issues to manage the issues. Open an issue directly in GitHub. 22 | 23 | If you believe you found a bug, and it's likely possible, please indicate a way to reproduce it, what you are seeing and 24 | what you would expect to see. 25 | 26 | == Before you contribute 27 | 28 | To contribute, use GitHub Pull Requests, from your **own** fork. 29 | 30 | Also, make sure you have set up your Git authorship correctly: 31 | 32 | ---- 33 | git config --global user.name "Your Full Name" 34 | git config --global user.email your.email@example.com 35 | ---- 36 | 37 | If you use different computers to contribute, please make sure the name is the same on all your computers. 38 | 39 | We use this information to acknowledge your contributions in release announcements. 40 | 41 | == Setup 42 | 43 | If you have not done so on this machine, you need to: 44 | 45 | * Install Git and configure your GitHub access 46 | * Install Java SDK 11+ (OpenJDK recommended) 47 | 48 | === IDE Config and Code Style 49 | 50 | RESTEasy has a strictly enforced code style. Code formatting is done by the Eclipse code formatter, using the config files 51 | found in the https://github.com/resteasy/resteasy-dev-tools/tree/main/ide-config/src/main/resources[eclipse-code-formatter.xml] 52 | file. By default, when you run `./mvnw install`, the code will be formatted automatically. 53 | When submitting a pull request the CI build will fail if running the formatter results in any code changes, so it is 54 | recommended that you always run a full Maven build before submitting a pull request. 55 | 56 | If you want to run the formatting without doing a full build, you can run `./mvnw process-sources`. 57 | 58 | ==== Eclipse Setup 59 | 60 | Open the *Preferences* window, and then navigate to _Java_ -> _Code Style_ -> _Formatter_. Click _Import_ and then 61 | select the `eclipse-code-formatter.xml` downloaded from the above link or clone the repository and navigate to the file. 62 | 63 | Next navigate to _Java_ -> _Code Style_ -> _Organize Imports_. Click _Import_ and select the `eclipse.importorder` file. 64 | 65 | ==== IDEA Setup 66 | 67 | Install the https://plugins.jetbrains.com/plugin/6546-adapter-for-eclipse-code-formatter/[Adapter for Eclipse Code Formatter]. 68 | See the https://github.com/krasa/EclipseCodeFormatter#instructions[documentation] on how to configure the plugin. -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = RESTEasy Examples 2 | 3 | IMPORTANT: Note that if you've come here for the O`Reilly RESTful Web Services with JAX-RS examples they are at the 4 | https://github.com/resteasy/resteasy-examples/tree/oreilly-jaxrs-2.0-workbook branch. 5 | 6 | The RESTEasy Examples are examples of using the 7 | https://jakarta.ee/specifications/restful-ws/[Jakarta RESTful Web Services specification] in general, as well as using 8 | RESTEasy specific API's. Each example should have a README which explains what it does and how it works. They should 9 | also be independent and can be treated as a singular project unless noted in the project. 10 | 11 | For full RESTEasy documentation see https://resteasy.dev/docs/. 12 | 13 | 14 | == Getting Started 15 | 16 | You need a minimum of Java 11 installed to get started. You can then execute the following in the root directory to 17 | build and test all projects: 18 | 19 | [source,bash] 20 | ---- 21 | ./mvnw clean install 22 | ---- 23 | 24 | This will execute Maven, build and test each independent project. 25 | 26 | == Issue Tracking 27 | 28 | Issues can be tracked on GitHub at https://github.com/resteasy/resteasy-examples/issues. If you've found a bug or would 29 | like to see some more examples feel free to file an issue or ask any in the 30 | https://github.com/resteasy/resteasy-examples/discussions[discussions]. 31 | 32 | -------------------------------------------------------------------------------- /async-job-service/README.adoc: -------------------------------------------------------------------------------- 1 | = Async Job Service 2 | 3 | This project is a simple example showing how to use the Asynchronous Job Service. 4 | 5 | == Building the project: 6 | 7 | === Run the following command to run the test for demo: 8 | [source,bash] 9 | ---- 10 | $ mvn clean test 11 | ---- 12 | 13 | === To start the demo service, run the following command: 14 | [source,bash] 15 | ---- 16 | $ mvn wildfly:dev 17 | ---- 18 | 19 | This will build a WAR and run it with WildFly -------------------------------------------------------------------------------- /async-job-service/src/main/java/dev/resteasy/examples/asyncjob/AsyncResource.java: -------------------------------------------------------------------------------- 1 | package dev.resteasy.examples.asyncjob; 2 | 3 | import java.util.Map; 4 | import java.util.concurrent.ConcurrentHashMap; 5 | import java.util.concurrent.TimeUnit; 6 | import java.util.concurrent.atomic.AtomicInteger; 7 | import java.util.concurrent.atomic.AtomicReference; 8 | import java.util.function.Supplier; 9 | 10 | import jakarta.annotation.Resource; 11 | import jakarta.enterprise.concurrent.ManagedExecutorService; 12 | import jakarta.enterprise.context.ApplicationScoped; 13 | import jakarta.json.Json; 14 | import jakarta.json.JsonArrayBuilder; 15 | import jakarta.json.JsonObjectBuilder; 16 | import jakarta.ws.rs.Consumes; 17 | import jakarta.ws.rs.GET; 18 | import jakarta.ws.rs.POST; 19 | import jakarta.ws.rs.PUT; 20 | import jakarta.ws.rs.Path; 21 | import jakarta.ws.rs.PathParam; 22 | import jakarta.ws.rs.Produces; 23 | import jakarta.ws.rs.container.AsyncResponse; 24 | import jakarta.ws.rs.container.Suspended; 25 | import jakarta.ws.rs.core.MediaType; 26 | 27 | /** 28 | * @author Bill Burke 29 | * @version $Revision: 1 $ 30 | */ 31 | @Path("/resource") 32 | @ApplicationScoped 33 | public class AsyncResource { 34 | private final Map postMessages = new ConcurrentHashMap<>(); 35 | private final AtomicReference putMessage = new AtomicReference<>(); 36 | private final AtomicInteger post = new AtomicInteger(0); 37 | 38 | @Resource 39 | private ManagedExecutorService executor; 40 | 41 | @POST 42 | @Produces(MediaType.TEXT_PLAIN) 43 | @Consumes(MediaType.TEXT_PLAIN) 44 | public void post(final String msg, @Suspended final AsyncResponse response) { 45 | submit(response, () -> { 46 | final int id = post.incrementAndGet(); 47 | postMessages.put(id, msg); 48 | return Integer.toString(id); 49 | }); 50 | } 51 | 52 | @GET 53 | @Produces(MediaType.APPLICATION_JSON) 54 | public void get(@Suspended final AsyncResponse response) { 55 | submit(response, () -> { 56 | try { 57 | TimeUnit.SECONDS.sleep(1); 58 | } catch (InterruptedException e) { 59 | response.resume(e); 60 | } 61 | final JsonArrayBuilder arrayBuilder = Json.createArrayBuilder(); 62 | postMessages.values().forEach(arrayBuilder::add); 63 | final JsonObjectBuilder jsonBuilder = Json.createObjectBuilder(); 64 | jsonBuilder.add("post", arrayBuilder); 65 | final String current = putMessage.get(); 66 | if (current != null) { 67 | jsonBuilder.add("put", current); 68 | } 69 | return jsonBuilder.build(); 70 | }); 71 | } 72 | 73 | @GET 74 | @Path("{id}") 75 | @Produces(MediaType.TEXT_PLAIN) 76 | public void getPostMessage(@PathParam("id") final int id, @Suspended final AsyncResponse response) { 77 | submit(response, () -> postMessages.get(id)); 78 | } 79 | 80 | @GET 81 | @Path("current") 82 | @Produces(MediaType.TEXT_PLAIN) 83 | public void getPutMessage(@Suspended final AsyncResponse response) { 84 | submit(response, putMessage::get); 85 | } 86 | 87 | @PUT 88 | @Produces(MediaType.TEXT_PLAIN) 89 | @Consumes(MediaType.TEXT_PLAIN) 90 | public void put(final String msg, @Suspended final AsyncResponse response) { 91 | submit(response, () -> putMessage.getAndSet(msg)); 92 | } 93 | 94 | private void submit(final AsyncResponse response, final Supplier resume) { 95 | response.setTimeout(10L, TimeUnit.SECONDS); 96 | executor.submit(() -> { 97 | try { 98 | TimeUnit.SECONDS.sleep(1); 99 | } catch (InterruptedException e) { 100 | response.resume(e); 101 | } 102 | response.resume(resume.get()); 103 | }); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /async-job-service/src/main/java/dev/resteasy/examples/asyncjob/RestApplication.java: -------------------------------------------------------------------------------- 1 | package dev.resteasy.examples.asyncjob; 2 | 3 | import jakarta.ws.rs.ApplicationPath; 4 | import jakarta.ws.rs.core.Application; 5 | 6 | /** 7 | * @author James R. Perkins 8 | */ 9 | @ApplicationPath("/") 10 | public class RestApplication extends Application { 11 | } 12 | -------------------------------------------------------------------------------- /async-job-service/src/main/webapp/WEB-INF/beans.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resteasy/resteasy-examples/272b3987c906439d8c7e5254f1649c4da9ab5b25/async-job-service/src/main/webapp/WEB-INF/beans.xml -------------------------------------------------------------------------------- /async-job-service/src/test/resources/arquillian.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 23 | 24 | 25 | 26 | ${jboss.home} 27 | ${jboss.server.config.file.name:standalone.xml} 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /bootable-jar/README.adoc: -------------------------------------------------------------------------------- 1 | = RESTEasy Bootable JAR Example 2 | 3 | Demonstrates creating a WildFly bootable JAR. 4 | 5 | == Build And Run 6 | 7 | * To build: `mvn clean install` 8 | * To run: `mvn wildfly-jar:run` or `java -jar target/products-runner-bootable.jar` 9 | * Access the JSON product result with: `http://127.0.0.1:8080/products` -------------------------------------------------------------------------------- /bootable-jar/src/main/java/dev/resteasy/examples/bootablejar/Product.java: -------------------------------------------------------------------------------- 1 | package dev.resteasy.examples.bootablejar; 2 | 3 | import java.util.Objects; 4 | 5 | public class Product implements Comparable { 6 | private String name; 7 | private int id; 8 | 9 | public Product() { 10 | } 11 | 12 | public Product(final int id, final String name) { 13 | this.id = id; 14 | this.name = name; 15 | } 16 | 17 | public String getName() { 18 | return name; 19 | } 20 | 21 | public void setName(String name) { 22 | this.name = name; 23 | } 24 | 25 | public int getId() { 26 | return id; 27 | } 28 | 29 | public void setId(int id) { 30 | this.id = id; 31 | } 32 | 33 | @Override 34 | public int hashCode() { 35 | return Objects.hash(id); 36 | } 37 | 38 | @Override 39 | public boolean equals(final Object obj) { 40 | if (this == obj) { 41 | return true; 42 | } 43 | if (!(obj instanceof Product)) { 44 | return false; 45 | } 46 | final Product other = (Product) obj; 47 | return id == other.id; 48 | } 49 | 50 | @Override 51 | public String toString() { 52 | return "Product[id=" + id + ", name=" + name + "]"; 53 | } 54 | 55 | @Override 56 | public int compareTo(final Product o) { 57 | return Integer.compare(id, o.id); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /bootable-jar/src/main/java/dev/resteasy/examples/bootablejar/ProductApplication.java: -------------------------------------------------------------------------------- 1 | package dev.resteasy.examples.bootablejar; 2 | 3 | import jakarta.ws.rs.ApplicationPath; 4 | import jakarta.ws.rs.core.Application; 5 | 6 | @ApplicationPath("/") 7 | public class ProductApplication extends Application { 8 | } 9 | -------------------------------------------------------------------------------- /bootable-jar/src/main/java/dev/resteasy/examples/bootablejar/ProductResource.java: -------------------------------------------------------------------------------- 1 | package dev.resteasy.examples.bootablejar; 2 | 3 | import jakarta.ws.rs.GET; 4 | import jakarta.ws.rs.Path; 5 | import jakarta.ws.rs.Produces; 6 | import jakarta.ws.rs.core.MediaType; 7 | 8 | @Path("/products") 9 | public class ProductResource { 10 | @GET 11 | @Produces(MediaType.APPLICATION_JSON) 12 | public Product[] getProducts() { 13 | return new Product[] { new Product(111, "JBoss EAP"), new Product(222, "RHEL"), 14 | new Product(333, "CentOS") }; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /bootable-jar/src/test/java/dev/resteasy/examples/bootablejar/ProductTestIT.java: -------------------------------------------------------------------------------- 1 | package dev.resteasy.examples.bootablejar; 2 | 3 | import jakarta.ws.rs.client.Client; 4 | import jakarta.ws.rs.client.ClientBuilder; 5 | import jakarta.ws.rs.client.WebTarget; 6 | import jakarta.ws.rs.core.Response; 7 | 8 | import org.jboss.arquillian.container.test.api.Deployment; 9 | import org.jboss.arquillian.container.test.api.RunAsClient; 10 | import org.jboss.arquillian.junit5.ArquillianExtension; 11 | import org.jboss.shrinkwrap.api.ShrinkWrap; 12 | import org.jboss.shrinkwrap.api.asset.EmptyAsset; 13 | import org.jboss.shrinkwrap.api.spec.WebArchive; 14 | import org.junit.jupiter.api.Assertions; 15 | import org.junit.jupiter.api.Test; 16 | import org.junit.jupiter.api.extension.ExtendWith; 17 | 18 | @ExtendWith(ArquillianExtension.class) 19 | @RunAsClient 20 | public class ProductTestIT { 21 | 22 | @Deployment(testable = false) 23 | public static WebArchive createDeployment() { 24 | // Dummy deployment 25 | return ShrinkWrap.create(WebArchive.class) 26 | .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml"); 27 | } 28 | 29 | @Test 30 | public void testResponse() { 31 | try (Client client = ClientBuilder.newClient()) { 32 | final WebTarget target = client.target("http://localhost:8080/products"); 33 | try (Response response = target.request().get()) { 34 | Assertions.assertEquals(200, response.getStatus()); 35 | final Product[] products = response.readEntity(Product[].class); 36 | Assertions.assertEquals(3, products.length); 37 | checkProduct(products[0], 111); 38 | checkProduct(products[1], 222); 39 | checkProduct(products[2], 333); 40 | } 41 | } 42 | } 43 | 44 | private void checkProduct(final Product product, final int expectedId) { 45 | Assertions.assertEquals(expectedId, product.getId(), () -> String.format("Expected %d got %s", expectedId, product)); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /bootable-jar/src/test/resources/arquillian.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 23 | 24 | 25 | 26 | ${bootable.jar} 27 | false 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /bootstrap-cdi/README.adoc: -------------------------------------------------------------------------------- 1 | = Bootstrap with CDI 2 | 3 | In https://jakarta.ee/specifications/restful-ws/3.1/[Jakarta RESTful Web Services 3.1] the `SeBootstrap` API was 4 | introduced. This quickstart shows how to use the API with RESTEasy. 5 | 6 | == Building 7 | 8 | To build the `bootstrap-cdi` quickstart you must have https://maven.apache.org/[Maven] installed and at least Java 11. 9 | Then you simply need to run the following: 10 | 11 | ---- 12 | mvn clean verify 13 | ---- 14 | 15 | This will create a `bootstrap-cdi.jar` which can be executed from the command line. A test is also executed as part of 16 | the build. 17 | 18 | == Running the Quickstart 19 | 20 | The `bootstrap-cdi.jar` created can be executed from the command. 21 | 22 | ---- 23 | java -jar target/bootstrap-cdi.jar 24 | ---- 25 | 26 | This will start an Undertow container with RESTEasy and CDI support. To exit the application you need to send a `SIGKILL` 27 | , for example `CTRL+C`. -------------------------------------------------------------------------------- /bootstrap-cdi/src/main/java/dev/resteasy/quickstart/bootstrap/Greeter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JBoss, Home of Professional Open Source. 3 | * 4 | * Copyright 2022 Red Hat, Inc., and individual contributors 5 | * as indicated by the @author tags. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * 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, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package dev.resteasy.quickstart.bootstrap; 21 | 22 | import jakarta.enterprise.context.ApplicationScoped; 23 | import jakarta.validation.constraints.NotNull; 24 | 25 | /** 26 | * A simple greeter CDI bean. 27 | * 28 | * @author James R. Perkins 29 | */ 30 | @ApplicationScoped 31 | public class Greeter { 32 | 33 | /** 34 | * Returns a greeting for the name provided. 35 | * 36 | * @param name the name to add to the greeting 37 | * @return a greeting 38 | */ 39 | public String greet(@NotNull final String name) { 40 | return "Hello " + name + "!"; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /bootstrap-cdi/src/main/java/dev/resteasy/quickstart/bootstrap/GreetingResource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JBoss, Home of Professional Open Source. 3 | * 4 | * Copyright 2022 Red Hat, Inc., and individual contributors 5 | * as indicated by the @author tags. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * 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, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package dev.resteasy.quickstart.bootstrap; 21 | 22 | import jakarta.inject.Inject; 23 | import jakarta.ws.rs.GET; 24 | import jakarta.ws.rs.Path; 25 | import jakarta.ws.rs.PathParam; 26 | import jakarta.ws.rs.Produces; 27 | import jakarta.ws.rs.core.MediaType; 28 | import jakarta.ws.rs.core.Response; 29 | 30 | /** 31 | * A simple resource for creating a greeting. 32 | * 33 | * @author James R. Perkins 34 | */ 35 | @Path("/") 36 | public class GreetingResource { 37 | 38 | @Inject 39 | private Greeter greeter; 40 | 41 | /** 42 | * A {@link GET} method which returns a greeting for the name passed in, in plain text. 43 | * 44 | * @param name the name for the greeting 45 | * @return a response with a greeting in plain text 46 | */ 47 | @GET 48 | @Path("/{name}") 49 | @Produces(MediaType.TEXT_PLAIN) 50 | public Response greet(@PathParam("name") final String name) { 51 | return Response.ok(greeter.greet(name)).build(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /bootstrap-cdi/src/main/java/dev/resteasy/quickstart/bootstrap/Main.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JBoss, Home of Professional Open Source. 3 | * 4 | * Copyright 2022 Red Hat, Inc., and individual contributors 5 | * as indicated by the @author tags. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * 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, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package dev.resteasy.quickstart.bootstrap; 21 | 22 | import jakarta.ws.rs.SeBootstrap; 23 | 24 | /** 25 | * An entry point for starting a REST container 26 | * 27 | * @author James R. Perkins 28 | */ 29 | public class Main { 30 | private static final boolean USE_CONSOLE = System.console() != null; 31 | 32 | public static void main(final String[] args) throws Exception { 33 | System.setProperty("java.util.logging.manager", "org.jboss.logmanager.LogManager"); 34 | SeBootstrap.start(RestActivator.class) 35 | .thenAccept(instance -> { 36 | instance.stopOnShutdown(stopResult -> print("Stopped container (%s)", stopResult.unwrap(Object.class))); 37 | print("Container running at %s", 38 | instance.configuration().baseUri()); 39 | print("Example: %s", 40 | instance.configuration().baseUriBuilder().path("rest/" + System.getProperty("user.name")).build()); 41 | print("Send SIGKILL to shutdown container"); 42 | }); 43 | Thread.currentThread().join(); 44 | } 45 | 46 | private static void print(final String fmt, final Object... args) { 47 | if (USE_CONSOLE) { 48 | System.console().format(fmt, args) 49 | .printf("%n"); 50 | } else { 51 | System.out.printf(fmt, args); 52 | System.out.println(); 53 | } 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /bootstrap-cdi/src/main/java/dev/resteasy/quickstart/bootstrap/RestActivator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JBoss, Home of Professional Open Source. 3 | * 4 | * Copyright 2022 Red Hat, Inc., and individual contributors 5 | * as indicated by the @author tags. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * 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, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package dev.resteasy.quickstart.bootstrap; 21 | 22 | import jakarta.ws.rs.ApplicationPath; 23 | import jakarta.ws.rs.core.Application; 24 | 25 | /** 26 | * Activates the REST application. 27 | * 28 | * @author James R. Perkins 29 | */ 30 | @ApplicationPath("/rest") 31 | public class RestActivator extends Application { 32 | } 33 | -------------------------------------------------------------------------------- /bootstrap-cdi/src/main/resources/META-INF/beans.xml: -------------------------------------------------------------------------------- 1 | 19 | 20 | 23 | -------------------------------------------------------------------------------- /bootstrap-cdi/src/main/resources/logging.properties: -------------------------------------------------------------------------------- 1 | # 2 | # JBoss, Home of Professional Open Source. 3 | # 4 | # Copyright 2022 Red Hat, Inc., and individual contributors 5 | # as indicated by the @author tags. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # 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, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | loggers=org.jboss.resteasy 21 | 22 | logger.level=INFO 23 | logger.handlers=CONSOLE 24 | 25 | logger.org.jboss.resteasy.level=${log.level:INFO} 26 | 27 | handler.CONSOLE=org.jboss.logmanager.handlers.ConsoleHandler 28 | handler.CONSOLE.formatter=COLOR-PATTERN 29 | handler.CONSOLE.properties=autoFlush,target 30 | handler.CONSOLE.autoFlush=true 31 | handler.CONSOLE.target=SYSTEM_OUT 32 | 33 | formatter.COLOR-PATTERN=org.jboss.logmanager.formatters.PatternFormatter 34 | formatter.COLOR-PATTERN.properties=pattern 35 | formatter.COLOR-PATTERN.pattern=%K{level}%d{HH\:mm\:ss,SSS} %-5p [%c] (%t) %s%e%n -------------------------------------------------------------------------------- /bootstrap-cdi/src/test/java/dev/resteasy/quickstart/bootstrap/GreeterTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JBoss, Home of Professional Open Source. 3 | * 4 | * Copyright 2022 Red Hat, Inc., and individual contributors 5 | * as indicated by the @author tags. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * 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, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package dev.resteasy.quickstart.bootstrap; 21 | 22 | import java.util.concurrent.TimeUnit; 23 | 24 | import jakarta.ws.rs.SeBootstrap; 25 | import jakarta.ws.rs.client.Client; 26 | import jakarta.ws.rs.client.ClientBuilder; 27 | import jakarta.ws.rs.core.Response; 28 | 29 | import org.junit.jupiter.api.AfterAll; 30 | import org.junit.jupiter.api.Assertions; 31 | import org.junit.jupiter.api.BeforeAll; 32 | import org.junit.jupiter.api.Test; 33 | 34 | /** 35 | * @author James R. Perkins 36 | */ 37 | public class GreeterTestCase { 38 | 39 | private static SeBootstrap.Instance INSTANCE; 40 | 41 | @BeforeAll 42 | public static void startInstance() throws Exception { 43 | INSTANCE = SeBootstrap.start(RestActivator.class) 44 | .toCompletableFuture().get(10, TimeUnit.SECONDS); 45 | Assertions.assertNotNull(INSTANCE, "Failed to start instance"); 46 | } 47 | 48 | @AfterAll 49 | public static void stopInstance() throws Exception { 50 | if (INSTANCE != null) { 51 | INSTANCE.stop() 52 | .toCompletableFuture() 53 | .get(10, TimeUnit.SECONDS); 54 | } 55 | } 56 | 57 | @Test 58 | public void greet() { 59 | try (Client client = ClientBuilder.newClient()) { 60 | final String name = System.getProperty("user.name", "RESTEasy"); 61 | final Response response = client.target(INSTANCE.configuration().baseUriBuilder().path("/rest/" + name)) 62 | .request() 63 | .get(); 64 | Assertions.assertEquals(Response.Status.OK, response.getStatusInfo(), 65 | () -> String.format("Expected 200 got %d: %s", response.getStatus(), response.readEntity(String.class))); 66 | Assertions.assertEquals(String.format("Hello %s!", name), response.readEntity(String.class)); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /contacts/README.adoc: -------------------------------------------------------------------------------- 1 | = Contacts 2 | 3 | This is a simple CRUD application to manage contacts. It uses the following Jakarta EE 10 API's: 4 | 5 | * https://jakarta.ee/specifications/restful-ws/[Jakarta RESTful Web Services] 6 | * https://jakarta.ee/specifications/cdi/[Jakarta Contexts Dependency Injection] 7 | * https://jakarta.ee/specifications/persistence/[Jakarta Persistence] 8 | * https://jakarta.ee/specifications/bean-validation/[Jakarta Bean Validation] 9 | * https://jakarta.ee/specifications/transactions/[Jakarta Transactions] 10 | 11 | The client side is composed of HTML and JavaScript. It receives events from the server (server-sent events) to indicate 12 | when a contact has been added, deleted or modified. The page should then be updated to reflect the changes. 13 | 14 | You can also watch events from the http://127.0.0.1:8080/contacts/events.html page when the application is running. 15 | 16 | == Building the application: 17 | 18 | [source,bash] 19 | ---- 20 | mvn clean verify 21 | ---- 22 | 23 | == Running the application in WildFly: 24 | 25 | [source,bash] 26 | ---- 27 | mvn clean wildfly:dev 28 | ---- 29 | 30 | Open a browser at the following URL: http://127.0.0.1:8080/contacts -------------------------------------------------------------------------------- /contacts/src/main/java/dev/resteasy/examples/data/ContactRegistry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JBoss, Home of Professional Open Source. 3 | * 4 | * Copyright 2023 Red Hat, Inc., and individual contributors 5 | * as indicated by the @author tags. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * 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, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package dev.resteasy.examples.data; 21 | 22 | import java.util.Collection; 23 | 24 | import jakarta.enterprise.context.RequestScoped; 25 | import jakarta.persistence.EntityManager; 26 | import jakarta.persistence.PersistenceContext; 27 | import jakarta.transaction.Transactional; 28 | import jakarta.validation.constraints.NotNull; 29 | 30 | import dev.resteasy.examples.model.Contact; 31 | 32 | /** 33 | * Manages the contact repository. 34 | * 35 | * @author James R. Perkins 36 | */ 37 | @RequestScoped 38 | @Transactional 39 | public class ContactRegistry { 40 | @PersistenceContext(unitName = "primary") 41 | private EntityManager em; 42 | 43 | /** 44 | * Returns all the current contacts. 45 | * 46 | * @return a collection of the current contacts 47 | */ 48 | public Collection getContacts() { 49 | return em.createNamedQuery("findAll", Contact.class) 50 | .getResultList(); 51 | } 52 | 53 | /** 54 | * Finds the contact given the id. 55 | * 56 | * @param id the contact id 57 | * @return the contact or {@code null} if not found 58 | */ 59 | public Contact getContactById(final long id) { 60 | return em.find(Contact.class, id); 61 | } 62 | 63 | /** 64 | * Updates, or adds if missing, the contact. 65 | * 66 | * @param contact the contact to update 67 | * @return the updated contact 68 | */ 69 | public Contact update(@NotNull final Contact contact) { 70 | return em.merge(contact); 71 | } 72 | 73 | /** 74 | * Adds a contact to the repository. 75 | * 76 | * @param contact the contact to add 77 | * @return the contact 78 | */ 79 | public Contact add(@NotNull final Contact contact) { 80 | em.persist(contact); 81 | return contact; 82 | } 83 | 84 | /** 85 | * Deletes the contact, if it exists, from the repository. 86 | * 87 | * @param id the contact id 88 | * @return the deleted contact 89 | */ 90 | public Contact delete(final long id) { 91 | final Contact contact = em.find(Contact.class, id); 92 | if (contact != null) { 93 | em.remove(contact); 94 | } 95 | return contact; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /contacts/src/main/java/dev/resteasy/examples/model/Contact.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JBoss, Home of Professional Open Source. 3 | * 4 | * Copyright 2023 Red Hat, Inc., and individual contributors 5 | * as indicated by the @author tags. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * 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, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package dev.resteasy.examples.model; 21 | 22 | import java.util.Objects; 23 | 24 | import jakarta.persistence.Column; 25 | import jakarta.persistence.Entity; 26 | import jakarta.persistence.EntityListeners; 27 | import jakarta.persistence.GeneratedValue; 28 | import jakarta.persistence.GenerationType; 29 | import jakarta.persistence.Id; 30 | import jakarta.persistence.NamedQuery; 31 | import jakarta.persistence.Table; 32 | import jakarta.persistence.UniqueConstraint; 33 | import jakarta.validation.constraints.Email; 34 | import jakarta.validation.constraints.Pattern; 35 | import jakarta.ws.rs.FormParam; 36 | 37 | import dev.resteasy.examples.resources.ContactListener; 38 | 39 | /** 40 | * An entity which represents a contact. 41 | * 42 | * @author James R. Perkins 43 | */ 44 | @Entity 45 | @Table(uniqueConstraints = @UniqueConstraint(columnNames = "email")) 46 | @EntityListeners(ContactListener.class) 47 | @NamedQuery(name = "findAll", query = "SELECT c FROM Contact c ORDER BY c.lastName ASC, c.firstName ASC") 48 | public class Contact { 49 | 50 | @Id 51 | @GeneratedValue(strategy = GenerationType.SEQUENCE) 52 | private Long id; 53 | 54 | @Column(name = "first_name") 55 | @Pattern(regexp = "[^0-9]*", message = "Must not contain numbers") 56 | @FormParam("firstName") 57 | private String firstName; 58 | @Column(name = "last_name") 59 | @Pattern(regexp = "[^0-9]*", message = "Must not contain numbers") 60 | @FormParam("lastName") 61 | private String lastName; 62 | 63 | @Column(name = "company_name") 64 | @FormParam("companyName") 65 | private String companyName; 66 | 67 | @Column(name = "phone_number") 68 | @FormParam("phoneNumber") 69 | private String phoneNumber; 70 | 71 | @Column 72 | @Email 73 | @FormParam("email") 74 | private String email; 75 | 76 | public Long getId() { 77 | return id; 78 | } 79 | 80 | public void setId(final Long id) { 81 | this.id = id; 82 | } 83 | 84 | public String getFirstName() { 85 | return firstName; 86 | } 87 | 88 | public void setFirstName(final String firstName) { 89 | this.firstName = firstName; 90 | } 91 | 92 | public String getLastName() { 93 | return lastName; 94 | } 95 | 96 | public void setLastName(final String lastName) { 97 | this.lastName = lastName; 98 | } 99 | 100 | public String getCompanyName() { 101 | return companyName; 102 | } 103 | 104 | public void setCompanyName(final String companyName) { 105 | this.companyName = companyName; 106 | } 107 | 108 | public String getPhoneNumber() { 109 | return phoneNumber; 110 | } 111 | 112 | public void setPhoneNumber(final String phoneNumber) { 113 | this.phoneNumber = phoneNumber; 114 | } 115 | 116 | public String getEmail() { 117 | return email; 118 | } 119 | 120 | public void setEmail(final String email) { 121 | this.email = email; 122 | } 123 | 124 | @Override 125 | public int hashCode() { 126 | return Objects.hash(getId()); 127 | } 128 | 129 | @Override 130 | public boolean equals(final Object obj) { 131 | if (this == obj) { 132 | return true; 133 | } 134 | if (!(obj instanceof Contact)) { 135 | return false; 136 | } 137 | final Contact other = (Contact) obj; 138 | return Objects.equals(getId(), other.getId()); 139 | } 140 | 141 | @Override 142 | public String toString() { 143 | return getClass().getSimpleName() + "[id=" + getId() 144 | + ", firstName=" + getFirstName() 145 | + ", lastName=" + getLastName() 146 | + ", companyName=" + getCompanyName() 147 | + ", phonNumner=" + getPhoneNumber() 148 | + ", email=" + getEmail() 149 | + "]"; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /contacts/src/main/java/dev/resteasy/examples/resources/ContactListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JBoss, Home of Professional Open Source. 3 | * 4 | * Copyright 2023 Red Hat, Inc., and individual contributors 5 | * as indicated by the @author tags. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * 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, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package dev.resteasy.examples.resources; 21 | 22 | import jakarta.annotation.PreDestroy; 23 | import jakarta.enterprise.context.ApplicationScoped; 24 | import jakarta.inject.Inject; 25 | import jakarta.persistence.PostPersist; 26 | import jakarta.persistence.PostRemove; 27 | import jakarta.persistence.PostUpdate; 28 | import jakarta.ws.rs.GET; 29 | import jakarta.ws.rs.Path; 30 | import jakarta.ws.rs.Produces; 31 | import jakarta.ws.rs.core.Context; 32 | import jakarta.ws.rs.core.MediaType; 33 | import jakarta.ws.rs.sse.Sse; 34 | import jakarta.ws.rs.sse.SseBroadcaster; 35 | import jakarta.ws.rs.sse.SseEventSink; 36 | 37 | import org.jboss.logging.Logger; 38 | 39 | import dev.resteasy.examples.model.Contact; 40 | 41 | /** 42 | * A listener for changes of contact entities. Once an add, update or delete has been made a notification is sent to 43 | * registered subscribers. 44 | * 45 | * @author James R. Perkins 46 | */ 47 | @ApplicationScoped 48 | @Path("/subscribe") 49 | public class ContactListener { 50 | private static final Logger LOGGER = Logger.getLogger(ContactListener.class); 51 | 52 | @Inject 53 | private Sse sse; 54 | 55 | @Inject 56 | private SseBroadcaster broadcaster; 57 | 58 | @PreDestroy 59 | public void close() { 60 | broadcaster.close(true); 61 | } 62 | 63 | /** 64 | * Subscribes a client to the events of an entity. 65 | * 66 | * @param sink the event sink to register 67 | */ 68 | @GET 69 | @Produces(MediaType.SERVER_SENT_EVENTS) 70 | public void subscribe(@Context final SseEventSink sink) { 71 | broadcaster.register(sink); 72 | } 73 | 74 | /** 75 | * Sends a notification to the subscribed clients that an entity as been added. 76 | * 77 | * @param contact the contact that has been added 78 | */ 79 | @PostPersist 80 | public void notifyAdded(final Contact contact) { 81 | notifyClient(contact, "contact.persist.added"); 82 | } 83 | 84 | /** 85 | * Sends a notification to the subscribed clients that an entity as been updated. 86 | * 87 | * @param contact the contact that has been updated 88 | */ 89 | @PostUpdate 90 | public void notifyUpdated(final Contact contact) { 91 | notifyClient(contact, "contact.persist.updated"); 92 | } 93 | 94 | /** 95 | * Sends a notification to the subscribed clients that an entity as been deleted. 96 | * 97 | * @param contact the contact that has been deleted 98 | */ 99 | @PostRemove 100 | public void notifyRemoved(final Contact contact) { 101 | notifyClient(contact, "contact.persist.removed"); 102 | } 103 | 104 | private void notifyClient(final Contact contact, final String name) { 105 | broadcaster.broadcast(sse.newEventBuilder() 106 | .name(name) 107 | .data(contact) 108 | .mediaType(MediaType.APPLICATION_JSON_TYPE).build()) 109 | .whenComplete((value, error) -> { 110 | if (error != null) { 111 | LOGGER.errorf(error, "Failed to notify clients of event %s: %s", name, value); 112 | } 113 | }); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /contacts/src/main/java/dev/resteasy/examples/resources/ContactResource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JBoss, Home of Professional Open Source. 3 | * 4 | * Copyright 2023 Red Hat, Inc., and individual contributors 5 | * as indicated by the @author tags. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * 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, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package dev.resteasy.examples.resources; 21 | 22 | import jakarta.enterprise.context.RequestScoped; 23 | import jakarta.inject.Inject; 24 | import jakarta.validation.Valid; 25 | import jakarta.ws.rs.Consumes; 26 | import jakarta.ws.rs.DELETE; 27 | import jakarta.ws.rs.GET; 28 | import jakarta.ws.rs.POST; 29 | import jakarta.ws.rs.PUT; 30 | import jakarta.ws.rs.Path; 31 | import jakarta.ws.rs.PathParam; 32 | import jakarta.ws.rs.Produces; 33 | import jakarta.ws.rs.core.MediaType; 34 | import jakarta.ws.rs.core.Response; 35 | import jakarta.ws.rs.core.UriInfo; 36 | 37 | import dev.resteasy.examples.data.ContactRegistry; 38 | import dev.resteasy.examples.model.Contact; 39 | 40 | /** 41 | * A REST endpoint to manage contacts. 42 | * 43 | * @author James R. Perkins 44 | */ 45 | @RequestScoped 46 | @Path("/contact") 47 | public class ContactResource { 48 | 49 | @Inject 50 | private ContactRegistry contactRegistry; 51 | 52 | @Inject 53 | private UriInfo uriInfo; 54 | 55 | /** 56 | * Returns all current contacts. 57 | * 58 | * @return all the current contacts 59 | */ 60 | @GET 61 | @Produces(MediaType.APPLICATION_JSON) 62 | public Response get() { 63 | return Response.ok(contactRegistry.getContacts()) 64 | .build(); 65 | } 66 | 67 | /** 68 | * Returns the contact, if it exists, with the given id. If the contact does not exist a 404 is returned. 69 | * 70 | * @param id the contact id 71 | * @return the contact or a 404 if not found 72 | */ 73 | @GET 74 | @Produces(MediaType.APPLICATION_JSON) 75 | @Path("{id}") 76 | public Response get(@PathParam("id") final long id) { 77 | final Contact contact = contactRegistry.getContactById(id); 78 | return contact == null ? Response.status(Response.Status.NOT_FOUND).build() : Response.ok(contact).build(); 79 | } 80 | 81 | /** 82 | * Updates the contact. 83 | * 84 | * @param contact the contact to update 85 | * @return the location to the updated contact 86 | */ 87 | @PUT 88 | @Consumes(MediaType.APPLICATION_JSON) 89 | @Path("/edit") 90 | public Response edit(@Valid final Contact contact) { 91 | return Response 92 | .created(uriInfo.getBaseUriBuilder().path("contact/" + contactRegistry.update(contact).getId()).build()) 93 | .build(); 94 | } 95 | 96 | /** 97 | * Adds the contact. 98 | * 99 | * @param contact the contact to add 100 | * @return the location to the added contact 101 | */ 102 | @POST 103 | @Consumes(MediaType.APPLICATION_JSON) 104 | @Path("/add") 105 | public Response add(@Valid final Contact contact) { 106 | return Response 107 | .created(uriInfo.getBaseUriBuilder().path("contact/" + contactRegistry.add(contact).getId()).build()) 108 | .build(); 109 | } 110 | 111 | /** 112 | * Deletes the contact with the given id. 113 | * 114 | * @param id the contact id 115 | * @return the deleted contact or a 404 if not found 116 | */ 117 | @DELETE 118 | @Path("/delete/{id}") 119 | @Produces(MediaType.APPLICATION_JSON) 120 | public Response delete(@PathParam("id") final long id) { 121 | final Contact contact = contactRegistry.delete(id); 122 | return contact == null ? Response.status(Response.Status.NOT_FOUND).build() : Response.ok(contact).build(); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /contacts/src/main/java/dev/resteasy/examples/resources/Producers.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JBoss, Home of Professional Open Source. 3 | * 4 | * Copyright 2023 Red Hat, Inc., and individual contributors 5 | * as indicated by the @author tags. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * 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, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package dev.resteasy.examples.resources; 21 | 22 | import java.util.concurrent.atomic.AtomicReference; 23 | 24 | import jakarta.enterprise.context.ApplicationScoped; 25 | import jakarta.enterprise.inject.Produces; 26 | import jakarta.inject.Inject; 27 | import jakarta.ws.rs.sse.Sse; 28 | import jakarta.ws.rs.sse.SseBroadcaster; 29 | 30 | /** 31 | * @author James R. Perkins 32 | */ 33 | @ApplicationScoped 34 | public class Producers { 35 | 36 | private final AtomicReference broadcaster = new AtomicReference<>(); 37 | 38 | @Inject 39 | private Sse sse; 40 | 41 | /** 42 | * Creates the broadcaster used for the application via injection. 43 | * 44 | * @return the broadcaster to use 45 | */ 46 | @Produces 47 | @ApplicationScoped 48 | public SseBroadcaster broadcaster() { 49 | return broadcaster.updateAndGet(sseBroadcaster -> { 50 | SseBroadcaster result = sseBroadcaster; 51 | if (result == null) { 52 | result = sse.newBroadcaster(); 53 | } 54 | return result; 55 | }); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /contacts/src/main/java/dev/resteasy/examples/resources/RestActivator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JBoss, Home of Professional Open Source. 3 | * 4 | * Copyright 2023 Red Hat, Inc., and individual contributors 5 | * as indicated by the @author tags. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * 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, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package dev.resteasy.examples.resources; 21 | 22 | import jakarta.ws.rs.ApplicationPath; 23 | import jakarta.ws.rs.core.Application; 24 | 25 | /** 26 | * Indicates that the deployment is a REST deployment. 27 | * 28 | * @author James R. Perkins 29 | */ 30 | @ApplicationPath("/api") 31 | public class RestActivator extends Application { 32 | } 33 | -------------------------------------------------------------------------------- /contacts/src/main/resources/META-INF/persistence.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 24 | 25 | java:comp/DefaultDataSource 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /contacts/src/main/webapp/WEB-INF/beans.xml: -------------------------------------------------------------------------------- 1 | 19 | 20 | -------------------------------------------------------------------------------- /contacts/src/main/webapp/events.html: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | 22 | 23 | 24 | Events 25 | 26 | 28 | 32 | 33 | 34 |
35 |
36 |
37 |
38 | 39 | 40 | 41 |
42 |
43 |
44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 |
EventData
55 |
56 | 57 | 60 | 110 | 111 | -------------------------------------------------------------------------------- /contacts/src/main/webapp/index.html: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | Contacts 26 | 27 | 29 | 30 | 31 | 32 | 33 | 38 |
39 |
40 |
41 | 42 |
43 | 44 | 45 | 46 | 47 | 48 | 49 | 54 | 55 | 56 | 57 | 58 | 59 |
NamePhone NumberEmail 50 | 53 |
60 |
61 | 62 | 75 | 76 | 90 | 106 | 107 | 115 | 124 | 125 | 126 | 127 | 130 | -------------------------------------------------------------------------------- /contacts/src/main/webapp/resources/components.js: -------------------------------------------------------------------------------- 1 | /* 2 | * JBoss, Home of Professional Open Source. 3 | * 4 | * Copyright 2023 Red Hat, Inc., and individual contributors 5 | * as indicated by the @author tags. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * 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, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | /** 21 | * A contact form. 22 | */ 23 | class ContactForm extends HTMLElement { 24 | constructor() { 25 | super(); 26 | } 27 | 28 | get name() { 29 | return this.getAttribute("name"); 30 | } 31 | 32 | get buttonId() { 33 | return this.getAttribute("data-button-id"); 34 | } 35 | 36 | get buttonText() { 37 | return this.getAttribute("data-button-text"); 38 | } 39 | 40 | get includeId() { 41 | return this.getAttribute("data-include-id"); 42 | } 43 | 44 | connectedCallback() { 45 | const name = this.name; 46 | const buttonId = this.buttonId; 47 | const buttonText = this.buttonText; 48 | const form = document.createElement("form"); 49 | form.setAttribute("name", name); 50 | // Use HTML for readability and ease of writing 51 | form.innerHTML = ` 52 |
53 | 54 |
55 | 57 | 58 |
59 |
60 | Names cannot have numbers. 61 |
62 |
63 |
64 | 65 |
66 | 67 | 68 |
69 |
70 | Names cannot have numbers. 71 |
72 |
73 |
74 | 75 |
76 | 77 | 78 |
79 |
80 |
81 | 82 |
83 | 84 | 85 |
86 |
87 |
88 | 89 |
90 | 91 | 92 |
93 |
94 | Invalid email address. 95 |
96 |
97 | 101 | `; 102 | // Do we need to add the input for the id? 103 | if (this.includeId) { 104 | const hiddenId = document.createElement("input"); 105 | hiddenId.setAttribute("type", "hidden"); 106 | hiddenId.setAttribute("name", "id"); 107 | form.appendChild(hiddenId); 108 | } 109 | // Add listeners to each input requiring validation 110 | const inputs = form.querySelectorAll(".needs-validation"); 111 | inputs.forEach((e) => { 112 | e.addEventListener("invalid", (event) => { 113 | const parent = event.target.parentElement; 114 | event.target.classList.add("is-invalid"); 115 | // If there is a parent, it should simply be a div and the class needs to be added there too 116 | if (parent) { 117 | parent.classList.add("is-invalid"); 118 | } 119 | }); 120 | }); 121 | // Add a click listener to clear the is-invalid class 122 | const submitButton = form.querySelector("button[type='submit']"); 123 | submitButton.addEventListener("click", () => { 124 | form.querySelectorAll(".is-invalid").forEach(e => e.classList.remove("is-invalid")); 125 | }); 126 | this.appendChild(form); 127 | } 128 | } 129 | 130 | // Define the custom components 131 | customElements.define("contact-form", ContactForm); -------------------------------------------------------------------------------- /contacts/src/test/resources/arquillian.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 23 | 24 | 25 | 26 | ${jboss.home} 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /dco.txt: -------------------------------------------------------------------------------- 1 | Developer Certificate of Origin 2 | Version 1.1 3 | 4 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 5 | 1 Letterman Drive 6 | Suite D4700 7 | San Francisco, CA, 94129 8 | 9 | Everyone is permitted to copy and distribute verbatim copies of this 10 | license document, but changing it is not allowed. 11 | 12 | 13 | Developer's Certificate of Origin 1.1 14 | 15 | By making a contribution to this project, I certify that: 16 | 17 | (a) The contribution was created in whole or in part by me and I 18 | have the right to submit it under the open source license 19 | indicated in the file; or 20 | 21 | (b) The contribution is based upon previous work that, to the best 22 | of my knowledge, is covered under an appropriate open source 23 | license and I have the right under that license to submit that 24 | work with modifications, whether created in whole or in part 25 | by me, under the same open source license (unless I am 26 | permitted to submit under a different license), as indicated 27 | in the file; or 28 | 29 | (c) The contribution was provided directly to me by some other 30 | person who certified (a), (b) or (c) and I have not modified 31 | it. 32 | 33 | (d) I understand and agree that this project and the contribution 34 | are public and that a record of the contribution (including all 35 | personal information I submit with it, including my sign-off) is 36 | maintained indefinitely and may be redistributed consistent with 37 | this project or the open source license(s) involved. 38 | -------------------------------------------------------------------------------- /examples-jsapi/.externalToolBuilders/org.maven.ide.eclipse.maven2Builder.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples-jsapi/README.adoc: -------------------------------------------------------------------------------- 1 | = JS-API 2 | 3 | Example of using RestEasy with JS-API: 4 | 5 | == Building the project: 6 | 7 | [source,bash] 8 | ---- 9 | mvn clean install 10 | ---- 11 | 12 | == Running the project and manually testing it: 13 | 14 | [source,bash] 15 | ---- 16 | mvn clean wildfly:dev 17 | ---- 18 | 19 | Open a browser at the following URL: http://127.0.0.1:8080/jsapi -------------------------------------------------------------------------------- /examples-jsapi/src/main/java/dev/resteasy/examples/Order.java: -------------------------------------------------------------------------------- 1 | package dev.resteasy.examples; 2 | 3 | import java.math.BigDecimal; 4 | import java.util.Objects; 5 | 6 | /** 7 | * @author James R. Perkins 8 | */ 9 | public class Order { 10 | private int id; 11 | private int items; 12 | 13 | private BigDecimal total; 14 | 15 | public Order() { 16 | } 17 | 18 | public int getId() { 19 | return id; 20 | } 21 | 22 | public void setId(final int id) { 23 | this.id = id; 24 | } 25 | 26 | public int getItems() { 27 | return items; 28 | } 29 | 30 | public void setItems(final int items) { 31 | this.items = items; 32 | } 33 | 34 | public BigDecimal getTotal() { 35 | return total; 36 | } 37 | 38 | public void setTotal(final BigDecimal total) { 39 | this.total = total; 40 | } 41 | 42 | @Override 43 | public int hashCode() { 44 | return Objects.hash(id); 45 | } 46 | 47 | @Override 48 | public boolean equals(final Object obj) { 49 | if (obj == this) { 50 | return true; 51 | } 52 | if (!(obj instanceof Order)) { 53 | return false; 54 | } 55 | return id == ((Order) obj).id; 56 | } 57 | 58 | @Override 59 | public String toString() { 60 | return "Order[id=" + id + ", items=" + items + ", total=" + total + "]"; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /examples-jsapi/src/main/java/dev/resteasy/examples/Orders.java: -------------------------------------------------------------------------------- 1 | package dev.resteasy.examples; 2 | 3 | import java.math.BigDecimal; 4 | import java.util.Random; 5 | 6 | import jakarta.ws.rs.GET; 7 | import jakarta.ws.rs.Path; 8 | import jakarta.ws.rs.PathParam; 9 | import jakarta.ws.rs.Produces; 10 | import jakarta.ws.rs.core.MediaType; 11 | 12 | @Path("orders") 13 | public class Orders { 14 | 15 | @GET 16 | @Path("{id}") 17 | @Produces(MediaType.APPLICATION_JSON) 18 | public Order createOrder(@PathParam("id") final int id) { 19 | return createRandomOrder(id); 20 | } 21 | 22 | private static Order createRandomOrder(final int id) { 23 | final Random random = new Random(); 24 | final Order order = new Order(); 25 | order.setId(id); 26 | order.setItems(nonZero(random, 30)); 27 | final String total = String.format("%d.%02d", nonZero(random, 300), random.nextInt(99)); 28 | order.setTotal(new BigDecimal(total)); 29 | return order; 30 | } 31 | 32 | private static int nonZero(final Random random, final int bound) { 33 | return nonZero(random, bound, 0); 34 | } 35 | 36 | private static int nonZero(final Random random, final int bound, final int count) { 37 | final int result = random.nextInt(bound); 38 | if (result == 0) { 39 | if (count == 10) { 40 | return 1; 41 | } 42 | return nonZero(random, bound, count + 1); 43 | } 44 | return result; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /examples-jsapi/src/main/java/dev/resteasy/examples/OrdersApplication.java: -------------------------------------------------------------------------------- 1 | package dev.resteasy.examples; 2 | 3 | import jakarta.ws.rs.ApplicationPath; 4 | import jakarta.ws.rs.core.Application; 5 | 6 | /** 7 | * @author Weinan Li 8 | */ 9 | @ApplicationPath("/resteasy") 10 | public class OrdersApplication extends Application { 11 | } 12 | -------------------------------------------------------------------------------- /examples-jsapi/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | JS API 7 | 8 | 9 | 10 | RESTEasy JSAPI 11 | org.jboss.resteasy.jsapi.JSAPIServlet 12 | 13 | 14 | 15 | RESTEasy JSAPI 16 | /rest-js 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /examples-jsapi/src/main/webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | 12 | RESTEasy JS-API Demo 13 | 14 | 15 |
16 |

RESTEasy JS-API Demo

17 |
18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
#ItemsTotal
31 |
32 | 33 | 34 | 46 | 47 | 48 | 51 | 54 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /examples-jsapi/src/test/java/dev/resteasy/examples/OrdersTest.java: -------------------------------------------------------------------------------- 1 | package dev.resteasy.examples; 2 | 3 | import java.util.Set; 4 | 5 | import jakarta.ws.rs.ApplicationPath; 6 | import jakarta.ws.rs.client.WebTarget; 7 | import jakarta.ws.rs.core.Application; 8 | import jakarta.ws.rs.core.Response; 9 | 10 | import org.junit.jupiter.api.Assertions; 11 | import org.junit.jupiter.api.Test; 12 | 13 | import dev.resteasy.junit.extension.annotations.RequestPath; 14 | import dev.resteasy.junit.extension.annotations.RestBootstrap; 15 | 16 | /** 17 | * @author James R. Perkins 18 | */ 19 | @RestBootstrap(OrdersTest.TestApplication.class) 20 | public class OrdersTest { 21 | 22 | @Test 23 | public void createOrder(@RequestPath("/orders/1") final WebTarget target) { 24 | final Response response = target.request().get(); 25 | Assertions.assertEquals(Response.Status.OK, response.getStatusInfo(), () -> response.readEntity(String.class)); 26 | final Order order = response.readEntity(Order.class); 27 | Assertions.assertEquals(1, order.getId()); 28 | } 29 | 30 | @ApplicationPath("/") 31 | public static class TestApplication extends Application { 32 | @Override 33 | public Set> getClasses() { 34 | return Set.of(Orders.class); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples-jsapi/src/test/resources/arquillian.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 23 | 24 | 25 | 26 | ${jboss.home} 27 | ${jboss.server.config.file.name:standalone.xml} 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /file-upload/README.adoc: -------------------------------------------------------------------------------- 1 | = File Upload 2 | 3 | This is a simple example of using a Jakarta REST application to upload files using `multipart/form-data`. It uses the 4 | following Jakarta EE 10 API's: 5 | 6 | * https://jakarta.ee/specifications/restful-ws/[Jakarta RESTful Web Services] 7 | * https://jakarta.ee/specifications/cdi/[Jakarta Contexts Dependency Injection] 8 | 9 | The client side is composed of HTML and JavaScript. It allows you to upload image files, text files and PDF's by 10 | default. It then displays the uploaded files. The default location of the uploaded files is the server directory under 11 | `file-uploads/${user.name}`. This can be overridden with the `upload.directory` system property. 12 | 13 | == Building the application: 14 | 15 | [source,bash] 16 | ---- 17 | mvn clean verify 18 | ---- 19 | 20 | == Running the application in WildFly: 21 | 22 | [source,bash] 23 | ---- 24 | mvn clean wildfly:dev 25 | ---- 26 | 27 | Open a browser at the following URL: http://127.0.0.1:8080/file-upload -------------------------------------------------------------------------------- /file-upload/src/main/java/dev/resteasy/examples/Environment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JBoss, Home of Professional Open Source. 3 | * 4 | * Copyright 2025 Red Hat, Inc., and individual contributors 5 | * as indicated by the @author tags. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * 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, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package dev.resteasy.examples; 21 | 22 | import java.io.IOException; 23 | import java.io.UncheckedIOException; 24 | import java.nio.file.FileSystems; 25 | import java.nio.file.Files; 26 | import java.nio.file.Path; 27 | import java.nio.file.attribute.PosixFilePermissions; 28 | import java.util.Optional; 29 | 30 | /** 31 | * @author James R. Perkins 32 | */ 33 | public class Environment { 34 | 35 | private static final Path UPLOAD_DIRECTORY; 36 | private static final boolean SUPPORTS_POSIX; 37 | 38 | static { 39 | final String user = System.getProperty("user.name", "anonymous"); 40 | final String uploadDirectory = System.getProperty("upload.directory"); 41 | Path dir = null; 42 | if (uploadDirectory == null) { 43 | final String jbossHome = System.getProperty("jboss.home.dir"); 44 | if (jbossHome != null) { 45 | dir = Path.of(jbossHome, "file-uploads", user); 46 | } 47 | } else { 48 | dir = Path.of(uploadDirectory, user); 49 | } 50 | try { 51 | if (dir == null) { 52 | dir = Files.createTempDirectory("file-uploads").resolve(user); 53 | } 54 | if (Files.notExists(dir)) { 55 | Files.createDirectories(dir); 56 | } 57 | UPLOAD_DIRECTORY = dir; 58 | } catch (IOException e) { 59 | throw new UncheckedIOException(e); 60 | } 61 | 62 | SUPPORTS_POSIX = FileSystems.getDefault().supportedFileAttributeViews().contains("posix"); 63 | } 64 | 65 | public static Path uploadDirectory() { 66 | return UPLOAD_DIRECTORY; 67 | } 68 | 69 | public static boolean supportsPosix() { 70 | return SUPPORTS_POSIX; 71 | } 72 | 73 | public static Optional resolvePermissions(final Path file) { 74 | if (Files.isRegularFile(file)) { 75 | if (SUPPORTS_POSIX) { 76 | try { 77 | return Optional.of(PosixFilePermissions.toString(Files.getPosixFilePermissions(file))); 78 | } catch (IOException e) { 79 | throw new UncheckedIOException(e); 80 | } 81 | } 82 | } 83 | return Optional.empty(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /file-upload/src/main/java/dev/resteasy/examples/SizeUnit.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JBoss, Home of Professional Open Source. 3 | * 4 | * Copyright 2025 Red Hat, Inc., and individual contributors 5 | * as indicated by the @author tags. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * 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, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package dev.resteasy.examples; 21 | 22 | import java.text.DecimalFormat; 23 | 24 | public enum SizeUnit { 25 | BYTE(1L, "B") { 26 | @Override 27 | public String toString(final long size) { 28 | return size + "B"; 29 | } 30 | }, 31 | KILOBYTE(BYTE, "KB"), 32 | MEGABYTE(KILOBYTE, "MB"), 33 | GIGABYTE(MEGABYTE, "GB"), 34 | TERABYTE(GIGABYTE, "TB"), 35 | PETABYTE(TERABYTE, "PB"), 36 | EXABYTE(TERABYTE, "EB"), 37 | 38 | ; 39 | 40 | private static final DecimalFormat FORMAT = new DecimalFormat("#.##"); 41 | private final long sizeInBytes; 42 | private final String abbreviation; 43 | 44 | SizeUnit(final long sizeInBytes, final String abbreviation) { 45 | this.sizeInBytes = sizeInBytes; 46 | this.abbreviation = abbreviation; 47 | } 48 | 49 | SizeUnit(final SizeUnit base, final String abbreviation) { 50 | this.sizeInBytes = base.sizeInBytes << 10; 51 | this.abbreviation = abbreviation; 52 | } 53 | 54 | /** 55 | * Returns the abbreviation for the unit. 56 | * 57 | * @return the abbreviation for the unit 58 | */ 59 | public String abbreviation() { 60 | return abbreviation; 61 | } 62 | 63 | /** 64 | * Converts the given size to bytes from this unit. For example {@code SizeUnit.KILOBYTES.toBytes(1L)} would return 65 | * 1024. 66 | * 67 | * @param size the size to convert 68 | * 69 | * @return the size in bytes 70 | */ 71 | public long toBytes(final long size) { 72 | return Math.multiplyExact(sizeInBytes, size); 73 | } 74 | 75 | /** 76 | * Converts the given size to the given unit to this unit. 77 | * 78 | * @param size the size to convert 79 | * @param unit the unit to convert the size to 80 | * 81 | * @return the converted units 82 | */ 83 | public double convert(final long size, final SizeUnit unit) { 84 | if (unit == BYTE) { 85 | return toBytes(size); 86 | } 87 | final long bytes = toBytes(size); 88 | return ((double) bytes / unit.sizeInBytes); 89 | } 90 | 91 | /** 92 | * Converts the size to a human-readable string format. 93 | *

94 | * For example {@code SizeUnit.KILOBYTE.toString(1024L)} would return "1 KB". 95 | *

96 | * 97 | * @param size the size, in bytes 98 | * 99 | * @return a human-readable size 100 | */ 101 | public String toString(final long size) { 102 | return FORMAT.format((double) size / sizeInBytes) + abbreviation; 103 | } 104 | 105 | /** 106 | * Converts the size, in bytes, to a human-readable form. For example {@code 1024} bytes return "1 KB". 107 | * 108 | * @param size the size, in bytes, to convert 109 | * 110 | * @return a human-readable size 111 | */ 112 | public static String toHumanReadable(final long size) { 113 | if (size == 0L) { 114 | return "0B"; 115 | } 116 | final SizeUnit[] values = values(); 117 | for (int i = values.length - 1; i >= 0; i--) { 118 | final SizeUnit unit = values[i]; 119 | if (size >= unit.sizeInBytes) { 120 | return unit.toString(size); 121 | } 122 | } 123 | return size + "B"; 124 | } 125 | } -------------------------------------------------------------------------------- /file-upload/src/main/java/dev/resteasy/examples/resources/FileResource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JBoss, Home of Professional Open Source. 3 | * 4 | * Copyright 2025 Red Hat, Inc., and individual contributors 5 | * as indicated by the @author tags. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * 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, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package dev.resteasy.examples.resources; 21 | 22 | import java.io.IOException; 23 | import java.nio.file.Files; 24 | import java.nio.file.StandardCopyOption; 25 | import java.util.stream.Collectors; 26 | 27 | import jakarta.json.Json; 28 | import jakarta.json.JsonArray; 29 | import jakarta.json.JsonArrayBuilder; 30 | import jakarta.json.JsonObject; 31 | import jakarta.json.JsonObjectBuilder; 32 | import jakarta.ws.rs.BadRequestException; 33 | import jakarta.ws.rs.Consumes; 34 | import jakarta.ws.rs.FormParam; 35 | import jakarta.ws.rs.GET; 36 | import jakarta.ws.rs.POST; 37 | import jakarta.ws.rs.Path; 38 | import jakarta.ws.rs.PathParam; 39 | import jakarta.ws.rs.Produces; 40 | import jakarta.ws.rs.core.EntityPart; 41 | import jakarta.ws.rs.core.MediaType; 42 | import jakarta.ws.rs.core.Response; 43 | 44 | import dev.resteasy.examples.Environment; 45 | import dev.resteasy.examples.SizeUnit; 46 | 47 | /** 48 | * @author James R. Perkins 49 | */ 50 | @Path("/upload") 51 | public class FileResource { 52 | 53 | @GET 54 | @Produces(MediaType.APPLICATION_JSON) 55 | @Path("/dir") 56 | public Response dir() { 57 | return Response.ok(Json.createObjectBuilder().add("dir", Environment.uploadDirectory().toString()).build()).build(); 58 | } 59 | 60 | @GET 61 | @Produces(MediaType.APPLICATION_JSON) 62 | public JsonArray listDirectory() throws IOException { 63 | final JsonArrayBuilder builder = Json.createArrayBuilder(); 64 | try (var paths = Files.walk(Environment.uploadDirectory())) { 65 | for (var path : paths.filter(p -> !p.equals(Environment.uploadDirectory())).sorted().collect(Collectors.toList())) { 66 | builder.add(toJson(path)); 67 | } 68 | } 69 | return builder.build(); 70 | } 71 | 72 | @GET 73 | @Path("{fileName}") 74 | @Produces(MediaType.APPLICATION_OCTET_STREAM) 75 | public Response download(@PathParam("fileName") String fileName) throws IOException { 76 | final var file = Environment.uploadDirectory().resolve(fileName); 77 | if (Files.notExists(file)) { 78 | return Response.status(Response.Status.NOT_FOUND).build(); 79 | } 80 | final Response.ResponseBuilder builder = Response.ok(Files.newInputStream(file)); 81 | builder.header("Content-Disposition", "attachment; filename=\"" + fileName + "\""); 82 | builder.header("Content-Length", Files.size(file)); 83 | return Response.ok(Files.newInputStream(file)).build(); 84 | } 85 | 86 | @POST 87 | @Consumes(MediaType.MULTIPART_FORM_DATA) 88 | public JsonObject uploadFile(@FormParam("file") final EntityPart file) throws IOException { 89 | 90 | if (file.getFileName().isEmpty()) { 91 | throw new BadRequestException("No fle was uploaded."); 92 | } 93 | final String fileName = file.getFileName().get(); 94 | if (fileName.isBlank()) { 95 | throw new BadRequestException("The file name could not be determined."); 96 | } 97 | 98 | // Copy the contents to a file 99 | final var uploadFile = Environment.uploadDirectory().resolve(fileName); 100 | Files.copy(file.getContent(), uploadFile, StandardCopyOption.REPLACE_EXISTING); 101 | return toJson(uploadFile); 102 | } 103 | 104 | private static JsonObject toJson(final java.nio.file.Path file) throws IOException { 105 | final String mimeType = Files.probeContentType(file); 106 | final JsonObjectBuilder builder = Json.createObjectBuilder(); 107 | builder.add("fileName", Environment.uploadDirectory().relativize(file).toString()); 108 | if (mimeType == null) { 109 | builder.addNull("mimeType"); 110 | } else { 111 | builder.add("mimeType", mimeType); 112 | } 113 | 114 | builder.add("size", SizeUnit.toHumanReadable(Files.size(file))); 115 | Environment.resolvePermissions(file) 116 | .ifPresent(permissions -> builder.add("permissions", permissions)); 117 | return builder.build(); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /file-upload/src/main/java/dev/resteasy/examples/resources/RestActivator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JBoss, Home of Professional Open Source. 3 | * 4 | * Copyright 2025 Red Hat, Inc., and individual contributors 5 | * as indicated by the @author tags. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * 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, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package dev.resteasy.examples.resources; 21 | 22 | import jakarta.ws.rs.ApplicationPath; 23 | import jakarta.ws.rs.core.Application; 24 | 25 | /** 26 | * Indicates that the deployment is a REST deployment. 27 | * 28 | * @author James R. Perkins 29 | */ 30 | @ApplicationPath("/api") 31 | public class RestActivator extends Application { 32 | } 33 | -------------------------------------------------------------------------------- /file-upload/src/main/webapp/WEB-INF/beans.xml: -------------------------------------------------------------------------------- 1 | 19 | 20 | -------------------------------------------------------------------------------- /file-upload/src/main/webapp/index.html: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | 22 | 23 | 24 | File Uploads 25 | 26 | 28 | 30 | 31 | 32 | 33 | 65 |
66 |
67 |
68 | 69 |
70 |
71 |
72 | 74 |
75 | A file is required for uploading. 76 |
77 |
78 | 79 |
80 |
81 |
Files located in
82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 |
FileTypeSizePermissions
94 |
95 |
96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /file-upload/src/main/webapp/resources/main.js: -------------------------------------------------------------------------------- 1 | /* 2 | * JBoss, Home of Professional Open Source. 3 | * 4 | * Copyright 2025 Red Hat, Inc., and individual contributors 5 | * as indicated by the @author tags. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * 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, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | (() => { 21 | "use strict"; 22 | window.addEventListener('load', () => { 23 | const form = document.forms.namedItem("form"); 24 | 25 | const fileListing = document.querySelector("#file-listing"); 26 | 27 | /** 28 | * Get a listing of all the files in the directory 29 | */ 30 | const loadFiles = () => { 31 | fetch("api/upload/", { 32 | method: "GET", 33 | }).then(response => { 34 | if (response.status === 200) { 35 | response.json().then((json) => { 36 | for (const file of json) { 37 | addFile(file); 38 | } 39 | }); 40 | } else { 41 | response.text().then(t => { 42 | console.error("Failed to submit form", t); 43 | }); 44 | } 45 | }).catch(reason => { 46 | console.error(reason); 47 | }); 48 | }; 49 | 50 | /** 51 | * Add a file to the table 52 | * @param json the JSON representation of the file 53 | */ 54 | const addFile = (json) => { 55 | const row = document.createElement("tr"); 56 | const fileName = document.createElement("td"); 57 | if (json.fileName) { 58 | const a = document.createElement("a"); 59 | a.setAttribute("href", "api/upload/" + encodeURIComponent(json.fileName)); 60 | a.textContent = json.fileName; 61 | fileName.appendChild(a); 62 | } 63 | row.appendChild(fileName); 64 | 65 | const mimeType = document.createElement("td"); 66 | if (json.mimeType) { 67 | mimeType.textContent = json.mimeType; 68 | } else { 69 | mimeType.textContent = "N/A"; 70 | } 71 | row.appendChild(mimeType); 72 | 73 | const size = document.createElement("td"); 74 | if (json.size) { 75 | size.textContent = json.size; 76 | } else { 77 | size.textContent = "N/A"; 78 | } 79 | row.appendChild(size); 80 | 81 | const permissions = document.createElement("td"); 82 | if (json.permissions) { 83 | permissions.textContent = json.permissions; 84 | } else { 85 | permissions.textContent = "N/A"; 86 | } 87 | row.appendChild(permissions); 88 | 89 | fileListing.append(row); 90 | }; 91 | 92 | const currentTheme = document.querySelector("#current-theme"); 93 | const darkThemeButton = document.querySelector("#dark-theme"); 94 | const lightThemeButton = document.querySelector("#light-theme"); 95 | 96 | darkThemeButton.addEventListener("click", () => { 97 | document.body.setAttribute("data-bs-theme", "dark"); 98 | darkThemeButton.classList.add("active"); 99 | lightThemeButton.classList.remove("active"); 100 | currentTheme.classList.add("bi-moon-stars-fill"); 101 | currentTheme.classList.remove("bi-sun-fill"); 102 | // bi-sun-fill bi-moon-stars-fill 103 | }); 104 | 105 | lightThemeButton.addEventListener("click", () => { 106 | document.body.setAttribute("data-bs-theme", "light"); 107 | lightThemeButton.classList.add("active"); 108 | darkThemeButton.classList.remove("active"); 109 | currentTheme.classList.add("bi-sun-fill"); 110 | currentTheme.classList.remove("bi-moon-stars-fill"); 111 | }); 112 | 113 | // Listen for the submit event 114 | form.addEventListener("submit", (event) => { 115 | event.preventDefault(); 116 | const formData = new FormData(event.currentTarget); 117 | document.querySelector("#file").value = ""; 118 | fetch("api/upload/", { 119 | method: "POST", 120 | body: formData 121 | }).then(response => { 122 | if (response.status === 200) { 123 | response.json().then((json) => { 124 | addFile(json); 125 | }); 126 | } else { 127 | response.text().then(t => { 128 | console.error("Failed to submit form", t); 129 | }); 130 | } 131 | }).catch(reason => { 132 | console.error(reason); 133 | }); 134 | }); 135 | 136 | // Load the default directory 137 | fetch("api/upload/dir", { 138 | method: "GET" 139 | }).then(response => { 140 | if (response.status === 200) { 141 | response.json().then((json) => { 142 | document.querySelector("#location").textContent = json.dir; 143 | }); 144 | } else { 145 | response.text().then(t => { 146 | console.error("Failed to get default directory", t); 147 | }); 148 | } 149 | }).catch(reason => { 150 | console.error(reason); 151 | }); 152 | // Initial load of the currently listed files 153 | loadFiles(); 154 | }); 155 | })() -------------------------------------------------------------------------------- /file-upload/src/test/resources/arquillian.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 23 | 24 | 25 | 26 | ${jboss.home} 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /grpc-bridge-example/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | dev.resteasy.tools 5 | resteasy-parent 6 | 2.0.3.Final 7 | 8 | 9 | 10 | dev.resteasy.examples 11 | grpcToRest.example 12 | 6.2.0.Final-SNAPSHOT 13 | war 14 | gRPC to Jakarta REST example 15 | Shows how to build a gRPC to Jakarta REST bridge example 16 | 17 | 18 | Apache License 2.0 19 | https://repository.jboss.org/licenses/apache-2.0.txt 20 | repo 21 | 22 | 23 | 24 | scm:git:git://github.com/resteasy/resteasy-examples.git 25 | scm:git:git@github.com:resteasy/resteasy-examples.git 26 | https://github.com/resteasy/resteasy-examples/tree/main/ 27 | 28 | 29 | 30 | 10.0.0 31 | 6.2.3.Final 32 | 4.13.2 33 | 34 | 35 | 36 | 37 | 38 | jakarta.platform 39 | jakarta.jakartaee-bom 40 | ${version.jakarta.ee} 41 | import 42 | pom 43 | 44 | 45 | 46 | 47 | 48 | 49 | jakarta.ws.rs 50 | jakarta.ws.rs-api 51 | 52 | 53 | 54 | 55 | ${project.artifactId}-${version} 56 | 57 | 58 | maven-war-plugin 59 | 60 | false 61 | 62 | 63 | 64 | net.revelc.code.formatter 65 | formatter-maven-plugin 66 | 67 | 68 | net.revelc.code 69 | impsort-maven-plugin 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /grpc-bridge-example/src/main/java/dev/resteasy/example/grpc/greet/GeneralGreeting.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JBoss, Home of Professional Open Source. 3 | * 4 | * Copyright 2023 Red Hat, Inc., and individual contributors 5 | * as indicated by the @author tags. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * 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, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package dev.resteasy.example.grpc.greet; 21 | 22 | public class GeneralGreeting extends Greeting { 23 | private String salute; 24 | 25 | public GeneralGreeting() { 26 | } 27 | 28 | public GeneralGreeting(String salute, String s) { 29 | super(s); 30 | this.salute = salute; 31 | } 32 | 33 | public String getSalute() { 34 | return salute; 35 | } 36 | 37 | public void setSalute(String salute) { 38 | this.salute = salute; 39 | } 40 | 41 | public String toString() { 42 | return salute + ", " + getS(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /grpc-bridge-example/src/main/java/dev/resteasy/example/grpc/greet/Greeter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JBoss, Home of Professional Open Source. 3 | * 4 | * Copyright 2023 Red Hat, Inc., and individual contributors 5 | * as indicated by the @author tags. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * 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, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package dev.resteasy.example.grpc.greet; 21 | 22 | import jakarta.ws.rs.GET; 23 | import jakarta.ws.rs.Path; 24 | import jakarta.ws.rs.PathParam; 25 | import jakarta.ws.rs.Produces; 26 | import jakarta.ws.rs.QueryParam; 27 | import jakarta.ws.rs.core.MediaType; 28 | 29 | @Path("") 30 | public class Greeter { 31 | 32 | @GET 33 | @Path("greet/{s}") 34 | @Produces(MediaType.APPLICATION_JSON) 35 | public Greeting greet(@PathParam("s") String s) { 36 | return new Greeting("hello, " + s); 37 | } 38 | 39 | @GET 40 | @Path("salute/{s}") 41 | @Produces(MediaType.APPLICATION_JSON) 42 | public GeneralGreeting generalGreet(@QueryParam("salute") String salute, @PathParam("s") String s) { 43 | return getGeneralGreeting(salute, s); 44 | } 45 | 46 | private GeneralGreeting getGeneralGreeting(String salute, String name) { 47 | return new GeneralGreeting(salute, name); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /grpc-bridge-example/src/main/java/dev/resteasy/example/grpc/greet/Greeting.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JBoss, Home of Professional Open Source. 3 | * 4 | * Copyright 2023 Red Hat, Inc., and individual contributors 5 | * as indicated by the @author tags. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * 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, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package dev.resteasy.example.grpc.greet; 21 | 22 | public class Greeting { 23 | private String s; 24 | 25 | public Greeting() { 26 | } 27 | 28 | public Greeting(String s) { 29 | this.s = s; 30 | } 31 | 32 | public String getS() { 33 | return s; 34 | } 35 | 36 | public void setS(String s) { 37 | this.s = s; 38 | } 39 | 40 | public String toString() { 41 | return "Hello, " + s; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /grpc-bridge-example/src/main/resources/GreetingTest: -------------------------------------------------------------------------------- 1 | /* 2 | * JBoss, Home of Professional Open Source. 3 | * 4 | * Copyright 2023 Red Hat, Inc., and individual contributors 5 | * as indicated by the @author tags. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * 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, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package org.jboss.resteasy.grpc.server; 21 | 22 | import org.junit.Assert; 23 | import org.junit.BeforeClass; 24 | import org.junit.Test; 25 | 26 | import org.greet.GreetServiceGrpc; 27 | import org.greet.GreetServiceGrpc.GreetServiceBlockingStub; 28 | import org.greet.Greet_proto.GeneralEntityMessage; 29 | import org.greet.Greet_proto.GeneralReturnMessage; 30 | import org.greet.Greet_proto.dev_resteasy_example_grpc_greet___GeneralGreeting; 31 | import org.greet.Greet_proto.dev_resteasy_example_grpc_greet___Greeting; 32 | 33 | import io.grpc.ManagedChannel; 34 | import io.grpc.ManagedChannelBuilder; 35 | import io.grpc.StatusRuntimeException; 36 | import jakarta.ws.rs.client.Client; 37 | import jakarta.ws.rs.client.ClientBuilder; 38 | import jakarta.ws.rs.core.Response; 39 | 40 | public class GreetingTest { 41 | 42 | private static ManagedChannel channelPlaintext; 43 | private static GreetServiceBlockingStub blockingStub; 44 | 45 | @BeforeClass 46 | public static void beforeClass() throws Exception { 47 | // Establish ServletContext 48 | Client client = ClientBuilder.newClient(); 49 | String url = "http://localhost:8080/grpcToRest.example.grpc-1.0.1.Final-SNAPSHOT/grpcToJakartaRest/grpcserver/context"; 50 | Response response = client.target(url).request().get(); 51 | Assert.assertEquals(200, response.getStatus()); 52 | client.close(); 53 | 54 | // Create gRPC connection 55 | channelPlaintext = ManagedChannelBuilder.forTarget("localhost:9555").usePlaintext().build(); 56 | blockingStub = GreetServiceGrpc.newBlockingStub(channelPlaintext); 57 | } 58 | 59 | @Test 60 | public void testGreeting() { 61 | GeneralEntityMessage.Builder builder = GeneralEntityMessage.newBuilder(); 62 | GeneralEntityMessage gem = builder.setURL("http://localhost:8080/greet/Bill").build(); 63 | try { 64 | GeneralReturnMessage grm = blockingStub.greet(gem); 65 | dev_resteasy_example_grpc_greet___Greeting greeting = grm.getDevResteasyExampleGrpcGreetGreetingField(); 66 | Assert.assertEquals("hello, Bill", greeting.getS()); 67 | } catch (StatusRuntimeException e) { 68 | // 69 | } 70 | } 71 | 72 | @Test 73 | public void testGeneralGreeting() { 74 | GeneralEntityMessage.Builder builder = GeneralEntityMessage.newBuilder(); 75 | GeneralEntityMessage gem = builder.setURL("http://localhost:8080/salute/Bill?salute=Heyyy").build(); 76 | GeneralReturnMessage grm = blockingStub.generalGreet(gem); 77 | dev_resteasy_example_grpc_greet___GeneralGreeting greeting = grm.getDevResteasyExampleGrpcGreetGeneralGreetingField(); 78 | Assert.assertEquals("Heyyy", greeting.getSalute()); 79 | Assert.assertEquals("Bill", greeting.getGreetingSuper().getS()); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /json-binding/README.adoc: -------------------------------------------------------------------------------- 1 | = Jettison Example 2 | 3 | Example of using RestEasy with: 4 | - Using JSON with RESTEasy 5 | - The Jakarta JSON Binding Provider 6 | 7 | == Building the project: 8 | 9 | [source,bash] 10 | ---- 11 | mvn clean install 12 | ---- 13 | 14 | == Running the project and manually testing it: 15 | 16 | [source,bash] 17 | ---- 18 | mvn wildfly:dev 19 | ---- 20 | 21 | The data is marshalled to JSON. Use the following `curl` command to access the JSON data: 22 | 23 | [source,bash] 24 | ---- 25 | $ curl http://localhost:8080/json-binding/library/books 26 | ---- 27 | 28 | To see an individual book, use the following `curl` command to access the JSON data: 29 | 30 | [source,bash] 31 | ---- 32 | $ curl http://localhost:8080/json-binding/library/books/9780596158040 33 | ---- -------------------------------------------------------------------------------- /json-binding/src/main/java/dev/resteasy/examples/data/Book.java: -------------------------------------------------------------------------------- 1 | package dev.resteasy.examples.data; 2 | 3 | import java.util.Objects; 4 | 5 | import jakarta.json.bind.annotation.JsonbPropertyOrder; 6 | 7 | /** 8 | * @author Bill Burke 9 | * @version $Revision: 1 $ 10 | */ 11 | @JsonbPropertyOrder({ 12 | "title", 13 | "author", 14 | "ISBN" 15 | }) 16 | public class Book implements Comparable { 17 | private String author; 18 | private long ISBN; 19 | private String title; 20 | 21 | public Book() { 22 | } 23 | 24 | public Book(String author, long ISBN, String title) { 25 | this.author = author; 26 | this.ISBN = ISBN; 27 | this.title = title; 28 | } 29 | 30 | public String getAuthor() { 31 | return author; 32 | } 33 | 34 | public void setAuthor(String author) { 35 | this.author = author; 36 | } 37 | 38 | public long getISBN() { 39 | return ISBN; 40 | } 41 | 42 | public void setISBN(long ISBN) { 43 | this.ISBN = ISBN; 44 | } 45 | 46 | public String getTitle() { 47 | return title; 48 | } 49 | 50 | public void setTitle(String title) { 51 | this.title = title; 52 | } 53 | 54 | @Override 55 | public int compareTo(final Book o) { 56 | return Long.compare(ISBN, o.ISBN); 57 | } 58 | 59 | @Override 60 | public int hashCode() { 61 | return Objects.hash(Long.hashCode(ISBN)); 62 | } 63 | 64 | @Override 65 | public boolean equals(final Object obj) { 66 | if (obj == this) { 67 | return true; 68 | } 69 | if (!(obj instanceof Book)) { 70 | return false; 71 | } 72 | final Book other = (Book) obj; 73 | return ISBN == other.ISBN; 74 | } 75 | 76 | @Override 77 | public String toString() { 78 | return "Book[title=" + title + ", author=" + author + ", ISBN=" + ISBN + "]"; 79 | } 80 | } -------------------------------------------------------------------------------- /json-binding/src/main/java/dev/resteasy/examples/data/BookListing.java: -------------------------------------------------------------------------------- 1 | package dev.resteasy.examples.data; 2 | 3 | import java.util.Collection; 4 | import java.util.Set; 5 | import java.util.TreeSet; 6 | 7 | import jakarta.json.bind.annotation.JsonbCreator; 8 | import jakarta.json.bind.annotation.JsonbProperty; 9 | import jakarta.json.bind.annotation.JsonbTypeDeserializer; 10 | 11 | /** 12 | * @author Bill Burke 13 | * @version $Revision: 1 $ 14 | */ 15 | @JsonbTypeDeserializer(BookListingDeserializer.class) 16 | public class BookListing { 17 | private final Set books; 18 | 19 | @JsonbCreator 20 | public BookListing(@JsonbProperty Collection books) { 21 | this.books = new TreeSet<>(books); 22 | } 23 | 24 | public Set getBooks() { 25 | return books; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /json-binding/src/main/java/dev/resteasy/examples/data/BookListingDeserializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JBoss, Home of Professional Open Source. 3 | * 4 | * Copyright 2022 Red Hat, Inc., and individual contributors 5 | * as indicated by the @author tags. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * 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, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package dev.resteasy.examples.data; 21 | 22 | import java.lang.reflect.Type; 23 | import java.util.ArrayList; 24 | import java.util.List; 25 | 26 | import jakarta.json.bind.serializer.DeserializationContext; 27 | import jakarta.json.bind.serializer.JsonbDeserializer; 28 | import jakarta.json.stream.JsonParser; 29 | 30 | /** 31 | * @author James R. Perkins 32 | */ 33 | public class BookListingDeserializer implements JsonbDeserializer { 34 | @Override 35 | public BookListing deserialize(final JsonParser parser, final DeserializationContext ctx, final Type rtType) { 36 | final List books = new ArrayList<>(); 37 | while (parser.hasNext()) { 38 | final JsonParser.Event event = parser.next(); 39 | if (event == JsonParser.Event.START_OBJECT) { 40 | books.add(ctx.deserialize(Book.class, parser)); 41 | } 42 | if (event == JsonParser.Event.END_OBJECT) { 43 | break; 44 | } 45 | } 46 | return new BookListing(books); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /json-binding/src/main/java/dev/resteasy/examples/service/Library.java: -------------------------------------------------------------------------------- 1 | package dev.resteasy.examples.service; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | import jakarta.ws.rs.GET; 7 | import jakarta.ws.rs.Path; 8 | import jakarta.ws.rs.PathParam; 9 | import jakarta.ws.rs.Produces; 10 | import jakarta.ws.rs.core.MediaType; 11 | import jakarta.ws.rs.core.Response; 12 | 13 | import dev.resteasy.examples.data.Book; 14 | import dev.resteasy.examples.data.BookListing; 15 | 16 | /** 17 | * @author Bill Burke 18 | * @version $Revision: 1 $ 19 | */ 20 | @Path("library") 21 | public class Library { 22 | private final Map books; 23 | 24 | public Library() { 25 | books = Map.ofEntries( 26 | Map.entry(9780596529260L, new Book("Leonard Richardson", 9780596529260L, "RESTful Web Services")), 27 | Map.entry(9780596009786L, new Book("Bill Burke", 9780596009786L, "EJB 3.0")), 28 | Map.entry(9780596158040L, new Book("Bill Burke", 9780596158040L, "RESTful Java with JAX-RS"))); 29 | } 30 | 31 | @GET 32 | @Path("books") 33 | @Produces(MediaType.APPLICATION_JSON) 34 | public BookListing getBooksMapped() { 35 | return new BookListing(List.copyOf(books.values())); 36 | } 37 | 38 | @GET 39 | @Path("books/{isbn}") 40 | @Produces(MediaType.APPLICATION_JSON) 41 | public Response getBook(@PathParam("isbn") final long isbn) { 42 | final Book book = books.get(isbn); 43 | if (book == null) { 44 | return Response.status(Response.Status.NOT_FOUND).build(); 45 | } 46 | return Response.ok(book).build(); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /json-binding/src/main/java/dev/resteasy/examples/service/LibraryApplication.java: -------------------------------------------------------------------------------- 1 | package dev.resteasy.examples.service; 2 | 3 | import java.util.Set; 4 | 5 | import jakarta.ws.rs.ApplicationPath; 6 | import jakarta.ws.rs.core.Application; 7 | 8 | /** 9 | * @author Bill Burke 10 | * @version $Revision: 1 $ 11 | */ 12 | @ApplicationPath("/") 13 | public class LibraryApplication extends Application { 14 | @Override 15 | public Set> getClasses() { 16 | return Set.of(Library.class); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /json-binding/src/test/java/dev/resteasy/examples/JsonBindingTest.java: -------------------------------------------------------------------------------- 1 | package dev.resteasy.examples; 2 | 3 | import java.net.URI; 4 | import java.util.Set; 5 | 6 | import jakarta.ws.rs.client.Client; 7 | import jakarta.ws.rs.client.ClientBuilder; 8 | import jakarta.ws.rs.core.MediaType; 9 | import jakarta.ws.rs.core.Response; 10 | import jakarta.ws.rs.core.UriBuilder; 11 | 12 | import org.jboss.arquillian.container.test.api.Deployment; 13 | import org.jboss.arquillian.container.test.api.RunAsClient; 14 | import org.jboss.arquillian.junit5.ArquillianExtension; 15 | import org.jboss.arquillian.test.api.ArquillianResource; 16 | import org.jboss.shrinkwrap.api.ShrinkWrap; 17 | import org.jboss.shrinkwrap.api.spec.WebArchive; 18 | import org.junit.jupiter.api.Assertions; 19 | import org.junit.jupiter.api.Test; 20 | import org.junit.jupiter.api.extension.ExtendWith; 21 | 22 | import dev.resteasy.examples.data.Book; 23 | import dev.resteasy.examples.data.BookListing; 24 | import dev.resteasy.examples.data.BookListingDeserializer; 25 | import dev.resteasy.examples.service.Library; 26 | import dev.resteasy.examples.service.LibraryApplication; 27 | 28 | /** 29 | * @author James R. Perkins 30 | */ 31 | @ExtendWith(ArquillianExtension.class) 32 | @RunAsClient 33 | public class JsonBindingTest { 34 | 35 | @Deployment 36 | public static WebArchive deployment() { 37 | return ShrinkWrap.create(WebArchive.class, JsonBindingTest.class.getSimpleName() + ".war") 38 | .addClasses(Book.class, BookListing.class, BookListingDeserializer.class, Library.class, 39 | LibraryApplication.class); 40 | } 41 | 42 | @ArquillianResource 43 | private URI uri; 44 | 45 | @Test 46 | public void books() { 47 | try ( 48 | Client client = ClientBuilder.newClient(); 49 | Response response = client.target(UriBuilder.fromUri(uri).path("library/books")) 50 | .request(MediaType.APPLICATION_JSON).get()) { 51 | Assertions.assertEquals(Response.Status.OK, response.getStatusInfo(), 52 | () -> response.readEntity(String.class)); 53 | final BookListing bookListing = response.readEntity(BookListing.class); 54 | Assertions.assertNotNull(bookListing); 55 | // We should have 3 books 56 | final Set books = bookListing.getBooks(); 57 | Assertions.assertEquals(3, books.size(), 58 | () -> "Expected 3 books, but got " + books); 59 | // The books should be in the ISBN order 60 | final Book[] array = books.toArray(new Book[0]); 61 | Assertions.assertEquals(9780596009786L, array[0].getISBN()); 62 | Assertions.assertEquals(9780596158040L, array[1].getISBN()); 63 | Assertions.assertEquals(9780596529260L, array[2].getISBN()); 64 | } 65 | } 66 | 67 | @Test 68 | public void singleBook() { 69 | try ( 70 | Client client = ClientBuilder.newClient(); 71 | Response response = client.target(UriBuilder.fromUri(uri).path("library/books/9780596158040")) 72 | .request(MediaType.APPLICATION_JSON).get()) { 73 | Assertions.assertEquals(Response.Status.OK, response.getStatusInfo(), 74 | () -> response.readEntity(String.class)); 75 | final Book book = response.readEntity(Book.class); 76 | Assertions.assertNotNull(book); 77 | Assertions.assertEquals(9780596158040L, book.getISBN()); 78 | Assertions.assertEquals("Bill Burke", book.getAuthor()); 79 | Assertions.assertEquals("RESTful Java with JAX-RS", book.getTitle()); 80 | } 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /json-binding/src/test/resources/arquillian.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 23 | 24 | 25 | 26 | ${jboss.home} 27 | ${jboss.server.config.file.name:standalone.xml} 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /microprofile-openapi/README.adoc: -------------------------------------------------------------------------------- 1 | = RESTEasy MicroProfile OpenAPI WAR Example 2 | 3 | Demonstrates creating a WAR with MicroProfile OpenAPI enabled. 4 | 5 | == Build And Run 6 | 7 | * To build: `mvn clean install` 8 | * To run: `mvn wildfly:dev` 9 | * Access the OpenAPI document with: `curl -H "Accept: application/json" http://127.0.0.1:8080/openapi` -------------------------------------------------------------------------------- /microprofile-openapi/src/main/java/dev/resteasy/examples/openapi/Product.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JBoss, Home of Professional Open Source. 3 | * 4 | * Copyright 2022 Red Hat, Inc., and individual contributors 5 | * as indicated by the @author tags. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * 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, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package dev.resteasy.examples.openapi; 21 | 22 | import java.util.Objects; 23 | 24 | public class Product implements Comparable { 25 | private String name; 26 | private int id; 27 | 28 | public Product() { 29 | } 30 | 31 | public Product(final int id, final String name) { 32 | this.id = id; 33 | this.name = name; 34 | } 35 | 36 | public String getName() { 37 | return name; 38 | } 39 | 40 | public void setName(String name) { 41 | this.name = name; 42 | } 43 | 44 | public int getId() { 45 | return id; 46 | } 47 | 48 | public void setId(int id) { 49 | this.id = id; 50 | } 51 | 52 | @Override 53 | public int hashCode() { 54 | return Objects.hash(id); 55 | } 56 | 57 | @Override 58 | public boolean equals(final Object obj) { 59 | if (this == obj) { 60 | return true; 61 | } 62 | if (!(obj instanceof Product)) { 63 | return false; 64 | } 65 | final Product other = (Product) obj; 66 | return id == other.id; 67 | } 68 | 69 | @Override 70 | public String toString() { 71 | return "Product[id=" + id + ", name=" + name + "]"; 72 | } 73 | 74 | @Override 75 | public int compareTo(final Product o) { 76 | return Integer.compare(id, o.id); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /microprofile-openapi/src/main/java/dev/resteasy/examples/openapi/ProductApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JBoss, Home of Professional Open Source. 3 | * 4 | * Copyright 2022 Red Hat, Inc., and individual contributors 5 | * as indicated by the @author tags. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * 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, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package dev.resteasy.examples.openapi; 21 | 22 | import jakarta.ws.rs.ApplicationPath; 23 | import jakarta.ws.rs.core.Application; 24 | 25 | import org.eclipse.microprofile.openapi.annotations.OpenAPIDefinition; 26 | import org.eclipse.microprofile.openapi.annotations.info.Info; 27 | 28 | @ApplicationPath("/") 29 | @OpenAPIDefinition(info = @Info(title = "ProductApplication", version = "1.0.0")) 30 | public class ProductApplication extends Application { 31 | } 32 | -------------------------------------------------------------------------------- /microprofile-openapi/src/main/java/dev/resteasy/examples/openapi/ProductResource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JBoss, Home of Professional Open Source. 3 | * 4 | * Copyright 2022 Red Hat, Inc., and individual contributors 5 | * as indicated by the @author tags. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * 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, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package dev.resteasy.examples.openapi; 21 | 22 | import jakarta.ws.rs.GET; 23 | import jakarta.ws.rs.Path; 24 | import jakarta.ws.rs.Produces; 25 | import jakarta.ws.rs.core.MediaType; 26 | 27 | import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; 28 | 29 | @Path("/products") 30 | public class ProductResource { 31 | @APIResponse(description = "return a list of products") 32 | @GET 33 | @Produces(MediaType.APPLICATION_JSON) 34 | public Product[] getProducts() { 35 | return new Product[] { new Product(111, "JBoss EAP"), new Product(222, "RHEL"), 36 | new Product(333, "CentOS") }; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /microprofile-openapi/src/test/java/dev/resteasy/examples/openapi/OpenApiTestIT.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JBoss, Home of Professional Open Source. 3 | * 4 | * Copyright 2022 Red Hat, Inc., and individual contributors 5 | * as indicated by the @author tags. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * 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, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package dev.resteasy.examples.openapi; 21 | 22 | import jakarta.ws.rs.client.Client; 23 | import jakarta.ws.rs.client.ClientBuilder; 24 | import jakarta.ws.rs.client.WebTarget; 25 | import jakarta.ws.rs.core.Response; 26 | 27 | import org.junit.jupiter.api.Assertions; 28 | import org.junit.jupiter.api.Test; 29 | 30 | public class OpenApiTestIT { 31 | @Test 32 | public void testResponse() { 33 | try (Client client = ClientBuilder.newClient()) { 34 | final WebTarget target = client.target("http://localhost:8080/openapi"); 35 | try (Response response = target.request().get()) { 36 | Assertions.assertEquals(200, response.getStatus()); 37 | final String doc = response.readEntity(String.class); 38 | Assertions.assertNotNull(doc); 39 | Assertions.assertTrue(doc.contains("title: ProductApplication")); 40 | Assertions.assertTrue(doc.contains("return a list of products")); 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | dev.resteasy.tools 7 | resteasy-parent 8 | 2.0.3.Final 9 | 10 | 11 | 12 | dev.resteasy.examples 13 | resteasy-examples-parent 14 | 6.2.0.Final-SNAPSHOT 15 | pom 16 | RESTEasy Examples 17 | 18 | https://resteasy.dev 19 | 20 | 21 | 22 | Apache License 2.0 23 | https://repository.jboss.org/licenses/apache-2.0.txt 24 | repo 25 | 26 | 27 | 28 | 29 | scm:git:git://github.com/resteasy/resteasy-examples.git 30 | scm:git:git@github.com:resteasy/resteasy-examples.git 31 | https://github.com/resteasy/resteasy-examples/tree/main/ 32 | 33 | 34 | 35 | async-job-service 36 | bootable-jar 37 | bootstrap-cdi 38 | contacts 39 | examples-jsapi 40 | file-upload 41 | json-binding 42 | microprofile-openapi 43 | servlet-example 44 | smime 45 | standalone-multipart 46 | tracing-example 47 | 48 | 49 | -------------------------------------------------------------------------------- /security.txt: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Security Contacts and Procedures 4 | 5 | The RESTEasy community takes security very seriously, and we aim to take immediate action to address serious 6 | security-related problems that involve our products or services. 7 | 8 | Please report any suspected security vulnerability in this project to Red Hat Product Security at secalert@redhat.com. 9 | You can use our GPG key to communicate with us securely. 10 | 11 | To report an issue in any Red Hat branded website or online service, please contact Red Hat Information Security at 12 | site-security@redhat.com. 13 | https://access.redhat.com/security/team/contact 14 | -------------------------------------------------------------------------------- /servlet-example/README.md: -------------------------------------------------------------------------------- 1 | ## Usage 2 | 3 | This project provides a minimal example showing the usage of RESTEasy in a Servlet container like Jetty or Tomcat. 4 | 5 | To build the project: 6 | 7 | ```bash 8 | $ mvn install 9 | ``` 10 | 11 | The example provides a Jetty plugin, so you can use the Maven command to start the server directly: 12 | 13 | ```bash 14 | $ mvn jetty:run 15 | ``` 16 | 17 | After the Jetty server is started, you can access the service with the following command: 18 | 19 | 20 | ```bash 21 | $ curl http://localhost:8080/app/greet 22 | Hello, world! 23 | ``` 24 | 25 | Because Tomcat doesn't provide an updated Maven plugin, so to deploy the example into Tomcat, you need to download a Tomcat 11 manually, and then deploy the built war `servlet-example.war` into the Tomcat `webapp` directory. After the Tomcat server is started and the example is deployed, you can access the service like this: 26 | 27 | ```bash 28 | ➤ curl http://localhost:8080/servlet-example/app/greet 29 | Hello, world! 30 | ``` 31 | 32 | -------------------------------------------------------------------------------- /servlet-example/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | dev.resteasy.tools 7 | resteasy-parent 8 | 2.0.3.Final 9 | 10 | 11 | 12 | dev.resteasy.examples 13 | servlet-example 14 | 6.2.0.Final-SNAPSHOT 15 | war 16 | RESTEasy Example: Minimal Servlet Example 17 | 18 | https://resteasy.dev 19 | 20 | 21 | 22 | Apache License 2.0 23 | https://repository.jboss.org/licenses/apache-2.0.txt 24 | repo 25 | 26 | 27 | 28 | 29 | scm:git:git://github.com/resteasy/resteasy-examples.git 30 | scm:git:git@github.com:resteasy/resteasy-examples.git 31 | https://github.com/resteasy/resteasy-examples/tree/main/ 32 | 33 | 34 | 35 | 6.2.12.Final 36 | 11.0.25 37 | 38 | 39 | 40 | 41 | org.jboss.resteasy 42 | resteasy-servlet-initializer 43 | ${version.org.jboss.resteasy} 44 | 45 | 46 | 47 | servlet-example 48 | 49 | 50 | org.eclipse.jetty 51 | jetty-maven-plugin 52 | ${version.org.eclipse.jetty} 53 | 54 | foo 55 | 9999 56 | 10 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /servlet-example/src/main/java/dev/resteasy/examples/servlet/GreetingResource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JBoss, Home of Professional Open Source. 3 | * 4 | * Copyright 2023 Red Hat, Inc., and individual contributors 5 | * as indicated by the @author tags. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * 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, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package dev.resteasy.examples.servlet; 21 | 22 | import jakarta.ws.rs.GET; 23 | import jakarta.ws.rs.Path; 24 | 25 | @Path("/greet") 26 | public class GreetingResource { 27 | @GET 28 | public String get() { 29 | return "Hello, world!"; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /servlet-example/src/main/java/dev/resteasy/examples/servlet/MyApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JBoss, Home of Professional Open Source. 3 | * 4 | * Copyright 2023 Red Hat, Inc., and individual contributors 5 | * as indicated by the @author tags. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * 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, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package dev.resteasy.examples.servlet; 21 | 22 | import jakarta.ws.rs.ApplicationPath; 23 | import jakarta.ws.rs.core.Application; 24 | 25 | @ApplicationPath("/app") 26 | public class MyApplication extends Application { 27 | } 28 | -------------------------------------------------------------------------------- /smime/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | "http.client" = "*" 8 | requests = "*" 9 | m2crypto = "*" 10 | 11 | [dev-packages] 12 | 13 | [requires] 14 | python_version = "3.8" 15 | -------------------------------------------------------------------------------- /smime/README.md: -------------------------------------------------------------------------------- 1 | # SMIME and Resteasy 2 | 3 | This example shows how to use SMIME over HTTP to encrypt and/or sign message bodies. The examples use keys and certificates 4 | generated by the 'openssl' command-line utility that is available on most unix-based systems as well as installable 5 | on Windows. If you're doing pure Java, then its fine to use the Java keytool and KeyStore facilities to manage your keys/certificates. 6 | The example also has python clients to show how different languages interacting. This is why we chose to use openssl 7 | to generate our keys/certificates. 8 | 9 | The pem files private.pem and cert.pem were generated with this command: 10 | 11 | ```bash 12 | $ openssl req -x509 -nodes -days 365 -newkey rsa:1024 -keyout private.pem -out cert.pem 13 | ``` 14 | 15 | There are two ways to run the example: 16 | 17 | ```bash 18 | $ mvn install 19 | ``` 20 | 21 | This builds, runs the WAR, and then invokes all the Java tests. This basically shows Java to Java communication 22 | passing back and forth SMIME messages over HTTP. 23 | 24 | The second way is to run the WAR and interact with Python clients: 25 | 26 | ```bash 27 | $ mvn jetty:run 28 | ``` 29 | 30 | This starts the web server in interaction mode. 31 | 32 | ## Run one of the python clients 33 | 34 | ```bash 35 | $ python getEncrypted.py 36 | $ python getSigned.py 37 | $ python postEncrypted.py 38 | $ python postSigned.py 39 | $ python getEncryptedSigned.py 40 | ``` 41 | 42 | 43 | -------------------------------------------------------------------------------- /smime/getEncrypted.py: -------------------------------------------------------------------------------- 1 | import http.client, urllib.parse 2 | from M2Crypto import BIO, SMIME, X509 3 | 4 | conn = http.client.HTTPConnection("localhost:9095") 5 | conn.request("GET", "/smime/encrypted") 6 | res = conn.getresponse() 7 | if res.status != 200: 8 | print((res.status)) 9 | raise Exception("Failed to connect") 10 | 11 | contentType = res.getheader("content-type") 12 | data = res.read() 13 | 14 | # Need to reconstruct a Mail message with content type 15 | # as SMIME wants it in that format 16 | bio = BIO.MemoryBuffer(b"Content-Type: ") 17 | bio.write(contentType) 18 | bio.write("\r\n\r\n") 19 | bio.write(data) 20 | 21 | s = SMIME.SMIME() 22 | s.load_key('src/main/resources/private.pem', 'src/main/resources/cert.pem') 23 | 24 | p7, d = SMIME.smime_load_pkcs7_bio(bio) 25 | out = s.decrypt(p7) 26 | 27 | print("--- Received Data ---") 28 | # It may contain headers like Content-Type, so you'll have to parse it 29 | print(out) -------------------------------------------------------------------------------- /smime/getEncryptedSigned.py: -------------------------------------------------------------------------------- 1 | import http.client, urllib.parse 2 | from M2Crypto import BIO, SMIME, X509 3 | 4 | conn = http.client.HTTPConnection("localhost:9095") 5 | conn.request("GET", "/smime/encrypted/signed") 6 | res = conn.getresponse() 7 | if res.status != 200: 8 | print((res.status)) 9 | raise Exception("Failed to connect") 10 | 11 | contentType = res.getheader("content-type") 12 | data = res.read() 13 | 14 | # Need to reconstruct a Mail message with content type 15 | # as SMIME wants it in that format 16 | bio = BIO.MemoryBuffer(b"Content-Type: ") 17 | bio.write(contentType) 18 | bio.write("\r\n\r\n") 19 | bio.write(data) 20 | 21 | s = SMIME.SMIME() 22 | s.load_key('src/main/resources/private.pem', 'src/main/resources/cert.pem') 23 | 24 | p7, d = SMIME.smime_load_pkcs7_bio(bio) 25 | out = s.decrypt(p7) 26 | 27 | 28 | 29 | # Load the signer's cert. 30 | x509 = X509.load_cert('src/main/resources/cert.pem') 31 | sk = X509.X509_Stack() 32 | sk.push(x509) 33 | s.set_x509_stack(sk) 34 | 35 | # Load the signer's CA cert. In this case, because the signer's 36 | # cert is self-signed, it is the signer's cert itself. 37 | st = X509.X509_Store() 38 | st.load_info('src/main/resources/cert.pem') 39 | s.set_x509_store(st) 40 | 41 | # Recall 'out' contains a PKCS #7 blob. 42 | # Transform 'out'; verify the resulting PKCS #7 blob. 43 | p7_bio = BIO.MemoryBuffer(out) 44 | p7, data = SMIME.smime_load_pkcs7_bio(p7_bio) 45 | v = s.verify(p7, data) 46 | 47 | print(v) 48 | 49 | -------------------------------------------------------------------------------- /smime/getSigned.py: -------------------------------------------------------------------------------- 1 | import http.client, urllib.parse 2 | from M2Crypto import BIO, SMIME, X509 3 | 4 | conn = http.client.HTTPConnection("localhost:9095") 5 | conn.request("GET", "/smime/signed") 6 | res = conn.getresponse() 7 | if res.status != 200: 8 | print((res.status)) 9 | raise Exception("Failed to connect") 10 | 11 | contentType = res.getheader("content-type") 12 | data = res.read() 13 | 14 | # Need to reconstruct a Mail message with content type 15 | # as SMIME wants it in that format 16 | bio = BIO.MemoryBuffer(b"Content-Type: ") 17 | bio.write(contentType) 18 | bio.write("\r\n\r\n") 19 | bio.write(data) 20 | 21 | s = SMIME.SMIME() 22 | 23 | # Load the signer's cert. 24 | x509 = X509.load_cert('src/main/resources/cert.pem') 25 | sk = X509.X509_Stack() 26 | sk.push(x509) 27 | s.set_x509_stack(sk) 28 | 29 | # Load the signer's CA cert. In this case, because the signer's 30 | # cert is self-signed, it is the signer's cert itself. 31 | st = X509.X509_Store() 32 | st.load_info('src/main/resources/cert.pem') 33 | s.set_x509_store(st) 34 | 35 | # Load the data, verify it. 36 | p7, data = SMIME.smime_load_pkcs7_bio(bio) 37 | v = s.verify(p7, data) 38 | print(v) 39 | -------------------------------------------------------------------------------- /smime/postEncrypted.py: -------------------------------------------------------------------------------- 1 | import http.client, urllib.parse 2 | from M2Crypto import BIO, Rand, SMIME, X509 3 | 4 | # Make a MemoryBuffer of the message. 5 | buf = BIO.MemoryBuffer( 6 | b"""Content-Type: application/xml 7 | 8 | 9 | """) 10 | 11 | # Seed the PRNG. 12 | Rand.load_file('randpool.dat', -1) 13 | 14 | # Instantiate an SMIME object. 15 | s = SMIME.SMIME() 16 | 17 | # Load target cert to encrypt to. 18 | x509 = X509.load_cert('src/main/resources/cert.pem') 19 | sk = X509.X509_Stack() 20 | sk.push(x509) 21 | s.set_x509_stack(sk) 22 | 23 | # Set cipher: 3-key triple-DES in CBC mode. 24 | s.set_cipher(SMIME.Cipher('des_ede3_cbc')) 25 | 26 | # Encrypt the buffer. 27 | p7 = s.encrypt(buf) 28 | 29 | out = BIO.MemoryBuffer() 30 | s.write(out, p7) 31 | 32 | # Strip out junk, I can't figure out a better way to do this 33 | # This is kind of a hack, but I couldn't figure out how to just 34 | # get the body without the headers, or just send the BIO directly thru the HTTP connection 35 | # s.write(out, py) adds a bunch of headers and doesn't just output the body 36 | # so, I read 5 lines then use the rest of the buffer to initalize the string 37 | 38 | out.readline() 39 | out.readline() 40 | out.readline() 41 | out.readline() 42 | out.readline() 43 | o = out.read() 44 | 45 | # This is an alternative way to extract the body 46 | #p7.write(out) 47 | #o = out.read() 48 | #o = o.replace('-----BEGIN PKCS7-----', '') 49 | #o = o.replace('-----END PKCS7-----', '') 50 | #o = o.strip() 51 | 52 | # Finally send the message 53 | conn = http.client.HTTPConnection("localhost:9095") 54 | headers = {"Content-Disposition" : "attachment; filename=\"smime.p7m\"", 55 | "Content-Type" : "application/pkcs7-mime; smime-type=enveloped-data; name=\"smime.p7m\"", 56 | "Content-Transfer-Encoding" :"base64"} 57 | 58 | 59 | conn.request("POST", "/smime/encrypted", o, headers) 60 | res = conn.getresponse() 61 | print((res.status, res.reason)) 62 | 63 | -------------------------------------------------------------------------------- /smime/postSigned.py: -------------------------------------------------------------------------------- 1 | import http.client, urllib.parse 2 | import re 3 | from M2Crypto import BIO, Rand, SMIME, X509 4 | 5 | 6 | data = b"""Content-Type: application/xml 7 | 8 | 9 | """ 10 | # Make a MemoryBuffer of the message. 11 | 12 | 13 | buf = BIO.MemoryBuffer(data) 14 | 15 | # Seed the PRNG. 16 | Rand.load_file('randpool.dat', -1) 17 | 18 | # Instantiate an SMIME object. 19 | s = SMIME.SMIME() 20 | s.load_key('src/main/resources/private.pem', 'src/main/resources/cert.pem') 21 | p7 = s.sign(buf, SMIME.PKCS7_DETACHED) 22 | 23 | out = BIO.MemoryBuffer() 24 | buf = BIO.MemoryBuffer(data) 25 | s.write(out, p7, buf) 26 | 27 | # Extract the content-type and multipart message. I can't figure out a better way to do this 28 | # This is kind of a hack, but I couldn't figure out how to just 29 | # get the body and headers separately or just send the BIO directly thru the HTTP connection 30 | l = out.readline() 31 | l = out.readline() 32 | 33 | result = re.match(b'Content-Type: (.*)', l) 34 | contentType = result.group(1) 35 | 36 | l = out.readline() 37 | l = out.readline() 38 | 39 | o = out.read() 40 | 41 | # Finally send the message 42 | conn = http.client.HTTPConnection("localhost:9095") 43 | headers = {"Content-Type" : contentType} 44 | 45 | 46 | conn.request("POST", "/smime/signed", o, headers) 47 | res = conn.getresponse() 48 | print((res.status, res.reason)) 49 | 50 | -------------------------------------------------------------------------------- /smime/src/main/java/dev/resteasy/example/smime/Customer.java: -------------------------------------------------------------------------------- 1 | package dev.resteasy.example.smime; 2 | 3 | import jakarta.xml.bind.annotation.XmlAttribute; 4 | import jakarta.xml.bind.annotation.XmlRootElement; 5 | 6 | /** 7 | * @author Bill Burke 8 | * @version $Revision: 1 $ 9 | */ 10 | @XmlRootElement(name = "customer") 11 | public class Customer { 12 | private String name; 13 | 14 | @XmlAttribute 15 | public String getName() { 16 | return name; 17 | } 18 | 19 | public void setName(String name) { 20 | this.name = name; 21 | } 22 | 23 | @Override 24 | public String toString() { 25 | return "Customer{" + 26 | "name='" + name + '\'' + 27 | '}'; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /smime/src/main/java/dev/resteasy/example/smime/SMIMEApplication.java: -------------------------------------------------------------------------------- 1 | package dev.resteasy.example.smime; 2 | 3 | import java.io.InputStream; 4 | import java.security.PrivateKey; 5 | import java.security.cert.X509Certificate; 6 | import java.util.HashSet; 7 | import java.util.Set; 8 | 9 | import jakarta.ws.rs.ApplicationPath; 10 | import jakarta.ws.rs.core.Application; 11 | 12 | import org.jboss.resteasy.security.PemUtils; 13 | 14 | /** 15 | * @author Bill Burke 16 | * @version $Revision: 1 $ 17 | */ 18 | @ApplicationPath("/") 19 | public class SMIMEApplication extends Application { 20 | private final Set resources = new HashSet<>(); 21 | 22 | public SMIMEApplication() throws Exception { 23 | InputStream privatePem = Thread.currentThread().getContextClassLoader().getResourceAsStream("private.pem"); 24 | PrivateKey privateKey = PemUtils.decodePrivateKey(privatePem); 25 | 26 | InputStream certPem = Thread.currentThread().getContextClassLoader().getResourceAsStream("cert.pem"); 27 | X509Certificate cert = PemUtils.decodeCertificate(certPem); 28 | 29 | SMIMEResource resource = new SMIMEResource(privateKey, cert); 30 | resources.add(resource); 31 | } 32 | 33 | @Override 34 | public Set getSingletons() { 35 | return resources; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /smime/src/main/java/dev/resteasy/example/smime/SMIMEResource.java: -------------------------------------------------------------------------------- 1 | package dev.resteasy.example.smime; 2 | 3 | import java.security.PrivateKey; 4 | import java.security.cert.X509Certificate; 5 | 6 | import jakarta.ws.rs.GET; 7 | import jakarta.ws.rs.POST; 8 | import jakarta.ws.rs.Path; 9 | import jakarta.ws.rs.Produces; 10 | import jakarta.ws.rs.WebApplicationException; 11 | import jakarta.ws.rs.core.MediaType; 12 | 13 | import org.jboss.resteasy.security.smime.EnvelopedInput; 14 | import org.jboss.resteasy.security.smime.EnvelopedOutput; 15 | import org.jboss.resteasy.security.smime.SignedInput; 16 | import org.jboss.resteasy.security.smime.SignedOutput; 17 | 18 | /** 19 | * @author Bill Burke 20 | * @version $Revision: 1 $ 21 | */ 22 | @Path("/smime") 23 | public class SMIMEResource { 24 | private final PrivateKey privateKey; 25 | private final X509Certificate certificate; 26 | 27 | public SMIMEResource(PrivateKey privateKey, X509Certificate certificate) { 28 | this.privateKey = privateKey; 29 | this.certificate = certificate; 30 | } 31 | 32 | @Path("encrypted") 33 | @GET 34 | public EnvelopedOutput getEncrypted() { 35 | System.out.println("HERE!!!!!"); 36 | Customer cust = new Customer(); 37 | cust.setName("Bill"); 38 | 39 | EnvelopedOutput output = new EnvelopedOutput(cust, MediaType.APPLICATION_XML_TYPE); 40 | output.setCertificate(certificate); 41 | return output; 42 | } 43 | 44 | @Path("encrypted") 45 | @POST 46 | public void postEncrypted(EnvelopedInput input) { 47 | Customer cust = input.getEntity(privateKey, certificate); 48 | System.out.println("Encrypted Server Input: "); 49 | System.out.println(cust); 50 | } 51 | 52 | @Path("signed") 53 | @GET 54 | @Produces("multipart/signed") 55 | public SignedOutput getSigned() { 56 | Customer cust = new Customer(); 57 | cust.setName("Bill"); 58 | 59 | SignedOutput output = new SignedOutput(cust, MediaType.APPLICATION_XML_TYPE); 60 | output.setPrivateKey(privateKey); 61 | output.setCertificate(certificate); 62 | return output; 63 | } 64 | 65 | @Path("signed") 66 | @POST 67 | public void postSigned(SignedInput input) throws Exception { 68 | Customer cust = input.getEntity(); 69 | System.out.println("Signed Server Input: "); 70 | System.out.println(cust); 71 | if (!input.verify(certificate)) { 72 | throw new WebApplicationException(500); 73 | } 74 | } 75 | 76 | @Path("/encrypted/signed") 77 | @GET 78 | public EnvelopedOutput getEncryptedSigned() { 79 | Customer cust = new Customer(); 80 | cust.setName("Bill"); 81 | 82 | SignedOutput signed = new SignedOutput(cust, MediaType.APPLICATION_XML_TYPE); 83 | signed.setCertificate(certificate); 84 | signed.setPrivateKey(privateKey); 85 | 86 | EnvelopedOutput output = new EnvelopedOutput(signed, "multipart/signed"); 87 | output.setCertificate(certificate); 88 | return output; 89 | } 90 | 91 | @Path("/encrypted/signed") 92 | @POST 93 | public void postEncryptedSigned(EnvelopedInput> input) throws Exception { 94 | SignedInput signed = input.getEntity(privateKey, certificate); 95 | Customer cust = signed.getEntity(); 96 | System.out.println("Encrypted and Signed Server Input: "); 97 | System.out.println(cust); 98 | if (!signed.verify(certificate)) { 99 | throw new WebApplicationException(500); 100 | } 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /smime/src/main/resources/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICsDCCAhmgAwIBAgIJANSmb/YlKw8RMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV 3 | BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX 4 | aWRnaXRzIFB0eSBMdGQwHhcNMTEwODI5MTcxNDM0WhcNMTIwODI4MTcxNDM0WjBF 5 | MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 6 | ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB 7 | gQDPAmm0r64Oa6nod1GaO35gbVhnaYjOEyf1fCUjmQJigjOrDBGejViYO/UuOMeD 8 | kPNtKsrmQu6svNtBojIW/Up06tWdX9W88/OsCUYU1MkZkCqK3p2ARtlJyMMn4vh6 9 | exKHPf/GURdzik1zbWqyEfdT1dD+896/oStld5uF0U0P6wIDAQABo4GnMIGkMB0G 10 | A1UdDgQWBBQOEp03rLlfr+nTrJoxBgyuHGpM9TB1BgNVHSMEbjBsgBQOEp03rLlf 11 | r+nTrJoxBgyuHGpM9aFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUt 12 | U3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJANSmb/Yl 13 | Kw8RMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAmtlnUEA2C/HYrBTw 14 | Tf1cApfno3wWrerc5Mzbon5zhsZGWuy52k60xQgev0qJ+rNKe88R/97lXy8NqUsN 15 | 7coLKBMfz+UC/ZZpK6JF7D3csFzIkHJeg/OpWAdkQ7yLd23d9DhL0bTGST5bDv4n 16 | Jxet/cBLVC1ayvXvz8CTDdT39As= 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /smime/src/main/resources/private.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXgIBAAKBgQDPAmm0r64Oa6nod1GaO35gbVhnaYjOEyf1fCUjmQJigjOrDBGe 3 | jViYO/UuOMeDkPNtKsrmQu6svNtBojIW/Up06tWdX9W88/OsCUYU1MkZkCqK3p2A 4 | RtlJyMMn4vh6exKHPf/GURdzik1zbWqyEfdT1dD+896/oStld5uF0U0P6wIDAQAB 5 | AoGAD7RzKI4bemSYo1ZKDpVs5QvmpRHQmzHA2lLszmKRV7/hA50KySiYDBSgaT1N 6 | haG9lg6lDgsN6HT7fWM8wZm9yDX0CcgY46V1+PTxAHvr1O+DEzeI4pqkCOhP2eYT 7 | FyAbybb2/0+u5naXhdZ39Z1dkc5v/IGOFdNFUYwpxg+2OMECQQD3LnestcIcXJ91 8 | 1Uq/vIs41eF4GYOrtvnegddMqBmh/yVqtI8jKEPiKIuxxn6unKb84cAC+UUNsNSk 9 | HY7DMtDTAkEA1mUMxbqp/MjD7ipHqo4OelecSoArYb49+rE9VZf9AHf/2xVYMyzc 10 | lK6qdlk6u/TFWwE32/YxZJDEPPmg494ViQJBAIcLFwz/vE48eE4rOoxoWPOzPlyc 11 | K+i7reXXsCXMTTzJJcsXXIBLtlKv7ioxFPMW2NCeEYZOY2CodHQ5Rc71S0sCQQCP 12 | UxZqyRMCrqRs/07Rsg3zs8YD+BMIUMnWYc6C/gxg19fveiFwdLkxnGLQo24kYMne 13 | wL27CqaSeET4DlmEe98hAkEAqyya8kh12OAErORdmnx3jXR1TJWIz48ojonITyeq 14 | 82/bMQaiVYpg/CsDYI2chVAmBvX7jKEVrdlYIl4cMsm0hA== 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /smime/src/test/java/dev/resteasy/example/smime/SMIMETest.java: -------------------------------------------------------------------------------- 1 | package dev.resteasy.example.smime; 2 | 3 | import java.io.InputStream; 4 | import java.net.URI; 5 | import java.security.PrivateKey; 6 | import java.security.cert.X509Certificate; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | import jakarta.ws.rs.client.Client; 10 | import jakarta.ws.rs.client.ClientBuilder; 11 | import jakarta.ws.rs.client.Entity; 12 | import jakarta.ws.rs.client.WebTarget; 13 | import jakarta.ws.rs.core.GenericType; 14 | import jakarta.ws.rs.core.Response; 15 | import jakarta.ws.rs.core.UriBuilder; 16 | 17 | import org.jboss.arquillian.container.test.api.Deployment; 18 | import org.jboss.arquillian.container.test.api.RunAsClient; 19 | import org.jboss.arquillian.junit5.ArquillianExtension; 20 | import org.jboss.arquillian.test.api.ArquillianResource; 21 | import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder; 22 | import org.jboss.resteasy.security.PemUtils; 23 | import org.jboss.resteasy.security.smime.EnvelopedInput; 24 | import org.jboss.resteasy.security.smime.EnvelopedOutput; 25 | import org.jboss.resteasy.security.smime.SignedInput; 26 | import org.jboss.resteasy.security.smime.SignedOutput; 27 | import org.jboss.shrinkwrap.api.ShrinkWrap; 28 | import org.jboss.shrinkwrap.api.asset.StringAsset; 29 | import org.jboss.shrinkwrap.api.spec.WebArchive; 30 | import org.junit.jupiter.api.AfterAll; 31 | import org.junit.jupiter.api.Assertions; 32 | import org.junit.jupiter.api.BeforeAll; 33 | import org.junit.jupiter.api.Test; 34 | import org.junit.jupiter.api.extension.ExtendWith; 35 | 36 | /** 37 | * @author Bill Burke 38 | * @version $Revision: 1 $ 39 | */ 40 | @ExtendWith(ArquillianExtension.class) 41 | @RunAsClient 42 | public class SMIMETest { 43 | private static PrivateKey privateKey; 44 | private static X509Certificate cert; 45 | private static Client client; 46 | 47 | @ArquillianResource 48 | private URI uri; 49 | 50 | @Deployment 51 | public static WebArchive deployment() { 52 | return ShrinkWrap.create(WebArchive.class, SMIMETest.class.getSimpleName() + ".war") 53 | .addClasses(Customer.class, SMIMEApplication.class, SMIMEResource.class) 54 | .addAsWebInfResource(SMIMETest.class.getResource("/cert.pem"), "classes/cert.pem") 55 | .addAsWebInfResource(SMIMETest.class.getResource("/private.pem"), "classes/private.pem") 56 | // Required until WFLY-13917 is resolved 57 | .addAsManifestResource(new StringAsset("Dependencies: org.bouncycastle import\n"), "MANIFEST.MF"); 58 | } 59 | 60 | @BeforeAll 61 | public static void setup() throws Exception { 62 | InputStream certPem = Thread.currentThread().getContextClassLoader().getResourceAsStream("cert.pem"); 63 | Assertions.assertNotNull(certPem); 64 | cert = PemUtils.decodeCertificate(certPem); 65 | 66 | InputStream privatePem = Thread.currentThread().getContextClassLoader().getResourceAsStream("private.pem"); 67 | privateKey = PemUtils.decodePrivateKey(privatePem); 68 | client = ((ResteasyClientBuilder) (ClientBuilder.newBuilder())).connectTimeout(120, TimeUnit.SECONDS) 69 | .readTimeout(120, TimeUnit.SECONDS).build(); 70 | } 71 | 72 | @AfterAll 73 | public static void shutdown() throws Exception { 74 | client.close(); 75 | } 76 | 77 | @Test 78 | public void testEncryptedGet() throws Exception { 79 | WebTarget target = client.target(generateUri("smime/encrypted")); 80 | EnvelopedInput input = target.request().get(EnvelopedInput.class); 81 | Customer cust = (Customer) input.getEntity(Customer.class, privateKey, cert); 82 | System.out.println("Encrypted Message From Server:"); 83 | System.out.println(cust); 84 | } 85 | 86 | @Test 87 | public void testEncryptedPost() throws Exception { 88 | WebTarget target = client.target(generateUri("smime/encrypted")); 89 | Customer cust = new Customer(); 90 | cust.setName("Bill"); 91 | EnvelopedOutput output = new EnvelopedOutput(cust, "application/xml"); 92 | output.setCertificate(cert); 93 | Response res = target.request().post(Entity.entity(output, "application/pkcs7-mime")); 94 | Assertions.assertEquals(204, res.getStatus()); 95 | res.close(); 96 | 97 | } 98 | 99 | @Test 100 | public void testSigned() throws Exception { 101 | WebTarget target = client.target(generateUri("smime/signed")); 102 | SignedInput input = target.request().get(SignedInput.class); 103 | Customer cust = (Customer) input.getEntity(Customer.class); 104 | System.out.println("Signed Message From Server: "); 105 | System.out.println(cust); 106 | input.verify(cert); 107 | 108 | } 109 | 110 | @Test 111 | public void testSignedPost() throws Exception { 112 | WebTarget target = client.target(generateUri("smime/signed")); 113 | Customer cust = new Customer(); 114 | cust.setName("Bill"); 115 | SignedOutput output = new SignedOutput(cust, "application/xml"); 116 | output.setPrivateKey(privateKey); 117 | output.setCertificate(cert); 118 | Response res = target.request().post(Entity.entity(output, "multipart/signed")); 119 | Assertions.assertEquals(204, res.getStatus()); 120 | res.close(); 121 | } 122 | 123 | @Test 124 | public void testEncryptedAndSignedGet() throws Exception { 125 | WebTarget target = client.target(generateUri("smime/encrypted/signed")); 126 | EnvelopedInput enveloped = target.request().get(new GenericType<>() { 127 | }); 128 | SignedInput signed = enveloped.getEntity(SignedInput.class, privateKey, cert); 129 | Customer cust = signed.getEntity(Customer.class); 130 | System.out.println(cust); 131 | Assertions.assertTrue(signed.verify(cert)); 132 | } 133 | 134 | @Test 135 | public void testEncryptedSignedPost() throws Exception { 136 | WebTarget target = client.target(generateUri("smime/encrypted/signed")); 137 | Customer cust = new Customer(); 138 | cust.setName("Bill"); 139 | SignedOutput signed = new SignedOutput(cust, "application/xml"); 140 | signed.setPrivateKey(privateKey); 141 | signed.setCertificate(cert); 142 | EnvelopedOutput output = new EnvelopedOutput(signed, "multipart/signed"); 143 | output.setCertificate(cert); 144 | Response res = target.request().post(Entity.entity(output, "application/pkcs7-mime")); 145 | Assertions.assertEquals(204, res.getStatus()); 146 | res.close(); 147 | } 148 | 149 | private UriBuilder generateUri(final String path) { 150 | return UriBuilder.fromUri(uri).path(path); 151 | } 152 | 153 | } 154 | -------------------------------------------------------------------------------- /smime/src/test/resources/arquillian.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 23 | 24 | 25 | 26 | ${jboss.home} 27 | ${jboss.server.config.file.name:standalone.xml} 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /standalone-multipart/README.adoc: -------------------------------------------------------------------------------- 1 | = Standalone multipart/form-data Example 2 | 3 | In https://jakarta.ee/specifications/restful-ws/3.1/[Jakarta RESTful Web Services 3.1] the `SeBootstrap` API was 4 | introduced as well as the `EntityPart` API for multipart data. This example shows how to use these API's with RESTEasy. 5 | 6 | == Building 7 | 8 | To build the `standalone-multipart` quickstart you must have https://maven.apache.org/[Maven] installed and at least 9 | Java 11. Then you simply need to run the following: 10 | 11 | ---- 12 | mvn clean verify 13 | ---- 14 | 15 | This will create a `standalone-multipart.jar` which can be executed from the command line. A test is also executed as 16 | part of the build. 17 | 18 | == Running the Quickstart 19 | 20 | The `standalone-multipart.jar` created can be executed from the command. 21 | 22 | ---- 23 | java -jar target/standalone-multipart.jar 24 | ---- 25 | 26 | This will start an Undertow container with RESTEasy and CDI support. Then make a multipart/form-data request and print 27 | the results of the request. You should end up seeing something like: 28 | 29 | --- 30 | Container running at http://localhost:8081/ 31 | OK 32 | {"name":"RESTEasy","data":"test content","entity":"entity-part"} 33 | --- -------------------------------------------------------------------------------- /standalone-multipart/src/main/java/dev/resteasy/examples/multipart/Main.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JBoss, Home of Professional Open Source. 3 | * 4 | * Copyright 2023 Red Hat, Inc., and individual contributors 5 | * as indicated by the @author tags. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * 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, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package dev.resteasy.examples.multipart; 21 | 22 | import java.nio.charset.StandardCharsets; 23 | import java.util.List; 24 | 25 | import jakarta.ws.rs.SeBootstrap; 26 | import jakarta.ws.rs.client.Client; 27 | import jakarta.ws.rs.client.ClientBuilder; 28 | import jakarta.ws.rs.client.Entity; 29 | import jakarta.ws.rs.core.EntityPart; 30 | import jakarta.ws.rs.core.GenericEntity; 31 | import jakarta.ws.rs.core.MediaType; 32 | import jakarta.ws.rs.core.Response; 33 | 34 | /** 35 | * An entry point for starting a REST container 36 | * 37 | * @author James R. Perkins 38 | */ 39 | public class Main { 40 | 41 | public static void main(final String[] args) throws Exception { 42 | System.setProperty("java.util.logging.manager", "org.jboss.logmanager.LogManager"); 43 | 44 | // Start the container 45 | final SeBootstrap.Instance instance = SeBootstrap.start(RestActivator.class) 46 | .thenApply(i -> { 47 | System.out.printf("Container running at %s%n", i.configuration().baseUri()); 48 | return i; 49 | }).toCompletableFuture().get(); 50 | 51 | // Create the client and make a multipart/form-data request 52 | try (Client client = ClientBuilder.newClient()) { 53 | // Create the entity parts for the request 54 | final List multipart = List.of( 55 | EntityPart.withName("name") 56 | .content("RESTEasy") 57 | .mediaType(MediaType.TEXT_PLAIN_TYPE) 58 | .build(), 59 | EntityPart.withName("entity") 60 | .content("entity-part") 61 | .mediaType(MediaType.TEXT_PLAIN_TYPE) 62 | .build(), 63 | EntityPart.withName("data") 64 | .content("test content".getBytes(StandardCharsets.UTF_8)) 65 | .mediaType(MediaType.APPLICATION_OCTET_STREAM_TYPE) 66 | .build()); 67 | try ( 68 | Response response = client.target(instance.configuration().baseUriBuilder().path("/api/upload")) 69 | .request(MediaType.APPLICATION_JSON_TYPE) 70 | .post(Entity.entity(new GenericEntity<>(multipart) { 71 | }, MediaType.MULTIPART_FORM_DATA_TYPE))) { 72 | printResponse(response); 73 | } 74 | } 75 | } 76 | 77 | private static void printResponse(final Response response) { 78 | System.out.println(response.getStatusInfo()); 79 | System.out.println(response.readEntity(String.class)); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /standalone-multipart/src/main/java/dev/resteasy/examples/multipart/RestActivator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JBoss, Home of Professional Open Source. 3 | * 4 | * Copyright 2023 Red Hat, Inc., and individual contributors 5 | * as indicated by the @author tags. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * 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, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package dev.resteasy.examples.multipart; 21 | 22 | import jakarta.enterprise.inject.Vetoed; 23 | import jakarta.servlet.annotation.MultipartConfig; 24 | import jakarta.ws.rs.ApplicationPath; 25 | import jakarta.ws.rs.core.Application; 26 | 27 | /** 28 | * Activates the REST application. 29 | * 30 | * @author James R. Perkins 31 | */ 32 | @ApplicationPath("/api") 33 | // Currently required to enable multipart/form-data in Undertow see https://issues.redhat.com/browse/RESTEASY-3376 34 | @MultipartConfig 35 | // See https://issues.redhat.com/browse/RESTEASY-3376 36 | @Vetoed 37 | public class RestActivator extends Application { 38 | } 39 | -------------------------------------------------------------------------------- /standalone-multipart/src/main/java/dev/resteasy/examples/multipart/UploadResource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JBoss, Home of Professional Open Source. 3 | * 4 | * Copyright 2023 Red Hat, Inc., and individual contributors 5 | * as indicated by the @author tags. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * 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, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package dev.resteasy.examples.multipart; 21 | 22 | import java.io.IOException; 23 | import java.io.InputStream; 24 | 25 | import jakarta.json.Json; 26 | import jakarta.json.JsonObjectBuilder; 27 | import jakarta.ws.rs.Consumes; 28 | import jakarta.ws.rs.FormParam; 29 | import jakarta.ws.rs.POST; 30 | import jakarta.ws.rs.Path; 31 | import jakarta.ws.rs.Produces; 32 | import jakarta.ws.rs.ServerErrorException; 33 | import jakarta.ws.rs.core.EntityPart; 34 | import jakarta.ws.rs.core.MediaType; 35 | import jakarta.ws.rs.core.Response; 36 | 37 | /** 38 | * A simple resource for creating a greeting. 39 | * 40 | * @author James R. Perkins 41 | */ 42 | @Path("/") 43 | public class UploadResource { 44 | 45 | @POST 46 | @Path("upload") 47 | @Consumes(MediaType.MULTIPART_FORM_DATA) 48 | @Produces(MediaType.APPLICATION_JSON) 49 | public Response upload(@FormParam("name") final String name, @FormParam("data") final InputStream data, 50 | @FormParam("entity") final EntityPart entityPart) { 51 | final JsonObjectBuilder builder = Json.createObjectBuilder(); 52 | builder.add("name", name); 53 | 54 | // Read the data into a string 55 | try (data) { 56 | builder.add("data", new String(data.readAllBytes())); 57 | } catch (IOException e) { 58 | throw new ServerErrorException("Failed to read data " + data, Response.Status.BAD_REQUEST); 59 | } 60 | try { 61 | builder.add(entityPart.getName(), entityPart.getContent(String.class)); 62 | } catch (IOException e) { 63 | throw new ServerErrorException("Failed to read entity " + entityPart, Response.Status.BAD_REQUEST); 64 | } 65 | return Response.ok(builder.build()).build(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /standalone-multipart/src/main/resources/META-INF/beans.xml: -------------------------------------------------------------------------------- 1 | 19 | 20 | 23 | -------------------------------------------------------------------------------- /standalone-multipart/src/main/resources/logging.properties: -------------------------------------------------------------------------------- 1 | # 2 | # JBoss, Home of Professional Open Source. 3 | # 4 | # Copyright 2022 Red Hat, Inc., and individual contributors 5 | # as indicated by the @author tags. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # 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, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | loggers=org.jboss.resteasy 21 | 22 | logger.level=INFO 23 | logger.handlers=CONSOLE 24 | 25 | logger.org.jboss.resteasy.level=${log.level:INFO} 26 | 27 | handler.CONSOLE=org.jboss.logmanager.handlers.ConsoleHandler 28 | handler.CONSOLE.formatter=COLOR-PATTERN 29 | handler.CONSOLE.properties=autoFlush,target 30 | handler.CONSOLE.autoFlush=true 31 | handler.CONSOLE.target=SYSTEM_OUT 32 | 33 | formatter.COLOR-PATTERN=org.jboss.logmanager.formatters.ColorPatternFormatter 34 | formatter.COLOR-PATTERN.properties=pattern 35 | formatter.COLOR-PATTERN.pattern=%d{HH\:mm\:ss,SSS} %-5p [%c] (%t) %s%e%n -------------------------------------------------------------------------------- /standalone-multipart/src/test/java/dev/resteasy/examples/multipart/UploadTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JBoss, Home of Professional Open Source. 3 | * 4 | * Copyright 2023 Red Hat, Inc., and individual contributors 5 | * as indicated by the @author tags. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * 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, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package dev.resteasy.examples.multipart; 21 | 22 | import java.nio.charset.StandardCharsets; 23 | import java.util.List; 24 | import java.util.concurrent.TimeUnit; 25 | 26 | import jakarta.ws.rs.SeBootstrap; 27 | import jakarta.ws.rs.client.Client; 28 | import jakarta.ws.rs.client.ClientBuilder; 29 | import jakarta.ws.rs.client.Entity; 30 | import jakarta.ws.rs.core.EntityPart; 31 | import jakarta.ws.rs.core.GenericEntity; 32 | import jakarta.ws.rs.core.MediaType; 33 | import jakarta.ws.rs.core.Response; 34 | 35 | import org.junit.jupiter.api.AfterAll; 36 | import org.junit.jupiter.api.Assertions; 37 | import org.junit.jupiter.api.BeforeAll; 38 | import org.junit.jupiter.api.Test; 39 | 40 | /** 41 | * @author James R. Perkins 42 | */ 43 | public class UploadTestCase { 44 | 45 | private static SeBootstrap.Instance INSTANCE; 46 | 47 | @BeforeAll 48 | public static void startInstance() throws Exception { 49 | INSTANCE = SeBootstrap.start(RestActivator.class) 50 | .toCompletableFuture().get(10, TimeUnit.SECONDS); 51 | Assertions.assertNotNull(INSTANCE, "Failed to start instance"); 52 | } 53 | 54 | @AfterAll 55 | public static void stopInstance() throws Exception { 56 | if (INSTANCE != null) { 57 | INSTANCE.stop() 58 | .toCompletableFuture() 59 | .get(10, TimeUnit.SECONDS); 60 | } 61 | } 62 | 63 | @Test 64 | public void upload() throws Exception { 65 | try (Client client = ClientBuilder.newClient()) { 66 | final List multipart = List.of( 67 | EntityPart.withName("name") 68 | .content("RESTEasy") 69 | .mediaType(MediaType.TEXT_PLAIN_TYPE) 70 | .build(), 71 | EntityPart.withName("data") 72 | .content("test content".getBytes(StandardCharsets.UTF_8)) 73 | .mediaType(MediaType.APPLICATION_OCTET_STREAM_TYPE) 74 | .build(), 75 | EntityPart.withName("entity") 76 | .content("entity-data") 77 | .mediaType(MediaType.TEXT_PLAIN_TYPE) 78 | .build()); 79 | try ( 80 | Response response = client.target(INSTANCE.configuration().baseUriBuilder().path("api/upload")) 81 | .request(MediaType.APPLICATION_JSON) 82 | .post(Entity.entity(new GenericEntity<>(multipart) { 83 | }, MediaType.MULTIPART_FORM_DATA))) { 84 | Assertions.assertEquals(Response.Status.OK, response.getStatusInfo()); 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /tracing-example/README.adoc: -------------------------------------------------------------------------------- 1 | = RESTEasy Tracing Example 2 | 3 | This is a demonstration of the resteasy tracing feature. 4 | 5 | To run the example, you can simply run it with WildFly: 6 | 7 | [source,bash] 8 | ---- 9 | $ mvn wildfly:dev 10 | ---- 11 | 12 | == Usage 13 | 14 | After server started, we can access the server and get the tracing info: 15 | 16 | === Command Line 17 | [source,bash] 18 | ---- 19 | $ curl -i http://localhost:8080/tracing-example/trace/level 20 | ---- 21 | 22 | And here is the sample output: 23 | 24 | [source,txt] 25 | ---- 26 | HTTP/1.1 200 OK 27 | X-RESTEasy-Tracing-180: org.jboss.resteasy.plugins.server.servlet.Servlet3AsyncHttpRequest@5c54713c MATCH [ ---- / 90377.40 ms | ---- %] Resource instance: [org.jboss.resteasy.core.ResourceMethodInvoker @15a29762] 28 | X-RESTEasy-Tracing-181: org.jboss.resteasy.plugins.server.servlet.Servlet3AsyncHttpRequest@5c54713c MATCH [ ---- / 90377.43 ms | ---- %] Matched method : public java.lang.String dev.resteasy.tracing.examples.TracingConfigResource.level(jakarta.ws.rs.core.Configuration) 29 | X-RESTEasy-Tracing-182: org.jboss.resteasy.plugins.server.servlet.Servlet3AsyncHttpRequest@5c54713c REQ-FILTER [ 0.00 / 90377.47 ms | 0.00 %] Filter by [org.jboss.resteasy.plugins.providers.jsonp.JsonpPatchMethodFilter @39f08fcd #2147483646] 30 | X-RESTEasy-Tracing-183: org.jboss.resteasy.plugins.server.servlet.Servlet3AsyncHttpRequest@5c54713c REQ-FILTER [ 0.00 / 90377.48 ms | 0.00 %] Filter by [org.jboss.resteasy.plugins.providers.jackson.PatchMethodFilter @5116d6ba #2147483647] 31 | X-RESTEasy-Tracing-184: org.jboss.resteasy.plugins.server.servlet.Servlet3AsyncHttpRequest@5c54713c REQ-FILTER [ 0.00 / 90377.49 ms | 0.00 %] Filter by [org.jboss.resteasy.plugins.providers.sse.SseEventSinkInterceptor @7ce628d4 #2147483647] 32 | X-RESTEasy-Tracing-185: org.jboss.resteasy.plugins.server.servlet.Servlet3AsyncHttpRequest@5c54713c REQ-FILTER [ 0.03 / 90377.49 ms | 0.00 %] Request summary: 3 filters 33 | X-RESTEasy-Tracing-186: org.jboss.resteasy.plugins.server.servlet.Servlet3AsyncHttpRequest@5c54713c INVOKE [ 0.26 / 90377.73 ms | 0.00 %] Resource [org.jboss.resteasy.plugins.server.resourcefactory.POJOResourceFactory @306e6620] method=[public java.lang.String dev.resteasy.tracing.examples.TracingConfigResource.level(jakarta.ws.rs.core.Configuration)] 34 | X-RESTEasy-Tracing-187: org.jboss.resteasy.plugins.server.servlet.Servlet3AsyncHttpRequest@5c54713c INVOKE [ ---- / 90377.85 ms | ---- %] Response: [org.jboss.resteasy.specimpl.BuiltResponse @131c9fd9 <200/SUCCESSFUL|OK|java.lang.String @5f955399>] 35 | X-RESTEasy-Tracing-188: org.jboss.resteasy.plugins.server.servlet.Servlet3AsyncHttpRequest@5c54713c RESP-FILTER [ 0.00 / 90377.99 ms | 0.00 %] Filter by [org.jboss.resteasy.security.doseta.DigitalSigningInterceptor @7f267bb2] 36 | X-RESTEasy-Tracing-189: org.jboss.resteasy.plugins.server.servlet.Servlet3AsyncHttpRequest@5c54713c RESP-FILTER [ 0.00 / 90378.00 ms | 0.00 %] Filter by [org.jboss.resteasy.plugins.interceptors.MessageSanitizerContainerResponseFilter @49b110a3 #4000] 37 | X-RESTEasy-Tracing-190: org.jboss.resteasy.plugins.server.servlet.Servlet3AsyncHttpRequest@5c54713c MBW [ ---- / 90378.04 ms | ---- %] Find MBW for type=[java.lang.String] genericType=[java.lang.String] mediaType=[[jakarta.ws.rs.core.MediaType @45b9c1ef]] annotations=[@jakarta.ws.rs.GET(), @jakarta.ws.rs.Path(value="/level")] 38 | ---- 39 | 40 | Above is the basic usage of the sample. You should also see output on the console WildFly is running in as well. 41 | 42 | === Web 43 | 44 | Navigate to http://localhost:8080/tracing-example. From there you should see a web page which can be used to execute 45 | HTTP requests through a client returning the tracing headers. -------------------------------------------------------------------------------- /tracing-example/src/main/java/dev/resteasy/examples/tracing/TraceMethodResource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JBoss, Home of Professional Open Source. 3 | * 4 | * Copyright 2023 Red Hat, Inc., and individual contributors 5 | * as indicated by the @author tags. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * 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, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package dev.resteasy.examples.tracing; 21 | 22 | import jakarta.enterprise.context.RequestScoped; 23 | import jakarta.ws.rs.Consumes; 24 | import jakarta.ws.rs.GET; 25 | import jakarta.ws.rs.POST; 26 | import jakarta.ws.rs.PUT; 27 | import jakarta.ws.rs.Path; 28 | import jakarta.ws.rs.Produces; 29 | import jakarta.ws.rs.core.MediaType; 30 | 31 | /** 32 | * @author James R. Perkins 33 | */ 34 | @Path("/headers") 35 | @RequestScoped 36 | @Consumes(MediaType.TEXT_PLAIN) 37 | @Produces(MediaType.TEXT_PLAIN) 38 | public class TraceMethodResource { 39 | 40 | @GET 41 | @Path("get") 42 | public String get() { 43 | return "GET trace"; 44 | } 45 | 46 | @POST 47 | @Path("post") 48 | public String post(final String value) { 49 | return String.format("POST trace: %s", value); 50 | } 51 | 52 | @PUT 53 | @Path("put") 54 | public String put(final String value) { 55 | return String.format("PUT trace: %s", value); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tracing-example/src/main/java/dev/resteasy/examples/tracing/TracingApp.java: -------------------------------------------------------------------------------- 1 | package dev.resteasy.examples.tracing; 2 | 3 | import jakarta.ws.rs.ApplicationPath; 4 | import jakarta.ws.rs.core.Application; 5 | 6 | @ApplicationPath("/trace") 7 | public class TracingApp extends Application { 8 | } 9 | -------------------------------------------------------------------------------- /tracing-example/src/main/java/dev/resteasy/examples/tracing/TracingConfigResource.java: -------------------------------------------------------------------------------- 1 | package dev.resteasy.examples.tracing; 2 | 3 | import jakarta.servlet.http.HttpServletRequest; 4 | import jakarta.ws.rs.GET; 5 | import jakarta.ws.rs.Path; 6 | import jakarta.ws.rs.core.Configuration; 7 | import jakarta.ws.rs.core.Context; 8 | 9 | import org.jboss.resteasy.tracing.RESTEasyTracingLogger; 10 | import org.jboss.resteasy.tracing.api.RESTEasyTracing; 11 | 12 | @Path("/") 13 | public class TracingConfigResource { 14 | 15 | @GET 16 | @Path("/type") 17 | public String type(@Context Configuration config) { 18 | return RESTEasyTracingLogger.getTracingConfig(config); 19 | } 20 | 21 | @GET 22 | @Path("/level") 23 | public String level(@Context Configuration config) { 24 | return RESTEasyTracingLogger.getTracingThreshold(config); 25 | } 26 | 27 | @GET 28 | @Path("/logger") 29 | public String logger(@Context HttpServletRequest request) { 30 | RESTEasyTracingLogger logger = (RESTEasyTracingLogger) request.getAttribute(RESTEasyTracing.PROPERTY_NAME); 31 | if (logger == null) { 32 | return ""; 33 | } else { 34 | return RESTEasyTracingLogger.class.getName(); 35 | } 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /tracing-example/src/main/webapp/WEB-INF/beans.xml: -------------------------------------------------------------------------------- 1 | 19 | 20 | -------------------------------------------------------------------------------- /tracing-example/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | tracing-example 7 | 8 | resteasy.server.tracing.type 9 | ALL 10 | 11 | 12 | resteasy.server.tracing.threshold 13 | VERBOSE 14 | 15 | -------------------------------------------------------------------------------- /tracing-example/src/main/webapp/index.html: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | Contacts 26 | 27 | 29 | 33 | 34 | 35 | 36 | 41 |
42 |
43 |
44 | 45 |
46 |
47 |
48 | Choose the HTTP method to invoke: 49 |
50 | 51 | 52 |
53 |
54 | 55 | 56 |
57 |
58 | 59 | 60 |
61 |
62 |
63 | 68 | 73 |
74 |
75 |
76 |
77 |
78 | HTTP Method: 79 |
80 |
81 | Invoked: 82 |
83 |
84 | Response: 85 |
86 |
87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 |
Header NameHeader Value
98 |
99 | 108 | 109 | 110 | 113 | -------------------------------------------------------------------------------- /tracing-example/src/main/webapp/resources/main.js: -------------------------------------------------------------------------------- 1 | /* 2 | * JBoss, Home of Professional Open Source. 3 | * 4 | * Copyright 2023 Red Hat, Inc., and individual contributors 5 | * as indicated by the @author tags. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * 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, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | (() => { 21 | "use strict"; 22 | 23 | window.addEventListener("load", () => { 24 | // Enable tooltips 25 | const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]'); 26 | [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl, { 27 | trigger: "hover", 28 | delay: 500 29 | })); 30 | // Add the functions to the buttons 31 | const submitButton = document.querySelector("#submit"); 32 | submitButton.addEventListener("click", () => { 33 | const radioButtons = document.querySelectorAll("input[name='httpMethod']"); 34 | let httpMethod; 35 | for (const radioButton of radioButtons) { 36 | if (radioButton.checked) { 37 | httpMethod = radioButton.value; 38 | break; 39 | } 40 | } 41 | loadTable(httpMethod); 42 | }); 43 | const clearButton = document.querySelector("#clear"); 44 | clearButton.addEventListener("click", () => { 45 | // Clear the elements we want to be reset 46 | const link = document.querySelector("#url"); 47 | clearElement(link); 48 | link.classList.remove("placeholder", "col-5", "bg-secondary"); 49 | clearElement(document.querySelector("#sentHttpMethod")); 50 | clearElement(document.querySelector("#response")); 51 | clearElement(document.querySelector("#output")); 52 | }); 53 | }); 54 | 55 | function loadTable(httpMethod) { 56 | const link = document.querySelector("#url"); 57 | document.querySelector("#sentHttpMethod").textContent = httpMethod.toUpperCase(); 58 | clearElement(link); 59 | link.classList.add("placeholder", "col-5", "bg-secondary"); 60 | 61 | // Get the table body for the output 62 | const tbody = document.querySelector("#output"); 63 | // Clear the table 64 | clearElement(tbody); 65 | // Add a temporary row to the table to indicate we are loading 66 | const trTemp = tbody.insertRow(); 67 | const col1Temp = trTemp.insertCell(); 68 | const spinnerDiv = document.createElement("div"); 69 | spinnerDiv.classList.add("spinner-border"); 70 | spinnerDiv.setAttribute("role", "status"); 71 | const spinner = document.createElement("span"); 72 | spinner.classList.add("visually-hidden"); 73 | spinner.textContent = "Loading..."; 74 | spinnerDiv.append(spinner); 75 | col1Temp.append(spinnerDiv); 76 | trTemp.insertCell(); 77 | 78 | const url = "trace/headers/" + httpMethod; 79 | // Create potential data 80 | let config = {}; 81 | if (httpMethod === "post" || httpMethod === "put") { 82 | config = { 83 | method: httpMethod.toUpperCase(), 84 | body: `${httpMethod} data` 85 | }; 86 | } 87 | fetch(url, config) 88 | .then((r) => { 89 | // Create a link to the resource we just invoked 90 | const a = document.createElement("a"); 91 | a.setAttribute("href", r.url); 92 | a.setAttribute("target", "_"); 93 | a.textContent = r.url; 94 | link.classList.remove("placeholder", "col-5", "bg-secondary"); 95 | link.append(a); 96 | r.text().then((text) => { 97 | clearElement(tbody); 98 | document.querySelector("#response").textContent = text; 99 | // Add a table entry for each header value 100 | const headers = r.headers; 101 | for (let header of headers.entries()) { 102 | const tr = tbody.insertRow(); 103 | const name = tr.insertCell(); 104 | name.textContent = header[0]; 105 | const value = tr.insertCell(); 106 | value.textContent = header[1]; 107 | } 108 | }); 109 | }).catch(err => { 110 | error("Request has failed: " + err); 111 | }); 112 | } 113 | 114 | /** 115 | * Clears the children of the element. 116 | * @param e the element to clear 117 | */ 118 | function clearElement(e) { 119 | while (e.firstChild) { 120 | e.removeChild(e.firstChild); 121 | } 122 | } 123 | 124 | function success(message) { 125 | showAlert(message); 126 | } 127 | 128 | function error(message) { 129 | showAlert(message, "danger"); 130 | } 131 | 132 | function showAlert(message, type = "success", autoHide = "true") { 133 | const alertPlaceholder = document.querySelector("#liveAlertPlaceholder"); 134 | const alert = document.querySelector("#alert").content.cloneNode(true).querySelector("div.toast"); 135 | alert.setAttribute("data-bs-autohide", autoHide); 136 | alert.classList.add("text-bg-" + type); 137 | const body = alert.querySelector(".toast-body"); 138 | body.textContent = message; 139 | alert.addEventListener("hidden.bs.toast", () => { 140 | alertPlaceholder.removeChild(alert); 141 | }); 142 | const toast = new bootstrap.Toast(alert); 143 | toast.show(); 144 | alertPlaceholder.append(alert); 145 | } 146 | })() -------------------------------------------------------------------------------- /tracing-example/src/test/java/dev/resteasy/examples/tracing/TracingTest.java: -------------------------------------------------------------------------------- 1 | package dev.resteasy.examples.tracing; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.net.URI; 6 | 7 | import jakarta.ws.rs.client.Client; 8 | import jakarta.ws.rs.client.ClientBuilder; 9 | import jakarta.ws.rs.client.WebTarget; 10 | import jakarta.ws.rs.core.UriBuilder; 11 | 12 | import org.jboss.arquillian.container.test.api.Deployment; 13 | import org.jboss.arquillian.container.test.api.RunAsClient; 14 | import org.jboss.arquillian.junit5.ArquillianExtension; 15 | import org.jboss.arquillian.test.api.ArquillianResource; 16 | import org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters; 17 | import org.jboss.resteasy.tracing.RESTEasyTracingLogger; 18 | import org.jboss.shrinkwrap.api.ShrinkWrap; 19 | import org.jboss.shrinkwrap.api.asset.StringAsset; 20 | import org.jboss.shrinkwrap.api.spec.WebArchive; 21 | import org.junit.jupiter.api.Test; 22 | import org.junit.jupiter.api.extension.ExtendWith; 23 | 24 | /** 25 | * @author James R. Perkins 26 | */ 27 | @ExtendWith(ArquillianExtension.class) 28 | @RunAsClient 29 | public class TracingTest { 30 | 31 | @ArquillianResource 32 | private URI uri; 33 | 34 | @Deployment 35 | public static WebArchive deployment() { 36 | return ShrinkWrap.create(WebArchive.class, TracingTest.class.getSimpleName() + ".war") 37 | .addClasses(TracingApp.class, TracingConfigResource.class) 38 | .addAsWebInfResource(new StringAsset("\n" + 39 | "\n" + 44 | " tracing-example\n" + 45 | " \n" + 46 | " resteasy.server.tracing.type\n" + 47 | " ALL\n" + 48 | " \n" + 49 | " \n" + 50 | " resteasy.server.tracing.threshold\n" + 51 | " VERBOSE\n" + 52 | " \n" + 53 | ""), "web.xml"); 54 | } 55 | 56 | @Test 57 | public void basicTest() { 58 | try (Client client = ClientBuilder.newClient()) { 59 | WebTarget target = client.target(uriBuilder().path("trace/type")); 60 | assertEquals(ResteasyContextParameters.RESTEASY_TRACING_TYPE_ALL, target.request().get(String.class)); 61 | 62 | target = client.target(uriBuilder().path("trace/level")); 63 | assertEquals(ResteasyContextParameters.RESTEASY_TRACING_LEVEL_VERBOSE, target.request().get(String.class)); 64 | 65 | target = client.target(uriBuilder().path("trace/logger")); 66 | assertEquals(RESTEasyTracingLogger.class.getName(), target.request().get(String.class)); 67 | } 68 | } 69 | 70 | private UriBuilder uriBuilder() { 71 | return UriBuilder.fromUri(uri); 72 | } 73 | } 74 | --------------------------------------------------------------------------------