Skip to content

Commit ba2654f

Browse files
committed
Improve multipart behaviour to comply with MP REST Client 4.0 TCK tests
Signed-off-by: jansupol <jan.supol@oracle.com>
1 parent 2bd5ba2 commit ba2654f

File tree

7 files changed

+248
-6
lines changed

7 files changed

+248
-6
lines changed

core-client/src/main/java/org/glassfish/jersey/client/RequestProcessingInitializationStage.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2012, 2024 Oracle and/or its affiliates. All rights reserved.
33
*
44
* This program and the accompanying materials are made available under the
55
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -16,7 +16,9 @@
1616

1717
package org.glassfish.jersey.client;
1818

19+
import java.util.Collection;
1920
import java.util.Collections;
21+
import java.util.Iterator;
2022
import java.util.function.Function;
2123
import java.util.stream.Collectors;
2224
import java.util.stream.StreamSupport;
@@ -26,6 +28,7 @@
2628

2729
import jakarta.inject.Provider;
2830

31+
import org.glassfish.jersey.innate.spi.MessageBodyWorkersSettable;
2932
import org.glassfish.jersey.internal.inject.InjectionManager;
3033
import org.glassfish.jersey.internal.inject.Providers;
3134
import org.glassfish.jersey.internal.util.collection.Ref;
@@ -80,6 +83,21 @@ public ClientRequest apply(ClientRequest requestContext) {
8083
requestContext.setWriterInterceptors(writerInterceptors);
8184
requestContext.setReaderInterceptors(readerInterceptors);
8285

86+
if (requestContext.getEntity() != null) {
87+
setWorkers(requestContext.getEntity());
88+
}
89+
8390
return requestContext;
8491
}
92+
93+
private void setWorkers(Object entity) {
94+
if (MessageBodyWorkersSettable.class.isInstance(entity)) {
95+
((MessageBodyWorkersSettable) entity).setMessageBodyWorkers(workersProvider);
96+
} else if (Collection.class.isInstance(entity)) {
97+
Iterator it = ((Collection) entity).iterator();
98+
while (it.hasNext()) {
99+
setWorkers(it.next());
100+
}
101+
}
102+
}
85103
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0, which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* This Source Code may also be made available under the following Secondary
9+
* Licenses when the conditions for such availability set forth in the
10+
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
11+
* version 2 with the GNU Classpath Exception, which is available at
12+
* https://www.gnu.org/software/classpath/license.html.
13+
*
14+
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15+
*/
16+
17+
package org.glassfish.jersey.innate.spi;
18+
19+
import org.glassfish.jersey.message.MessageBodyWorkers;
20+
21+
/**
22+
* Entity type that expects the {@link MessageBodyWorkers} to be set for converting the entity to another types.
23+
*/
24+
public interface MessageBodyWorkersSettable {
25+
26+
/**
27+
* Set message body workers used to transform an entity stream into particular Java type.
28+
*
29+
* @param messageBodyWorkers message body workers.
30+
*/
31+
public void setMessageBodyWorkers(final MessageBodyWorkers messageBodyWorkers);
32+
}

media/multipart/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,12 @@
9292
<version>${project.version}</version>
9393
<scope>test</scope>
9494
</dependency>
95+
<dependency>
96+
<groupId>org.glassfish.jersey.media</groupId>
97+
<artifactId>jersey-media-json-processing</artifactId>
98+
<version>${project.version}</version>
99+
<scope>test</scope>
100+
</dependency>
95101

96102
<dependency>
97103
<groupId>org.junit.jupiter</groupId>

media/multipart/src/main/java/org/glassfish/jersey/media/multipart/BodyPart.java

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2012, 2024 Oracle and/or its affiliates. All rights reserved.
33
*
44
* This program and the accompanying materials are made available under the
55
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -16,10 +16,13 @@
1616

1717
package org.glassfish.jersey.media.multipart;
1818

19+
import java.io.ByteArrayInputStream;
1920
import java.io.IOException;
21+
import java.io.InputStream;
2022
import java.lang.annotation.Annotation;
2123
import java.lang.reflect.Type;
2224
import java.text.ParseException;
25+
import java.util.Arrays;
2326

2427
import jakarta.ws.rs.ProcessingException;
2528
import jakarta.ws.rs.core.GenericType;
@@ -28,6 +31,7 @@
2831
import jakarta.ws.rs.ext.MessageBodyReader;
2932
import jakarta.ws.rs.ext.Providers;
3033

34+
import org.glassfish.jersey.innate.spi.MessageBodyWorkersSettable;
3135
import org.glassfish.jersey.internal.util.collection.ImmutableMultivaluedMap;
3236
import org.glassfish.jersey.media.multipart.internal.LocalizationMessages;
3337
import org.glassfish.jersey.message.MessageBodyWorkers;
@@ -41,7 +45,7 @@
4145
* @author Paul Sandoz
4246
* @author Michal Gajdos
4347
*/
44-
public class BodyPart {
48+
public class BodyPart implements MessageBodyWorkersSettable {
4549

4650
protected ContentDisposition contentDisposition = null;
4751

@@ -285,7 +289,15 @@ <T> T getEntityAs(final GenericType<T> genericEntity) {
285289
}
286290

287291
<T> T getEntityAs(final Class<T> type, Type genericType) {
288-
if (entity == null || !(entity instanceof BodyPartEntity)) {
292+
InputStream inputStream = null;
293+
if (BodyPartEntity.class.isInstance(entity)) {
294+
inputStream = ((BodyPartEntity) entity).getInputStream();
295+
} else if (InputStream.class.isInstance(entity)) {
296+
inputStream = (InputStream) entity;
297+
} else if (byte[].class.isInstance(entity)) {
298+
inputStream = new ByteArrayInputStream((byte[]) entity);
299+
}
300+
if (inputStream == null) {
289301
throw new IllegalStateException(LocalizationMessages.ENTITY_HAS_WRONG_TYPE());
290302
}
291303
if (type == BodyPartEntity.class) {
@@ -299,8 +311,7 @@ <T> T getEntityAs(final Class<T> type, Type genericType) {
299311
}
300312

301313
try {
302-
return reader.readFrom(type, genericType, annotations, mediaType, headers,
303-
((BodyPartEntity) entity).getInputStream());
314+
return reader.readFrom(type, genericType, annotations, mediaType, headers, inputStream);
304315
} catch (final IOException ioe) {
305316
throw new ProcessingException(LocalizationMessages.ERROR_READING_ENTITY(String.class), ioe);
306317
}
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/*
2+
* Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0, which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* This Source Code may also be made available under the following Secondary
9+
* Licenses when the conditions for such availability set forth in the
10+
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
11+
* version 2 with the GNU Classpath Exception, which is available at
12+
* https://www.gnu.org/software/classpath/license.html.
13+
*
14+
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15+
*/
16+
17+
package org.glassfish.jersey.media.multipart;
18+
19+
import jakarta.json.Json;
20+
import jakarta.json.JsonArray;
21+
import jakarta.json.JsonArrayBuilder;
22+
import jakarta.json.JsonObject;
23+
import jakarta.json.JsonObjectBuilder;
24+
import jakarta.json.JsonValue;
25+
import jakarta.ws.rs.BadRequestException;
26+
import jakarta.ws.rs.client.Client;
27+
import jakarta.ws.rs.client.ClientBuilder;
28+
import jakarta.ws.rs.client.ClientRequestContext;
29+
import jakarta.ws.rs.client.ClientRequestFilter;
30+
import jakarta.ws.rs.client.Entity;
31+
import jakarta.ws.rs.core.EntityPart;
32+
import jakarta.ws.rs.core.MediaType;
33+
import jakarta.ws.rs.core.Response;
34+
import org.junit.jupiter.api.Assertions;
35+
import org.junit.jupiter.api.Test;
36+
37+
import java.io.ByteArrayInputStream;
38+
import java.io.IOException;
39+
import java.io.InputStream;
40+
import java.io.UncheckedIOException;
41+
import java.util.LinkedHashMap;
42+
import java.util.List;
43+
import java.util.Map;
44+
import java.util.stream.Collectors;
45+
46+
/**
47+
* Tests in clientFilter before the multipart provider is invoked.
48+
* Check the workers are set.
49+
*
50+
* Modified MP Rest Client TCK tests
51+
*/
52+
public class ClientFilterTests {
53+
/**
54+
* Tests that a single file is upload. The response is a simple JSON response with the file information.
55+
*
56+
* @throws Exception
57+
* if a test error occurs
58+
*/
59+
@Test
60+
public void uploadFile() throws Exception {
61+
try (Client client = createClient()) {
62+
final byte[] content;
63+
try (InputStream in = ClientFilterTests.class.getResourceAsStream("/multipart/test-file1.txt")) {
64+
Assertions.assertNotNull(in, "Could not find /multipart/test-file1.txt");
65+
content = in.readAllBytes();
66+
}
67+
// Send in an InputStream to ensure it works with an InputStream
68+
final List<EntityPart> files = List.of(EntityPart.withFileName("test-file1.txt")
69+
.content(new ByteArrayInputStream(content))
70+
.mediaType(MediaType.APPLICATION_OCTET_STREAM_TYPE)
71+
.build());
72+
try (Response response = client.target("http://localhost").request()
73+
.post(Entity.entity(files, MediaType.MULTIPART_FORM_DATA))) {
74+
Assertions.assertEquals(201, response.getStatus());
75+
final JsonArray jsonArray = response.readEntity(JsonArray.class);
76+
Assertions.assertNotNull(jsonArray);
77+
Assertions.assertEquals(jsonArray.size(), 1);
78+
final JsonObject json = jsonArray.getJsonObject(0);
79+
Assertions.assertEquals(json.getString("name"), "test-file1.txt");
80+
Assertions.assertEquals(json.getString("fileName"), "test-file1.txt");
81+
Assertions.assertEquals(json.getString("content"), "This is a test file for file 1.");
82+
}
83+
}
84+
}
85+
86+
/**
87+
* Tests that two files are upload. The response is a simple JSON response with the file information.
88+
*
89+
* @throws Exception
90+
* if a test error occurs
91+
*/
92+
@Test
93+
public void uploadMultipleFiles() throws Exception {
94+
try (Client client = createClient()) {
95+
final Map<String, byte[]> entityPartContent = new LinkedHashMap<>(2);
96+
try (InputStream in = ClientFilterTests.class.getResourceAsStream("/multipart/test-file1.txt")) {
97+
Assertions.assertNotNull(in, "Could not find /multipart/test-file1.txt");
98+
entityPartContent.put("test-file1.txt", in.readAllBytes());
99+
}
100+
try (InputStream in = ClientFilterTests.class.getResourceAsStream("/multipart/test-file2.txt")) {
101+
Assertions.assertNotNull(in, "Could not find /multipart/test-file2.txt");
102+
entityPartContent.put("test-file2.txt", in.readAllBytes());
103+
}
104+
final List<EntityPart> files = entityPartContent.entrySet()
105+
.stream()
106+
.map((entry) -> {
107+
try {
108+
return EntityPart.withName(entry.getKey())
109+
.fileName(entry.getKey())
110+
.content(entry.getValue())
111+
.mediaType(MediaType.APPLICATION_OCTET_STREAM_TYPE)
112+
.build();
113+
} catch (IOException e) {
114+
throw new UncheckedIOException(e);
115+
}
116+
})
117+
.collect(Collectors.toList());
118+
119+
try (Response response = client.target("http://localhost").request()
120+
.post(Entity.entity(files, MediaType.MULTIPART_FORM_DATA))) {
121+
Assertions.assertEquals(201, response.getStatus());
122+
final JsonArray jsonArray = response.readEntity(JsonArray.class);
123+
Assertions.assertNotNull(jsonArray);
124+
Assertions.assertEquals(jsonArray.size(), 2);
125+
// Don't assume the results are in a specific order
126+
for (JsonValue value : jsonArray) {
127+
final JsonObject json = value.asJsonObject();
128+
if (json.getString("name").equals("test-file1.txt")) {
129+
Assertions.assertEquals(json.getString("fileName"), "test-file1.txt");
130+
Assertions.assertEquals(json.getString("content"), "This is a test file for file 1.");
131+
} else if (json.getString("name").equals("test-file2.txt")) {
132+
Assertions.assertEquals(json.getString("fileName"), "test-file2.txt");
133+
Assertions.assertEquals(json.getString("content"), "This is a test file for file 2.");
134+
} else {
135+
Assertions.fail(String.format("Unexpected entry %s in JSON response: %n%s", json, jsonArray));
136+
}
137+
}
138+
}
139+
}
140+
}
141+
142+
private static Client createClient() {
143+
return ClientBuilder.newClient().register(new FileManagerFilter());
144+
}
145+
146+
public static class FileManagerFilter implements ClientRequestFilter {
147+
148+
@Override
149+
public void filter(final ClientRequestContext requestContext) throws IOException {
150+
if (requestContext.getMethod().equals("POST")) {
151+
// Download the file
152+
@SuppressWarnings("unchecked")
153+
final List<EntityPart> entityParts = (List<EntityPart>) requestContext.getEntity();
154+
final JsonArrayBuilder jsonBuilder = Json.createArrayBuilder();
155+
for (EntityPart part : entityParts) {
156+
final JsonObjectBuilder jsonPartBuilder = Json.createObjectBuilder();
157+
jsonPartBuilder.add("name", part.getName());
158+
if (part.getFileName().isPresent()) {
159+
jsonPartBuilder.add("fileName", part.getFileName().get());
160+
} else {
161+
throw new BadRequestException("No file name for entity part " + part);
162+
}
163+
jsonPartBuilder.add("content", part.getContent(String.class));
164+
jsonBuilder.add(jsonPartBuilder);
165+
}
166+
requestContext.abortWith(Response.status(201).entity(jsonBuilder.build()).build());
167+
} else {
168+
requestContext
169+
.abortWith(Response.status(Response.Status.BAD_REQUEST).entity("Invalid request").build());
170+
}
171+
}
172+
}
173+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
This is a test file for file 1.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
This is a test file for file 2.

0 commit comments

Comments
 (0)