From dbda2600ee2a1cedf61c3f6c00d70e312e7ca06d Mon Sep 17 00:00:00 2001 From: pkryshtop Date: Tue, 12 Nov 2024 18:44:40 +0200 Subject: [PATCH] feat: pass correct filename on attachment upload --- .../ext/ExtendedMultipartFormWriter.java | 35 ++++++++++++++++++- .../smartling/resteasy/ext/FileFormParam.java | 23 ++++++++++++ .../ext/ExtendedMultipartFormWriterTest.java | 25 +++++++++++++ .../resteasy/ext/data/DummyMultipartApi.java | 5 +++ .../ext/data/FormWithFileFormParams.java | 30 ++++++++++++++++ ...dedMultipartFormWriterIntegrationTest.java | 34 ++++++++++++++++++ .../v2/pto/AttachmentUploadPTO.java | 4 +-- .../attachments/v2/AttachmentsApiTest.java | 2 +- 8 files changed, 154 insertions(+), 4 deletions(-) create mode 100644 smartling-api-commons/src/main/java/com/smartling/resteasy/ext/FileFormParam.java create mode 100644 smartling-api-commons/src/test/java/com/smartling/resteasy/ext/data/FormWithFileFormParams.java diff --git a/smartling-api-commons/src/main/java/com/smartling/resteasy/ext/ExtendedMultipartFormWriter.java b/smartling-api-commons/src/main/java/com/smartling/resteasy/ext/ExtendedMultipartFormWriter.java index fafe1f39..d4b0d12e 100644 --- a/smartling-api-commons/src/main/java/com/smartling/resteasy/ext/ExtendedMultipartFormWriter.java +++ b/smartling-api-commons/src/main/java/com/smartling/resteasy/ext/ExtendedMultipartFormWriter.java @@ -1,5 +1,6 @@ package com.smartling.resteasy.ext; +import org.apache.commons.lang3.StringUtils; import org.jboss.resteasy.annotations.providers.multipart.PartType; import org.jboss.resteasy.plugins.providers.multipart.FieldEnablerPrivilegedAction; import org.jboss.resteasy.plugins.providers.multipart.MultipartFormAnnotationWriter; @@ -33,7 +34,8 @@ public class ExtendedMultipartFormWriter extends MultipartFormAnnotationWriter @Override protected void getFields(Class type, MultipartFormDataOutput output, Object obj) throws IOException { - for (Field field : type.getDeclaredFields()) + Field[] declaredFields = type.getDeclaredFields(); + for (Field field : declaredFields) { if (field.isAnnotationPresent(DynamicFormParam.class) && field.isAnnotationPresent(PartType.class)) { @@ -84,11 +86,42 @@ protected void getFields(Class type, MultipartFormDataOutput output, Object o } } } + if (field.isAnnotationPresent(FileFormParam.class) && field.isAnnotationPresent(PartType.class)) + { + AccessController.doPrivileged(new FieldEnablerPrivilegedAction(field)); + + FileFormParam fileFormParam = field.getAnnotation(FileFormParam.class); + String name = fileFormParam.value(); + Object value = safeExtractValue(obj, field); + String mediaType = field.getAnnotation(PartType.class).value(); + String filename = getFilename(obj, declaredFields, field); + + output.addFormData(name, value, field.getType(), field.getGenericType(), MediaType.valueOf(mediaType), filename); + } } super.getFields(type, output, obj); } + private String getFilename(Object obj, Field[] declaredFields, Field field) + { + String filenameField = field.getAnnotation(FileFormParam.class).filenameField(); + if (StringUtils.isNotEmpty(filenameField)) + { + for (Field declaredField : declaredFields) + { + if (declaredField.getName().equals(filenameField)) + { + AccessController.doPrivileged(new FieldEnablerPrivilegedAction(declaredField)); + + return (String) safeExtractValue(obj, declaredField); + } + } + } + + return getFilename(field); + } + private Object safeExtractValue(Object obj, Field field) { Object value; diff --git a/smartling-api-commons/src/main/java/com/smartling/resteasy/ext/FileFormParam.java b/smartling-api-commons/src/main/java/com/smartling/resteasy/ext/FileFormParam.java new file mode 100644 index 00000000..39ad23eb --- /dev/null +++ b/smartling-api-commons/src/main/java/com/smartling/resteasy/ext/FileFormParam.java @@ -0,0 +1,23 @@ +package com.smartling.resteasy.ext; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Must be used in conjunction with {@link org.jboss.resteasy.annotations.providers.multipart.PartType}. + * It defines file form data with filename. + *

+ * It is a replacement for {@link javax.ws.rs.FormParam} and {@link org.jboss.resteasy.annotations.jaxrs.FormParam} + * and should not be used together on the same field. + *

+ * Be careful, it may not fully replace FormParam functionality. + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface FileFormParam +{ + String value(); + String filenameField() default ""; +} diff --git a/smartling-api-commons/src/test/java/com/smartling/resteasy/ext/ExtendedMultipartFormWriterTest.java b/smartling-api-commons/src/test/java/com/smartling/resteasy/ext/ExtendedMultipartFormWriterTest.java index 008b3868..b51479a2 100644 --- a/smartling-api-commons/src/test/java/com/smartling/resteasy/ext/ExtendedMultipartFormWriterTest.java +++ b/smartling-api-commons/src/test/java/com/smartling/resteasy/ext/ExtendedMultipartFormWriterTest.java @@ -1,18 +1,21 @@ package com.smartling.resteasy.ext; import com.smartling.resteasy.ext.data.FormWithDynamicParams; +import com.smartling.resteasy.ext.data.FormWithFileFormParams; import com.smartling.resteasy.ext.data.FormWithListParams; import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataOutput; import org.junit.Before; import org.junit.Test; import java.io.ByteArrayInputStream; +import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import static java.lang.Boolean.TRUE; +import static javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM_TYPE; import static javax.ws.rs.core.MediaType.TEXT_PLAIN_TYPE; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -85,4 +88,26 @@ public void shouldExtractListFormParams() throws Exception verifyNoMoreInteractions(output); } + @Test + public void shouldExtractFileFormParams() throws Exception + { + MultipartFormDataOutput output = mock(MultipartFormDataOutput.class); + InputStream ins1 = mock(InputStream.class); + InputStream ins2 = mock(InputStream.class); + InputStream ins3 = mock(InputStream.class); + + FormWithFileFormParams form = new FormWithFileFormParams(); + form.setFile1(ins1); + form.setFile1Name("file1.txt"); + form.setFile2(ins2); + form.setFile3(ins3); + + testedInstance.getFields(FormWithFileFormParams.class, output, form); + + verify(output).addFormData(eq("file1"), eq(ins1), eq(InputStream.class), eq(InputStream.class), eq(APPLICATION_OCTET_STREAM_TYPE), eq("file1.txt")); + verify(output).addFormData(eq("file2"), eq(ins2), eq(InputStream.class), eq(InputStream.class), eq(APPLICATION_OCTET_STREAM_TYPE), eq("file2")); + verify(output).addFormData(eq("file3"), eq(ins3), eq(InputStream.class), eq(InputStream.class), eq(APPLICATION_OCTET_STREAM_TYPE), isNull()); + verify(output).addFormData(eq("file1Name"), eq("file1.txt"), eq(String.class), eq(String.class), eq(TEXT_PLAIN_TYPE), isNull()); + verifyNoMoreInteractions(output); + } } diff --git a/smartling-api-commons/src/test/java/com/smartling/resteasy/ext/data/DummyMultipartApi.java b/smartling-api-commons/src/test/java/com/smartling/resteasy/ext/data/DummyMultipartApi.java index d297dce4..e6eea483 100644 --- a/smartling-api-commons/src/test/java/com/smartling/resteasy/ext/data/DummyMultipartApi.java +++ b/smartling-api-commons/src/test/java/com/smartling/resteasy/ext/data/DummyMultipartApi.java @@ -21,4 +21,9 @@ public interface DummyMultipartApi @Consumes(MULTIPART_FORM_DATA) @Path(DUMMY_MULTIPART_API + "/list-fields") void postListParams(@MultipartForm FormWithListParams form); + + @POST + @Consumes(MULTIPART_FORM_DATA) + @Path(DUMMY_MULTIPART_API + "/file-fields") + void postFileFormParams(@MultipartForm FormWithFileFormParams form); } diff --git a/smartling-api-commons/src/test/java/com/smartling/resteasy/ext/data/FormWithFileFormParams.java b/smartling-api-commons/src/test/java/com/smartling/resteasy/ext/data/FormWithFileFormParams.java new file mode 100644 index 00000000..f4457bf2 --- /dev/null +++ b/smartling-api-commons/src/test/java/com/smartling/resteasy/ext/data/FormWithFileFormParams.java @@ -0,0 +1,30 @@ +package com.smartling.resteasy.ext.data; + +import com.smartling.resteasy.ext.FileFormParam; +import lombok.Data; +import org.jboss.resteasy.annotations.providers.multipart.PartFilename; +import org.jboss.resteasy.annotations.providers.multipart.PartType; + +import javax.ws.rs.FormParam; +import java.io.InputStream; + +@Data +public class FormWithFileFormParams +{ + @FileFormParam(value = "file1", filenameField = "file1Name") + @PartType("application/octet-stream") + private InputStream file1; + + @FileFormParam("file2") + @PartFilename("file2") + @PartType("application/octet-stream") + private InputStream file2; + + @FileFormParam("file3") + @PartType("application/octet-stream") + private InputStream file3; + + @FormParam("file1Name") + @PartType("text/plain") + private String file1Name; +} diff --git a/smartling-api-commons/src/test/java/com/smartling/resteasy/ext/integration/ExtendedMultipartFormWriterIntegrationTest.java b/smartling-api-commons/src/test/java/com/smartling/resteasy/ext/integration/ExtendedMultipartFormWriterIntegrationTest.java index a766b041..1f4ca0a3 100644 --- a/smartling-api-commons/src/test/java/com/smartling/resteasy/ext/integration/ExtendedMultipartFormWriterIntegrationTest.java +++ b/smartling-api-commons/src/test/java/com/smartling/resteasy/ext/integration/ExtendedMultipartFormWriterIntegrationTest.java @@ -4,6 +4,7 @@ import com.smartling.resteasy.ext.ExtendedMultipartFormWriter; import com.smartling.resteasy.ext.data.DummyMultipartApi; import com.smartling.resteasy.ext.data.FormWithDynamicParams; +import com.smartling.resteasy.ext.data.FormWithFileFormParams; import com.smartling.resteasy.ext.data.FormWithListParams; import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder; import org.junit.Before; @@ -12,6 +13,7 @@ import javax.ws.rs.client.ClientBuilder; import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -99,4 +101,36 @@ public void shouldExtractListFormParams() throws Exception .build()) ); } + + @Test + public void shouldExtractFileFormParams() throws Exception + { + FormWithFileFormParams form = new FormWithFileFormParams(); + form.setFile1(new ByteArrayInputStream("file1 data".getBytes(StandardCharsets.UTF_8))); + form.setFile1Name("file1.txt"); + form.setFile2(new ByteArrayInputStream("file2 data".getBytes(StandardCharsets.UTF_8))); + form.setFile3(new ByteArrayInputStream("file3 data".getBytes(StandardCharsets.UTF_8))); + + + dummyApi().postFileFormParams(form); + + dummyApiEndpoint.verify(postRequestedFor(urlEqualTo(DUMMY_MULTIPART_API + "/file-fields")) + .withRequestBodyPart(aMultipart() + .withHeader("Content-Disposition", equalTo("form-data; name=\"file1\"; filename=\"file1.txt\"")) + .withBody(equalTo("file1 data")) + .build()) + .withRequestBodyPart(aMultipart() + .withHeader("Content-Disposition", equalTo("form-data; name=\"file2\"; filename=\"file2\"")) + .withBody(equalTo("file2 data")) + .build()) + .withRequestBodyPart(aMultipart() + .withHeader("Content-Disposition", equalTo("form-data; name=\"file3\"")) + .withBody(equalTo("file3 data")) + .build()) + .withRequestBodyPart(aMultipart() + .withName("file1Name") + .withBody(equalTo("file1.txt")) + .build()) + ); + } } diff --git a/smartling-attachments-api/src/main/java/com/smartling/api/attachments/v2/pto/AttachmentUploadPTO.java b/smartling-attachments-api/src/main/java/com/smartling/api/attachments/v2/pto/AttachmentUploadPTO.java index e4e08120..421bcb00 100644 --- a/smartling-attachments-api/src/main/java/com/smartling/api/attachments/v2/pto/AttachmentUploadPTO.java +++ b/smartling-attachments-api/src/main/java/com/smartling/api/attachments/v2/pto/AttachmentUploadPTO.java @@ -1,5 +1,6 @@ package com.smartling.api.attachments.v2.pto; +import com.smartling.resteasy.ext.FileFormParam; import com.smartling.resteasy.ext.ListFormParam; import lombok.AllArgsConstructor; import lombok.Data; @@ -16,8 +17,7 @@ @AllArgsConstructor public class AttachmentUploadPTO { - @FormParam("file") - @PartFilename("file") + @FileFormParam(value = "file", filenameField = "name") @PartType("application/octet-stream") private InputStream file; diff --git a/smartling-attachments-api/src/test/java/com/smartling/api/attachments/v2/AttachmentsApiTest.java b/smartling-attachments-api/src/test/java/com/smartling/api/attachments/v2/AttachmentsApiTest.java index b4acb766..1d8b6550 100644 --- a/smartling-attachments-api/src/test/java/com/smartling/api/attachments/v2/AttachmentsApiTest.java +++ b/smartling-attachments-api/src/test/java/com/smartling/api/attachments/v2/AttachmentsApiTest.java @@ -178,7 +178,7 @@ public void testUploadAttachment() throws Exception String partSeparator = requestBody.substring(0, 38); assertEquals(requestBody, partSeparator + "\r\n" + - "Content-Disposition: form-data; name=\"file\"; filename=\"file\"\r\n" + + "Content-Disposition: form-data; name=\"file\"; filename=\"test.txt\"\r\n" + "Content-Type: application/octet-stream\r\n" + "\r\n" + "content\r\n" +