diff --git a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java index 7f671a296366f..73d84c0735487 100644 --- a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java +++ b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java @@ -17,6 +17,7 @@ import java.io.Closeable; import java.io.File; +import java.io.InputStream; import java.lang.reflect.Modifier; import java.nio.file.Path; import java.util.AbstractMap; @@ -1649,6 +1650,9 @@ private void handleMultipartField(String formParamName, String partType, String } else if (type.equals(Path.class.getName())) { // and so is path addFile(ifValueNotNull, multipartForm, formParamName, partType, partFilename, fieldValue); + } else if (type.equals(InputStream.class.getName())) { + // and so is path + addInputStream(ifValueNotNull, multipartForm, formParamName, partType, partFilename, fieldValue, type); } else if (type.equals(Buffer.class.getName())) { // and buffer addBuffer(ifValueNotNull, multipartForm, formParamName, partType, partFilename, fieldValue, errorLocation); @@ -1663,8 +1667,12 @@ private void handleMultipartField(String formParamName, String partType, String fieldValue); addBuffer(ifValueNotNull, multipartForm, formParamName, partType, partFilename, buffer, errorLocation); } else if (parameterGenericType.equals(MULTI_BYTE_SIGNATURE)) { - addMultiAsFile(ifValueNotNull, multipartForm, formParamName, partType, fieldValue, errorLocation); + addMultiAsFile(ifValueNotNull, multipartForm, formParamName, partType, partFilename, fieldValue, errorLocation); } else if (partType != null) { + if (partFilename != null) { + log.warnf("Using the @PartFilename annotation is unsupported on the type '%s'. Problematic field is: '%s'", + partType, formParamName); + } // assume POJO: addPojo(ifValueNotNull, multipartForm, formParamName, partType, fieldValue, type); } else { @@ -1677,6 +1685,17 @@ private void handleMultipartField(String formParamName, String partType, String } } + private void addInputStream(BytecodeCreator methodCreator, AssignableResultHandle multipartForm, String formParamName, + String partType, String partFilename, ResultHandle fieldValue, String type) { + methodCreator.assign(multipartForm, + methodCreator.invokeVirtualMethod(MethodDescriptor.ofMethod(QuarkusMultipartForm.class, "entity", + QuarkusMultipartForm.class, String.class, String.class, Object.class, String.class, Class.class), + multipartForm, methodCreator.load(formParamName), methodCreator.load(partFilename), fieldValue, + methodCreator.load(partType), + // FIXME: doesn't support generics + methodCreator.loadClassFromTCCL(type))); + } + private void addPojo(BytecodeCreator methodCreator, AssignableResultHandle multipartForm, String formParamName, String partType, ResultHandle fieldValue, String type) { methodCreator.assign(multipartForm, @@ -1780,12 +1799,17 @@ private void addString(BytecodeCreator methodCreator, AssignableResultHandle mul } private void addMultiAsFile(BytecodeCreator methodCreator, AssignableResultHandle multipartForm, String formParamName, - String partType, + String partType, String partFilename, ResultHandle multi, String errorLocation) { // they all default to plain/text except buffers/byte[]/Multi/File/Path if (partType == null) { partType = MediaType.APPLICATION_OCTET_STREAM; } + String filename = partFilename; + if (filename == null) { + filename = formParamName; + } + if (partType.equalsIgnoreCase(MediaType.APPLICATION_OCTET_STREAM)) { methodCreator.assign(multipartForm, // MultipartForm#binaryFileUpload(String name, String filename, Multi content, String mediaType); @@ -1794,7 +1818,7 @@ private void addMultiAsFile(BytecodeCreator methodCreator, AssignableResultHandl MethodDescriptor.ofMethod(QuarkusMultipartForm.class, "multiAsBinaryFileUpload", QuarkusMultipartForm.class, String.class, String.class, Multi.class, String.class), - multipartForm, methodCreator.load(formParamName), methodCreator.load(formParamName), + multipartForm, methodCreator.load(formParamName), methodCreator.load(filename), multi, methodCreator.load(partType))); } else { methodCreator.assign(multipartForm, @@ -1804,7 +1828,7 @@ private void addMultiAsFile(BytecodeCreator methodCreator, AssignableResultHandl MethodDescriptor.ofMethod(QuarkusMultipartForm.class, "multiAsTextFileUpload", QuarkusMultipartForm.class, String.class, String.class, Multi.class, String.class), - multipartForm, methodCreator.load(formParamName), methodCreator.load(formParamName), + multipartForm, methodCreator.load(formParamName), methodCreator.load(filename), multi, methodCreator.load(partType))); } } diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/multipart/MultipartFilenameTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/multipart/MultipartFilenameTest.java index e4e8aa704f800..74c00adfe85d3 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/multipart/MultipartFilenameTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/multipart/MultipartFilenameTest.java @@ -3,6 +3,7 @@ import static org.assertj.core.api.Assertions.assertThat; import java.io.BufferedReader; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -10,6 +11,7 @@ import java.net.URI; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.util.ArrayList; import java.util.stream.Collectors; import jakarta.enterprise.context.ApplicationScoped; @@ -29,6 +31,7 @@ import io.quarkus.test.QuarkusUnitTest; import io.quarkus.test.common.http.TestHTTPResource; +import io.smallrye.mutiny.Multi; public class MultipartFilenameTest { @@ -72,6 +75,39 @@ void shouldUseFileNameFromAnnotationUsingString() { assertThat(client.postMultipartWithPartFilenameUsingString(form)).isEqualTo("clientFile:file content"); } + @Test + void shouldUseFileNameFromAnnotationUsingByteArray() { + Client client = RestClientBuilder.newBuilder().baseUri(baseUri).build(Client.class); + + ClientFormUsingByteArray form = new ClientFormUsingByteArray(); + form.file = "file content".getBytes(StandardCharsets.UTF_8); + assertThat(client.postMultipartWithPartFilenameUsingByteArray(form)).isEqualTo("clientFile:file content"); + } + + @Test + void shouldUseFileNameFromAnnotationUsingInputStream() { + Client client = RestClientBuilder.newBuilder().baseUri(baseUri).build(Client.class); + + ClientFormUsingInputStream form = new ClientFormUsingInputStream(); + form.file = new ByteArrayInputStream("file content".getBytes(StandardCharsets.UTF_8)); + assertThat(client.postMultipartWithPartFilenameUsingInputStream(form)).isEqualTo("clientFile:file content"); + } + + @Test + void shouldUseFileNameFromAnnotationUsingMultiByte() { + Client client = RestClientBuilder.newBuilder().baseUri(baseUri).build(Client.class); + + var list = new ArrayList(); + var array = "file content".getBytes(StandardCharsets.UTF_8); + for (var b : array) { + list.add(b); + } + + ClientFormUsingMultiByte form = new ClientFormUsingMultiByte(); + form.file = Multi.createFrom().items(list.stream()); + assertThat(client.postMultipartWithPartFilenameUsingMultiByte(form)).isEqualTo("clientFile:file content"); + } + @Test void shouldCopyFileContentToString() throws IOException { Client client = RestClientBuilder.newBuilder().baseUri(baseUri).build(Client.class); @@ -121,7 +157,7 @@ public String upload(@MultipartForm FormData form) { } @POST - @Path("/using-string") + @Path("/using-form-data") @Consumes(MediaType.MULTIPART_FORM_DATA) public String uploadWithFileContentUsingString(@MultipartForm FormData form) throws IOException { return form.myFile.fileName() + ":" + Files.readString(form.myFile.uploadedFile()); @@ -185,10 +221,25 @@ public interface Client { String postMultipartWithPartFilename(@MultipartForm ClientFormUsingFile clientForm); @POST - @Path("/using-string") + @Path("/using-form-data") @Consumes(MediaType.MULTIPART_FORM_DATA) String postMultipartWithPartFilenameUsingString(@MultipartForm ClientFormUsingString clientForm); + @POST + @Path("/using-form-data") + @Consumes(MediaType.MULTIPART_FORM_DATA) + String postMultipartWithPartFilenameUsingByteArray(@MultipartForm ClientFormUsingByteArray clientForm); + + @POST + @Path("/using-form-data") + @Consumes(MediaType.MULTIPART_FORM_DATA) + String postMultipartWithPartFilenameUsingInputStream(@MultipartForm ClientFormUsingInputStream clientForm); + + @POST + @Path("/using-form-data") + @Consumes(MediaType.MULTIPART_FORM_DATA) + String postMultipartWithPartFilenameUsingMultiByte(@MultipartForm ClientFormUsingMultiByte clientForm); + @POST @Path("/file-content") @Consumes(MediaType.MULTIPART_FORM_DATA) @@ -228,4 +279,31 @@ public static class ClientFormUsingString { @PartFilename(FILE_NAME) public String file; } + + public static class ClientFormUsingByteArray { + public static final String FILE_NAME = "clientFile"; + + @FormParam("myFile") + @PartType(MediaType.APPLICATION_OCTET_STREAM) + @PartFilename(FILE_NAME) + public byte[] file; + } + + public static class ClientFormUsingInputStream { + public static final String FILE_NAME = "clientFile"; + + @FormParam("myFile") + @PartType(MediaType.APPLICATION_OCTET_STREAM) + @PartFilename(FILE_NAME) + public InputStream file; + } + + public static class ClientFormUsingMultiByte { + public static final String FILE_NAME = "clientFile"; + + @FormParam("myFile") + @PartType(MediaType.APPLICATION_OCTET_STREAM) + @PartFilename(FILE_NAME) + public Multi file; + } } diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/multipart/QuarkusMultipartForm.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/multipart/QuarkusMultipartForm.java index 2a082ffa09edb..36ce207fcaba2 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/multipart/QuarkusMultipartForm.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/multipart/QuarkusMultipartForm.java @@ -50,7 +50,11 @@ public QuarkusMultipartForm attribute(String name, String value, String filename } public QuarkusMultipartForm entity(String name, Object entity, String mediaType, Class type) { - pojos.add(new PojoFieldData(name, entity, mediaType, type, parts.size())); + return entity(name, null, entity, mediaType, type); + } + + public QuarkusMultipartForm entity(String name, String filename, Object entity, String mediaType, Class type) { + pojos.add(new PojoFieldData(name, filename, entity, mediaType, type, parts.size())); parts.add(null); // make place for ^ return this; } @@ -132,19 +136,22 @@ public void preparePojos(RestClientRequestContext context) throws IOException { break; } } - parts.set(pojo.position, new QuarkusMultipartFormDataPart(pojo.name, value, pojo.mediaType, pojo.type)); + parts.set(pojo.position, + new QuarkusMultipartFormDataPart(pojo.name, pojo.filename, value, pojo.mediaType, pojo.type)); } } public static class PojoFieldData { private final String name; + private final String filename; private final Object entity; private final String mediaType; private final Class type; private final int position; - public PojoFieldData(String name, Object entity, String mediaType, Class type, int position) { + public PojoFieldData(String name, String filename, Object entity, String mediaType, Class type, int position) { this.name = name; + this.filename = filename; this.entity = entity; this.mediaType = mediaType; this.type = type; diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/multipart/QuarkusMultipartFormDataPart.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/multipart/QuarkusMultipartFormDataPart.java index c5f8b9e70e338..64b46f9f3f6b6 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/multipart/QuarkusMultipartFormDataPart.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/multipart/QuarkusMultipartFormDataPart.java @@ -20,7 +20,12 @@ public class QuarkusMultipartFormDataPart { private final Multi multiByteContent; public QuarkusMultipartFormDataPart(String name, Buffer content, String mediaType, Class type) { + this(name, null, content, mediaType, type); + } + + public QuarkusMultipartFormDataPart(String name, String filename, Buffer content, String mediaType, Class type) { this.name = name; + this.filename = filename; this.content = content; this.mediaType = mediaType; this.type = type; @@ -37,7 +42,6 @@ public QuarkusMultipartFormDataPart(String name, Buffer content, String mediaTyp } this.isObject = true; this.value = null; - this.filename = null; this.pathname = null; this.text = false; } diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/multipart/QuarkusMultipartFormUpload.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/multipart/QuarkusMultipartFormUpload.java index 03514b349e572..21bb2a23db394 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/multipart/QuarkusMultipartFormUpload.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/multipart/QuarkusMultipartFormUpload.java @@ -68,8 +68,12 @@ public FileUpload createFileUpload(HttpRequest request, String name, String file if (formDataPart.isAttribute()) { encoder.addBodyAttribute(formDataPart.name(), formDataPart.value()); } else if (formDataPart.isObject()) { - MemoryFileUpload data = new MemoryFileUpload(formDataPart.name(), "", formDataPart.mediaType(), - formDataPart.isText() ? null : "binary", null, formDataPart.content().length()); + MemoryFileUpload data = new MemoryFileUpload(formDataPart.name(), + formDataPart.filename() != null ? formDataPart.filename() : "", + formDataPart.mediaType(), + formDataPart.isText() ? null : "binary", + null, + formDataPart.content().length()); data.setContent(formDataPart.content().getByteBuf()); encoder.addBodyHttpData(data); } else if (formDataPart.multiByteContent() != null) {