From 9191f3bc8272f0956b9f7433b7339ba9a5d7305a Mon Sep 17 00:00:00 2001 From: "James R. Perkins" Date: Wed, 23 Aug 2023 14:59:37 -0700 Subject: [PATCH] Add a standalone multipart example. Signed-off-by: James R. Perkins --- pom.xml | 3 +- standalone-multipart/pom.xml | 172 ++++++++++++++++++ .../dev/resteasy/examples/multipart/Main.java | 83 +++++++++ .../examples/multipart/RestActivator.java | 38 ++++ .../examples/multipart/UploadResource.java | 67 +++++++ .../src/main/resources/META-INF/beans.xml | 23 +++ .../src/main/resources/logging.properties | 35 ++++ .../examples/multipart/UploadTestCase.java | 88 +++++++++ 8 files changed, 508 insertions(+), 1 deletion(-) create mode 100644 standalone-multipart/pom.xml create mode 100644 standalone-multipart/src/main/java/dev/resteasy/examples/multipart/Main.java create mode 100644 standalone-multipart/src/main/java/dev/resteasy/examples/multipart/RestActivator.java create mode 100644 standalone-multipart/src/main/java/dev/resteasy/examples/multipart/UploadResource.java create mode 100644 standalone-multipart/src/main/resources/META-INF/beans.xml create mode 100644 standalone-multipart/src/main/resources/logging.properties create mode 100644 standalone-multipart/src/test/java/dev/resteasy/examples/multipart/UploadTestCase.java diff --git a/pom.xml b/pom.xml index f13bded7..623bc1cf 100644 --- a/pom.xml +++ b/pom.xml @@ -39,8 +39,9 @@ examples-jsapi json-binding microprofile-openapi + servlet-example smime + standalone-multipart tracing-example - servlet-example diff --git a/standalone-multipart/pom.xml b/standalone-multipart/pom.xml new file mode 100644 index 00000000..6ac076e0 --- /dev/null +++ b/standalone-multipart/pom.xml @@ -0,0 +1,172 @@ + + + + + 4.0.0 + + dev.resteasy.tools + resteasy-parent + 2.0.3.Final + + + + dev.resteasy.examples + standalone-multipart + 6.1.0.Final-SNAPSHOT + RESTEasy Quick Start: Standalone MultiPart Example + + + + 4.0.1 + 3.1.0 + 3.0.2.Final + 6.2.5.Final + 5.10.0 + + + 1.2.3 + + + + + + org.jboss.resteasy + resteasy-bom + ${version.org.jboss.resteasy} + pom + import + + + org.junit + junit-bom + ${version.org.junit} + pom + import + + + + + + + jakarta.ws.rs + jakarta.ws.rs-api + ${version.jakarta.ws.rs.api} + + + + org.jboss.logmanager + jboss-logmanager + ${version.org.jboss.logmanager} + + + + org.jboss.resteasy + resteasy-client + + + org.jboss.resteasy + resteasy-json-p-provider + + + org.jboss.resteasy + resteasy-multipart-provider + + + org.jboss.resteasy + resteasy-undertow-cdi + + + + + org.junit.jupiter + junit-jupiter + test + + + + + ${project.artifactId} + + + net.revelc.code.formatter + formatter-maven-plugin + + + net.revelc.code + impsort-maven-plugin + + + + org.jboss.jandex + jandex-maven-plugin + ${version.jandex.maven.plugin} + + + make-index + + jandex + + + + + + maven-jar-plugin + + + + dev.resteasy.examples.multipart.Main + dev.resteasy.quickstart.bootstrap + + + + + + maven-shade-plugin + + + + + module-info.class + + + + + + package + + shade + + + + + + maven-surefire-plugin + + + org.jboss.logmanager.LogManager + + + + + + + \ No newline at end of file diff --git a/standalone-multipart/src/main/java/dev/resteasy/examples/multipart/Main.java b/standalone-multipart/src/main/java/dev/resteasy/examples/multipart/Main.java new file mode 100644 index 00000000..be3cfe1e --- /dev/null +++ b/standalone-multipart/src/main/java/dev/resteasy/examples/multipart/Main.java @@ -0,0 +1,83 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2023 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.resteasy.examples.multipart; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +import jakarta.ws.rs.SeBootstrap; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.core.EntityPart; +import jakarta.ws.rs.core.GenericEntity; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + +/** + * An entry point for starting a REST container + * + * @author James R. Perkins + */ +public class Main { + + public static void main(final String[] args) throws Exception { + System.setProperty("java.util.logging.manager", "org.jboss.logmanager.LogManager"); + + // Start the container + final SeBootstrap.Instance instance = SeBootstrap.start(RestActivator.class) + .thenApply(i -> { + System.out.printf("Container running at %s%n", i.configuration().baseUri()); + return i; + }).toCompletableFuture().get(); + + // Create the client and make a multipart/form-data request + try (Client client = ClientBuilder.newClient()) { + // Create the entity parts for the request + final List multipart = List.of( + EntityPart.withName("name") + .content("RESTEasy") + .mediaType(MediaType.TEXT_PLAIN_TYPE) + .build(), + EntityPart.withName("entity") + .content("entity-part") + .mediaType(MediaType.TEXT_PLAIN_TYPE) + .build(), + EntityPart.withName("data") + .content("test content".getBytes(StandardCharsets.UTF_8)) + .mediaType(MediaType.APPLICATION_OCTET_STREAM_TYPE) + .build()); + try ( + Response response = client.target(instance.configuration().baseUriBuilder().path("/api/upload")) + .request(MediaType.APPLICATION_JSON_TYPE) + .post(Entity.entity(new GenericEntity<>(multipart) { + }, MediaType.MULTIPART_FORM_DATA_TYPE)) + ) { + printResponse(response); + } + } + } + + private static void printResponse(final Response response) { + System.out.println(response.getStatusInfo()); + System.out.println(response.readEntity(String.class)); + } + +} diff --git a/standalone-multipart/src/main/java/dev/resteasy/examples/multipart/RestActivator.java b/standalone-multipart/src/main/java/dev/resteasy/examples/multipart/RestActivator.java new file mode 100644 index 00000000..57a543c1 --- /dev/null +++ b/standalone-multipart/src/main/java/dev/resteasy/examples/multipart/RestActivator.java @@ -0,0 +1,38 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2023 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.resteasy.examples.multipart; + +import jakarta.enterprise.inject.Vetoed; +import jakarta.servlet.annotation.MultipartConfig; +import jakarta.ws.rs.ApplicationPath; +import jakarta.ws.rs.core.Application; + +/** + * Activates the REST application. + * + * @author James R. Perkins + */ +@ApplicationPath("/api") +// Currently required to enable multipart/form-data in Undertow see https://issues.redhat.com/browse/RESTEASY-3376 +@MultipartConfig +// See https://issues.redhat.com/browse/RESTEASY-3376 +@Vetoed +public class RestActivator extends Application { +} diff --git a/standalone-multipart/src/main/java/dev/resteasy/examples/multipart/UploadResource.java b/standalone-multipart/src/main/java/dev/resteasy/examples/multipart/UploadResource.java new file mode 100644 index 00000000..2f6c104f --- /dev/null +++ b/standalone-multipart/src/main/java/dev/resteasy/examples/multipart/UploadResource.java @@ -0,0 +1,67 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2023 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.resteasy.examples.multipart; + +import java.io.IOException; +import java.io.InputStream; + +import jakarta.json.Json; +import jakarta.json.JsonObjectBuilder; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.FormParam; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.ServerErrorException; +import jakarta.ws.rs.core.EntityPart; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + +/** + * A simple resource for creating a greeting. + * + * @author James R. Perkins + */ +@Path("/") +public class UploadResource { + + @POST + @Path("upload") + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Produces(MediaType.APPLICATION_JSON) + public Response upload(@FormParam("name") final String name, @FormParam("data") final InputStream data, + @FormParam("entity") final EntityPart entityPart) { + final JsonObjectBuilder builder = Json.createObjectBuilder(); + builder.add("name", name); + + // Read the data into a string + try (data) { + builder.add("data", new String(data.readAllBytes())); + } catch (IOException e) { + throw new ServerErrorException("Failed to read data " + data, Response.Status.BAD_REQUEST); + } + try { + builder.add(entityPart.getName(), entityPart.getContent(String.class)); + } catch (IOException e) { + throw new ServerErrorException("Failed to read entity " + entityPart, Response.Status.BAD_REQUEST); + } + return Response.ok(builder.build()).build(); + } +} diff --git a/standalone-multipart/src/main/resources/META-INF/beans.xml b/standalone-multipart/src/main/resources/META-INF/beans.xml new file mode 100644 index 00000000..acab26c3 --- /dev/null +++ b/standalone-multipart/src/main/resources/META-INF/beans.xml @@ -0,0 +1,23 @@ + + + + \ No newline at end of file diff --git a/standalone-multipart/src/main/resources/logging.properties b/standalone-multipart/src/main/resources/logging.properties new file mode 100644 index 00000000..c3c68703 --- /dev/null +++ b/standalone-multipart/src/main/resources/logging.properties @@ -0,0 +1,35 @@ +# +# JBoss, Home of Professional Open Source. +# +# Copyright 2022 Red Hat, Inc., and individual contributors +# as indicated by the @author tags. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +loggers=org.jboss.resteasy + +logger.level=INFO +logger.handlers=CONSOLE + +logger.org.jboss.resteasy.level=${log.level:INFO} + +handler.CONSOLE=org.jboss.logmanager.handlers.ConsoleHandler +handler.CONSOLE.formatter=COLOR-PATTERN +handler.CONSOLE.properties=autoFlush,target +handler.CONSOLE.autoFlush=true +handler.CONSOLE.target=SYSTEM_OUT + +formatter.COLOR-PATTERN=org.jboss.logmanager.formatters.ColorPatternFormatter +formatter.COLOR-PATTERN.properties=pattern +formatter.COLOR-PATTERN.pattern=%d{HH\:mm\:ss,SSS} %-5p [%c] (%t) %s%e%n \ No newline at end of file diff --git a/standalone-multipart/src/test/java/dev/resteasy/examples/multipart/UploadTestCase.java b/standalone-multipart/src/test/java/dev/resteasy/examples/multipart/UploadTestCase.java new file mode 100644 index 00000000..664c705b --- /dev/null +++ b/standalone-multipart/src/test/java/dev/resteasy/examples/multipart/UploadTestCase.java @@ -0,0 +1,88 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2023 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.resteasy.examples.multipart; + +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import jakarta.ws.rs.SeBootstrap; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.core.EntityPart; +import jakarta.ws.rs.core.GenericEntity; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * @author James R. Perkins + */ +public class UploadTestCase { + + private static SeBootstrap.Instance INSTANCE; + + @BeforeAll + public static void startInstance() throws Exception { + INSTANCE = SeBootstrap.start(RestActivator.class) + .toCompletableFuture().get(10, TimeUnit.SECONDS); + Assertions.assertNotNull(INSTANCE, "Failed to start instance"); + } + + @AfterAll + public static void stopInstance() throws Exception { + if (INSTANCE != null) { + INSTANCE.stop() + .toCompletableFuture() + .get(10, TimeUnit.SECONDS); + } + } + + @Test + public void upload() throws Exception { + try (Client client = ClientBuilder.newClient()) { + final List multipart = List.of( + EntityPart.withName("name") + .content("RESTEasy") + .mediaType(MediaType.TEXT_PLAIN_TYPE) + .build(), + EntityPart.withName("data") + .content("test content".getBytes(StandardCharsets.UTF_8)) + .mediaType(MediaType.APPLICATION_OCTET_STREAM_TYPE) + .build(), + EntityPart.withName("entity") + .content("entity-data") + .mediaType(MediaType.TEXT_PLAIN_TYPE) + .build()); + try ( + Response response = client.target(INSTANCE.configuration().baseUriBuilder().path("api/upload")) + .request(MediaType.APPLICATION_JSON) + .post(Entity.entity(new GenericEntity<>(multipart) { + }, MediaType.MULTIPART_FORM_DATA))) { + Assertions.assertEquals(Response.Status.OK, response.getStatusInfo()); + } + } + } +}