Skip to content

Commit e000c18

Browse files
authored
Merge pull request #31550 from FroMage/rr-multipart-file-as-normal
RESTEasy Reactive: treat multipart file input as regular input
2 parents f14cb40 + 914e3be commit e000c18

File tree

7 files changed

+209
-134
lines changed

7 files changed

+209
-134
lines changed

extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/FormData.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ public class FormData {
1717
@PartType(MediaType.APPLICATION_JSON)
1818
public Map<String, Object> map;
1919

20+
@RestForm
21+
@PartType(MediaType.APPLICATION_JSON)
22+
public Map<String, Object> map2;
23+
2024
@FormParam("names")
2125
@PartType(MediaType.TEXT_PLAIN)
2226
public List<String> names;
@@ -34,6 +38,11 @@ public class FormData {
3438
@Valid
3539
public Person person;
3640

41+
@RestForm
42+
@PartType(MediaType.APPLICATION_JSON)
43+
@Valid
44+
public Person person2;
45+
3746
@RestForm
3847
@PartType(MediaType.APPLICATION_JSON)
3948
public Person[] persons;
@@ -42,6 +51,14 @@ public class FormData {
4251
@PartType(MediaType.APPLICATION_JSON)
4352
public List<Person> persons2;
4453

54+
@RestForm
55+
@PartType(MediaType.APPLICATION_JSON)
56+
public Person[] persons3;
57+
58+
@RestForm
59+
@PartType(MediaType.APPLICATION_JSON)
60+
public List<Person> persons4;
61+
4562
@RestForm("htmlFile")
4663
private FileUpload htmlPart;
4764

extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MultipartResource.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ public class MultipartResource {
2929
@Path("/json")
3030
public Map<String, Object> greeting(@Valid @BeanParam FormData formData) {
3131
Map<String, Object> result = new HashMap<>(formData.map);
32+
result.putAll(formData.map2);
3233
result.put("person", formData.person);
34+
result.put("person2", formData.person2);
3335
result.put("htmlFileSize", formData.getHtmlPart().size());
3436
result.put("htmlFilePath", formData.getHtmlPart().uploadedFile().toAbsolutePath().toString());
3537
result.put("htmlFileContentType", formData.getHtmlPart().contentType());
@@ -38,6 +40,8 @@ public Map<String, Object> greeting(@Valid @BeanParam FormData formData) {
3840
result.put("numbers2", formData.numbers2);
3941
result.put("persons", formData.persons);
4042
result.put("persons2", formData.persons2);
43+
result.put("persons3", formData.persons3);
44+
result.put("persons4", formData.persons4);
4145
return result;
4246
}
4347

@@ -48,6 +52,7 @@ public Map<String, Object> greeting(@Valid @BeanParam FormData formData) {
4852
@Path("/param/json")
4953
public Map<String, Object> greeting(
5054
@RestForm @PartType(MediaType.APPLICATION_JSON) Map<String, Object> map,
55+
@RestForm @PartType(MediaType.APPLICATION_JSON) Map<String, Object> map2,
5156

5257
@FormParam("names") @PartType(MediaType.TEXT_PLAIN) List<String> names,
5358

@@ -56,14 +61,20 @@ public Map<String, Object> greeting(
5661
@RestForm @PartType(MediaType.TEXT_PLAIN) List<Integer> numbers2,
5762

5863
@RestForm @PartType(MediaType.APPLICATION_JSON) @Valid Person person,
64+
@RestForm @PartType(MediaType.APPLICATION_JSON) @Valid Person person2,
5965

6066
@RestForm @PartType(MediaType.APPLICATION_JSON) Person[] persons,
6167

6268
@RestForm @PartType(MediaType.APPLICATION_JSON) List<Person> persons2,
6369

70+
@RestForm @PartType(MediaType.APPLICATION_JSON) Person[] persons3,
71+
72+
@RestForm @PartType(MediaType.APPLICATION_JSON) List<Person> persons4,
6473
@RestForm("htmlFile") FileUpload htmlPart) {
6574
Map<String, Object> result = new HashMap<>(map);
75+
result.putAll(map2);
6676
result.put("person", person);
77+
result.put("person2", person2);
6778
result.put("htmlFileSize", htmlPart.size());
6879
result.put("htmlFilePath", htmlPart.uploadedFile().toAbsolutePath().toString());
6980
result.put("htmlFileContentType", htmlPart.contentType());
@@ -72,6 +83,8 @@ public Map<String, Object> greeting(
7283
result.put("numbers2", numbers2);
7384
result.put("persons", persons);
7485
result.put("persons2", persons2);
86+
result.put("persons3", persons3);
87+
result.put("persons4", persons4);
7588
return result;
7689
}
7790
}

extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MultipartTest.java

Lines changed: 38 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import io.quarkus.test.QuarkusUnitTest;
1818
import io.restassured.RestAssured;
19+
import io.restassured.builder.MultiPartSpecBuilder;
1920

2021
public class MultipartTest {
2122

@@ -36,6 +37,12 @@ public JavaArchive get() {
3637

3738
@Test
3839
public void testValid() throws IOException {
40+
testValid("/multipart/json");
41+
}
42+
43+
private void testValid(String url) throws IOException {
44+
// NOTE: the multipart file name is ignored for String types, so we must convert them
45+
// to byte[] in order to send a file name
3946
RestAssured.given()
4047
.multiPart("map",
4148
"{\n" +
@@ -44,7 +51,15 @@ public void testValid() throws IOException {
4451
" \"foo2\": \"bar2\"\n" +
4552
" }\n" +
4653
"}")
54+
.multiPart(new MultiPartSpecBuilder(("{\n" +
55+
" \"foo2\": \"bar\",\n" +
56+
" \"sub2\": {\n" +
57+
" \"foo2\": \"bar2\"\n" +
58+
" }\n" +
59+
"}").getBytes()).controlName("map2").fileName("something.js").build())
4760
.multiPart("person", "{\"first\": \"Bob\", \"last\": \"Builder\"}", "application/json")
61+
.multiPart(new MultiPartSpecBuilder("{\"first\": \"Bob\", \"last\": \"Builder\"}".getBytes())
62+
.controlName("person2").fileName("something.js").mimeType("application/json").build())
4863
.multiPart("htmlFile", HTML_FILE, "text/html")
4964
.multiPart("names", "name1")
5065
.multiPart("names", "name2")
@@ -56,15 +71,27 @@ public void testValid() throws IOException {
5671
.multiPart("persons", "{\"first\": \"First2\", \"last\": \"Last2\"}", "application/json")
5772
.multiPart("persons2", "{\"first\": \"First1\", \"last\": \"Last1\"}", "application/json")
5873
.multiPart("persons2", "{\"first\": \"First2\", \"last\": \"Last2\"}", "application/json")
74+
.multiPart(new MultiPartSpecBuilder("{\"first\": \"First1\", \"last\": \"Last1\"}".getBytes())
75+
.controlName("persons3").fileName("something.js").mimeType("application/json").build())
76+
.multiPart(new MultiPartSpecBuilder("{\"first\": \"First2\", \"last\": \"Last2\"}".getBytes())
77+
.controlName("persons3").fileName("something.js").mimeType("application/json").build())
78+
.multiPart(new MultiPartSpecBuilder("{\"first\": \"First1\", \"last\": \"Last1\"}".getBytes())
79+
.controlName("persons4").fileName("something.js").mimeType("application/json").build())
80+
.multiPart(new MultiPartSpecBuilder("{\"first\": \"First2\", \"last\": \"Last2\"}".getBytes())
81+
.controlName("persons4").fileName("something.js").mimeType("application/json").build())
5982
.accept("application/json")
6083
.when()
61-
.post("/multipart/json")
84+
.post(url)
6285
.then()
6386
.statusCode(200)
6487
.body("foo", equalTo("bar"))
6588
.body("sub.foo2", equalTo("bar2"))
89+
.body("foo2", equalTo("bar"))
90+
.body("sub2.foo2", equalTo("bar2"))
6691
.body("person.first", equalTo("Bob"))
6792
.body("person.last", equalTo("Builder"))
93+
.body("person2.first", equalTo("Bob"))
94+
.body("person2.last", equalTo("Builder"))
6895
.body("htmlFileSize", equalTo(Files.readAllBytes(HTML_FILE.toPath()).length))
6996
.body("htmlFilePath", not(equalTo(HTML_FILE.toPath().toAbsolutePath().toString())))
7097
.body("htmlFileContentType", equalTo("text/html"))
@@ -81,57 +108,20 @@ public void testValid() throws IOException {
81108
.body("persons2[0].first", equalTo("First1"))
82109
.body("persons2[0].last", equalTo("Last1"))
83110
.body("persons2[1].first", equalTo("First2"))
84-
.body("persons2[1].last", equalTo("Last2"));
111+
.body("persons2[1].last", equalTo("Last2"))
112+
.body("persons3[0].first", equalTo("First1"))
113+
.body("persons3[0].last", equalTo("Last1"))
114+
.body("persons3[1].first", equalTo("First2"))
115+
.body("persons3[1].last", equalTo("Last2"))
116+
.body("persons4[0].first", equalTo("First1"))
117+
.body("persons4[0].last", equalTo("Last1"))
118+
.body("persons4[1].first", equalTo("First2"))
119+
.body("persons4[1].last", equalTo("Last2"));
85120
}
86121

87122
@Test
88123
public void testValidParam() throws IOException {
89-
RestAssured.given()
90-
.multiPart("map",
91-
"{\n" +
92-
" \"foo\": \"bar\",\n" +
93-
" \"sub\": {\n" +
94-
" \"foo2\": \"bar2\"\n" +
95-
" }\n" +
96-
"}")
97-
.multiPart("person", "{\"first\": \"Bob\", \"last\": \"Builder\"}", "application/json")
98-
.multiPart("htmlFile", HTML_FILE, "text/html")
99-
.multiPart("names", "name1")
100-
.multiPart("names", "name2")
101-
.multiPart("numbers", 1)
102-
.multiPart("numbers", 2)
103-
.multiPart("numbers2", 1)
104-
.multiPart("numbers2", 2)
105-
.multiPart("persons", "{\"first\": \"First1\", \"last\": \"Last1\"}", "application/json")
106-
.multiPart("persons", "{\"first\": \"First2\", \"last\": \"Last2\"}", "application/json")
107-
.multiPart("persons2", "{\"first\": \"First1\", \"last\": \"Last1\"}", "application/json")
108-
.multiPart("persons2", "{\"first\": \"First2\", \"last\": \"Last2\"}", "application/json")
109-
.accept("application/json")
110-
.when()
111-
.post("/multipart/param/json")
112-
.then()
113-
.statusCode(200)
114-
.body("foo", equalTo("bar"))
115-
.body("sub.foo2", equalTo("bar2"))
116-
.body("person.first", equalTo("Bob"))
117-
.body("person.last", equalTo("Builder"))
118-
.body("htmlFileSize", equalTo(Files.readAllBytes(HTML_FILE.toPath()).length))
119-
.body("htmlFilePath", not(equalTo(HTML_FILE.toPath().toAbsolutePath().toString())))
120-
.body("htmlFileContentType", equalTo("text/html"))
121-
.body("names[0]", equalTo("name1"))
122-
.body("names[1]", equalTo("name2"))
123-
.body("numbers[0]", equalTo(1))
124-
.body("numbers[1]", equalTo(2))
125-
.body("numbers2[0]", equalTo(1))
126-
.body("numbers2[1]", equalTo(2))
127-
.body("persons[0].first", equalTo("First1"))
128-
.body("persons[0].last", equalTo("Last1"))
129-
.body("persons[1].first", equalTo("First2"))
130-
.body("persons[1].last", equalTo("Last2"))
131-
.body("persons2[0].first", equalTo("First1"))
132-
.body("persons2[0].last", equalTo("Last1"))
133-
.body("persons2[1].first", equalTo("First2"))
134-
.body("persons2[1].last", equalTo("Last2"));
124+
testValid("/multipart/param/json");
135125
}
136126

137127
@Test

extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartInputTest.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,22 @@ public void testSimple() {
8383

8484
// ensure that the 3 uploaded files where created on disk
8585
Assertions.assertEquals(3, uploadDir.toFile().listFiles().length);
86+
87+
// same with text as file
88+
RestAssured.given()
89+
.multiPart("name", "something.txt", "Alice".getBytes())
90+
.multiPart("active", "true")
91+
.multiPart("num", "25")
92+
.multiPart("status", "WORKING")
93+
.multiPart("htmlFile", HTML, "text/html")
94+
.multiPart("xmlFile", XML, "text/xml")
95+
.multiPart("txtFile", TXT, "text/plain")
96+
.accept("text/plain")
97+
.when()
98+
.post("/multipart/simple/2")
99+
.then()
100+
.statusCode(200)
101+
.body(equalTo("Alice - true - 50 - WORKING - true - true - true"));
86102
}
87103

88104
@Test
@@ -125,6 +141,22 @@ public void testSimpleParam() {
125141

126142
// ensure that the 3 uploaded files where created on disk
127143
Assertions.assertEquals(3, uploadDir.toFile().listFiles().length);
144+
145+
// same with text as file
146+
RestAssured.given()
147+
.multiPart("name", "something.txt", "Alice".getBytes())
148+
.multiPart("active", "true")
149+
.multiPart("num", "25")
150+
.multiPart("status", "WORKING")
151+
.multiPart("htmlFile", HTML, "text/html")
152+
.multiPart("xmlFile", XML, "text/xml")
153+
.multiPart("txtFile", TXT, "text/plain")
154+
.accept("text/plain")
155+
.when()
156+
.post("/multipart/param/simple/2")
157+
.then()
158+
.statusCode(200)
159+
.body(equalTo("Alice - true - 50 - WORKING - true - true - true"));
128160
}
129161

130162
@Test

independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/ClassInjectorTransformer.java

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -769,52 +769,38 @@ private void loadMultipartParameter(MethodVisitor injectMethod, FieldInfo fieldI
769769
case PartType:
770770
/*
771771
* if (single) {
772-
* String param = (String) context.getFormParameter(name, true, false);
773-
* return MultipartSupport.convertFormAttribute(param, typeClass, genericType, MediaType.valueOf(mimeType),
774-
* context,
775-
* name);
772+
* return MultipartSupport.getConvertedFormAttribute(name, typeClass, genericType,
773+
* MediaType.valueOf(mimeType),
774+
* context);
776775
* } else {
777-
* List<String> params = (List<String>) context.getFormParameter(name, false, false);
778-
* return MultipartSupport.convertFormAttributes(params, typeClass, genericType,
779-
* MediaType.valueOf(mimeType), context, name);
776+
* return MultipartSupport.getConvertedFormAttributes(name, typeClass, genericType,
777+
* MediaType.valueOf(mimeType),
778+
* context);
780779
* }
781780
*/
782-
// ctx param
783-
injectMethod.visitIntInsn(Opcodes.ALOAD, 1);
784781
// name
785782
injectMethod.visitLdcInsn(param.getName());
786-
// single
787-
injectMethod.visitLdcInsn(param.isSingle());
788-
// encoded
789-
injectMethod.visitLdcInsn(false);
790-
injectMethod.visitMethodInsn(Opcodes.INVOKEINTERFACE, QUARKUS_REST_INJECTION_CONTEXT_BINARY_NAME,
791-
"getFormParameter",
792-
"(Ljava/lang/String;ZZ)Ljava/lang/Object;", true);
793-
injectMethod.visitTypeInsn(Opcodes.CHECKCAST, param.isSingle() ? STRING_BINARY_NAME : LIST_BINARY_NAME);
794-
// class, generic type, media type, context, name
783+
// class, generic type, media type
795784
injectMethod.visitFieldInsn(Opcodes.GETSTATIC, this.thisName, fieldInfo.name() + "_type", CLASS_DESCRIPTOR);
796785
injectMethod.visitFieldInsn(Opcodes.GETSTATIC, this.thisName, fieldInfo.name() + "_genericType",
797786
TYPE_DESCRIPTOR);
798787
injectMethod.visitFieldInsn(Opcodes.GETSTATIC, this.thisName, fieldInfo.name() + "_mediaType",
799788
MEDIA_TYPE_DESCRIPTOR);
789+
// ctx param
800790
injectMethod.visitIntInsn(Opcodes.ALOAD, 1);
801791
injectMethod.visitTypeInsn(Opcodes.CHECKCAST, RESTEASY_REACTIVE_REQUEST_CONTEXT_BINARY_NAME);
802-
injectMethod.visitLdcInsn(param.getName());
803-
String firstParamDescriptor;
804792
String returnDescriptor;
805793
String methodName;
806794
if (param.isSingle()) {
807-
firstParamDescriptor = STRING_DESCRIPTOR;
808795
returnDescriptor = OBJECT_DESCRIPTOR;
809-
methodName = "convertFormAttribute";
796+
methodName = "getConvertedFormAttribute";
810797
} else {
811-
firstParamDescriptor = LIST_DESCRIPTOR;
812798
returnDescriptor = LIST_DESCRIPTOR;
813-
methodName = "convertFormAttributes";
799+
methodName = "getConvertedFormAttributes";
814800
}
815801
injectMethod.visitMethodInsn(Opcodes.INVOKESTATIC, MULTIPART_SUPPORT_BINARY_NAME, methodName,
816-
"(" + firstParamDescriptor + CLASS_DESCRIPTOR + TYPE_DESCRIPTOR + MEDIA_TYPE_DESCRIPTOR
817-
+ RESTEASY_REACTIVE_REQUEST_CONTEXT_DESCRIPTOR + STRING_DESCRIPTOR + ")" + returnDescriptor,
802+
"(" + STRING_DESCRIPTOR + CLASS_DESCRIPTOR + TYPE_DESCRIPTOR + MEDIA_TYPE_DESCRIPTOR
803+
+ RESTEASY_REACTIVE_REQUEST_CONTEXT_DESCRIPTOR + ")" + returnDescriptor,
818804
false);
819805
break;
820806
default:

0 commit comments

Comments
 (0)