Skip to content

Commit

Permalink
Resteasy Reactive JAXB using multipart only bound XML media types
Browse files Browse the repository at this point in the history
  • Loading branch information
Sgitario authored and igorregis committed Oct 16, 2022
1 parent 199afdf commit 3339af6
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@

public class ResteasyReactiveJaxbProcessor {

private static final List<String> XML_TYPES = List.of(MediaType.APPLICATION_XML, MediaType.TEXT_XML);

@BuildStep
void feature(BuildProducer<FeatureBuildItem> feature) {
feature.produce(new FeatureBuildItem(Feature.RESTEASY_REACTIVE_JAXB));
Expand All @@ -56,10 +58,10 @@ void additionalProviders(BuildProducer<AdditionalBeanBuildItem> additionalBean,

additionalReaders
.produce(new MessageBodyReaderBuildItem(ServerJaxbMessageBodyReader.class.getName(), Object.class.getName(),
List.of(MediaType.APPLICATION_XML, MediaType.TEXT_XML), RuntimeType.SERVER, true, Priorities.USER));
XML_TYPES, RuntimeType.SERVER, true, Priorities.USER));
additionalWriters
.produce(new MessageBodyWriterBuildItem(ServerJaxbMessageBodyWriter.class.getName(), Object.class.getName(),
List.of(MediaType.APPLICATION_XML, MediaType.TEXT_XML), RuntimeType.SERVER, true, Priorities.USER));
XML_TYPES, RuntimeType.SERVER, true, Priorities.USER));
}

@BuildStep
Expand Down Expand Up @@ -87,11 +89,17 @@ void registerClassesToBeBound(ResteasyReactiveResourceMethodEntriesBuildItem res
}

// If consumes "application/xml" or "multipart/form-data", we register all the classes of the parameters
if (consumesXml(resourceInfo) || consumesMultipart(resourceInfo)) {
boolean consumesXml = consumesXml(resourceInfo);
boolean consumesMultipart = consumesMultipart(resourceInfo);
if (consumesXml || consumesMultipart) {
for (Type parameter : methodInfo.parameterTypes()) {
ClassInfo effectiveParameter = getEffectiveClassInfo(parameter, indexView);
if (effectiveParameter != null) {
classesInfo.add(effectiveParameter);
if (consumesXml) {
classesInfo.add(effectiveParameter);
} else if (consumesMultipart) {
classesInfo.addAll(getEffectivePartsUsingXml(effectiveParameter, indexView));
}
}
}
}
Expand All @@ -112,18 +120,26 @@ void setupJaxbContextConfigForValidator(Capabilities capabilities,
private List<ClassInfo> getEffectivePartsUsingXml(ClassInfo returnType, IndexView indexView) {
List<ClassInfo> classInfos = new ArrayList<>();
for (FieldInfo field : returnType.fields()) {
AnnotationInstance partTypeInstance = field.annotation(ResteasyReactiveDotNames.PART_TYPE_NAME);
if (partTypeInstance != null) {
AnnotationValue partTypeValue = partTypeInstance.value();
if (partTypeValue != null && MediaType.APPLICATION_XML.equals(partTypeValue.asString())) {
classInfos.add(getEffectiveClassInfo(field.type(), indexView));
}
if (isPartTypeXml(field)) {
classInfos.add(getEffectiveClassInfo(field.type(), indexView));
}
}

return classInfos;
}

private boolean isPartTypeXml(FieldInfo field) {
AnnotationInstance partType = field.annotation(ResteasyReactiveDotNames.PART_TYPE_NAME);
if (partType != null) {
AnnotationValue partTypeValue = partType.value();
if (containsMediaType(new String[] { partTypeValue.asString() }, XML_TYPES)) {
return true;
}
}

return false;
}

private ClassInfo getEffectiveClassInfo(Type type, IndexView indexView) {
if (type.kind() == Type.Kind.VOID || type.kind() == Type.Kind.PRIMITIVE) {
return null;
Expand Down Expand Up @@ -158,26 +174,28 @@ private ClassInfo getEffectiveClassInfo(Type type, IndexView indexView) {
}

private boolean consumesXml(ResourceMethod resourceInfo) {
return containsMediaType(resourceInfo.getConsumes(), MediaType.APPLICATION_XML);
return containsMediaType(resourceInfo.getConsumes(), XML_TYPES);
}

private boolean consumesMultipart(ResourceMethod resourceInfo) {
return containsMediaType(resourceInfo.getConsumes(), MediaType.MULTIPART_FORM_DATA);
return containsMediaType(resourceInfo.getConsumes(), List.of(MediaType.MULTIPART_FORM_DATA));
}

private boolean producesXml(ResourceMethod resourceInfo) {
return containsMediaType(resourceInfo.getProduces(), MediaType.APPLICATION_XML);
return containsMediaType(resourceInfo.getProduces(), XML_TYPES);
}

private boolean producesMultipart(ResourceMethod resourceInfo) {
return containsMediaType(resourceInfo.getProduces(), MediaType.MULTIPART_FORM_DATA);
return containsMediaType(resourceInfo.getProduces(), List.of(MediaType.MULTIPART_FORM_DATA));
}

private boolean containsMediaType(String[] types, String mediaType) {
private boolean containsMediaType(String[] types, List<String> mediaTypes) {
if (types != null) {
for (String type : types) {
if (type.toLowerCase(Locale.ROOT).contains(mediaType)) {
return true;
for (String mediaType : mediaTypes) {
if (type.toLowerCase(Locale.ROOT).contains(mediaType)) {
return true;
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@

import static org.assertj.core.api.Assertions.assertThat;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;

import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
Expand All @@ -12,6 +17,7 @@
import org.jboss.resteasy.reactive.MultipartForm;
import org.jboss.resteasy.reactive.PartType;
import org.jboss.resteasy.reactive.RestForm;
import org.jboss.resteasy.reactive.multipart.FileUpload;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
Expand All @@ -36,6 +42,7 @@ public class MultipartTest {
+ "<school>"
+ "<name>Divino Pastor</name>"
+ "</school>";
private final File HTML_FILE = new File("./src/test/resources/test.html");

@RegisterExtension
static QuarkusUnitTest test = new QuarkusUnitTest()
Expand All @@ -51,7 +58,7 @@ public void testOutput() {
.extract().asString();

assertContains(response, "name", MediaType.TEXT_PLAIN, EXPECTED_RESPONSE_NAME);
assertContains(response, "person", MediaType.APPLICATION_XML, EXPECTED_RESPONSE_PERSON);
assertContains(response, "person", MediaType.TEXT_XML, EXPECTED_RESPONSE_PERSON);
}

@Test
Expand All @@ -68,6 +75,19 @@ public void testInput() {
assertThat(response).isEqualTo("John-Divino Pastor");
}

@Test
public void testInputFile() throws IOException {
String response = RestAssured
.given()
.multiPart("file", HTML_FILE, "text/html")
.post("/multipart/input/file")
.then()
.statusCode(200)
.extract().asString();

assertThat(response).isEqualTo(String.valueOf(Files.readAllBytes(HTML_FILE.toPath()).length));
}

private void assertContains(String response, String name, String contentType, Object value) {
String[] lines = response.split("--");
assertThat(lines).anyMatch(line -> line.contains(String.format(EXPECTED_CONTENT_DISPOSITION_PART, name))
Expand Down Expand Up @@ -97,6 +117,13 @@ public String input(@MultipartForm MultipartInput input) {
return input.name + "-" + input.school.name;
}

@POST
@Path("/input/file")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public int inputFile(@MultipartForm FileUploadData data) throws IOException {
return Files.readAllBytes(data.fileUpload.filePath()).length;
}

}

private static class MultipartOutputResponse {
Expand All @@ -105,10 +132,15 @@ private static class MultipartOutputResponse {
String name;

@RestForm
@PartType(MediaType.APPLICATION_XML)
@PartType(MediaType.TEXT_XML)
Person person;
}

public static class FileUploadData {
@FormParam("file")
FileUpload fileUpload;
}

public static class MultipartInput {

@RestForm
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<!-- head definitions go here -->
</head>
<body>
<!-- the content goes here -->
</body>
</html>

0 comments on commit 3339af6

Please sign in to comment.