Skip to content

Commit

Permalink
Improve multipart behaviour to comply with MP REST Client 4.0 TCK tests
Browse files Browse the repository at this point in the history
Signed-off-by: jansupol <[email protected]>
  • Loading branch information
jansupol committed Dec 19, 2024
1 parent 2bd5ba2 commit ba2654f
Show file tree
Hide file tree
Showing 7 changed files with 248 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2024 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
Expand All @@ -16,7 +16,9 @@

package org.glassfish.jersey.client;

import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
Expand All @@ -26,6 +28,7 @@

import jakarta.inject.Provider;

import org.glassfish.jersey.innate.spi.MessageBodyWorkersSettable;
import org.glassfish.jersey.internal.inject.InjectionManager;
import org.glassfish.jersey.internal.inject.Providers;
import org.glassfish.jersey.internal.util.collection.Ref;
Expand Down Expand Up @@ -80,6 +83,21 @@ public ClientRequest apply(ClientRequest requestContext) {
requestContext.setWriterInterceptors(writerInterceptors);
requestContext.setReaderInterceptors(readerInterceptors);

if (requestContext.getEntity() != null) {
setWorkers(requestContext.getEntity());
}

return requestContext;
}

private void setWorkers(Object entity) {
if (MessageBodyWorkersSettable.class.isInstance(entity)) {
((MessageBodyWorkersSettable) entity).setMessageBodyWorkers(workersProvider);
} else if (Collection.class.isInstance(entity)) {
Iterator it = ((Collection) entity).iterator();
while (it.hasNext()) {
setWorkers(it.next());
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/

package org.glassfish.jersey.innate.spi;

import org.glassfish.jersey.message.MessageBodyWorkers;

/**
* Entity type that expects the {@link MessageBodyWorkers} to be set for converting the entity to another types.
*/
public interface MessageBodyWorkersSettable {

/**
* Set message body workers used to transform an entity stream into particular Java type.
*
* @param messageBodyWorkers message body workers.
*/
public void setMessageBodyWorkers(final MessageBodyWorkers messageBodyWorkers);
}
6 changes: 6 additions & 0 deletions media/multipart/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-processing</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2024 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
Expand All @@ -16,10 +16,13 @@

package org.glassfish.jersey.media.multipart;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.text.ParseException;
import java.util.Arrays;

import jakarta.ws.rs.ProcessingException;
import jakarta.ws.rs.core.GenericType;
Expand All @@ -28,6 +31,7 @@
import jakarta.ws.rs.ext.MessageBodyReader;
import jakarta.ws.rs.ext.Providers;

import org.glassfish.jersey.innate.spi.MessageBodyWorkersSettable;
import org.glassfish.jersey.internal.util.collection.ImmutableMultivaluedMap;
import org.glassfish.jersey.media.multipart.internal.LocalizationMessages;
import org.glassfish.jersey.message.MessageBodyWorkers;
Expand All @@ -41,7 +45,7 @@
* @author Paul Sandoz
* @author Michal Gajdos
*/
public class BodyPart {
public class BodyPart implements MessageBodyWorkersSettable {

protected ContentDisposition contentDisposition = null;

Expand Down Expand Up @@ -285,7 +289,15 @@ <T> T getEntityAs(final GenericType<T> genericEntity) {
}

<T> T getEntityAs(final Class<T> type, Type genericType) {
if (entity == null || !(entity instanceof BodyPartEntity)) {
InputStream inputStream = null;
if (BodyPartEntity.class.isInstance(entity)) {
inputStream = ((BodyPartEntity) entity).getInputStream();
} else if (InputStream.class.isInstance(entity)) {
inputStream = (InputStream) entity;
} else if (byte[].class.isInstance(entity)) {
inputStream = new ByteArrayInputStream((byte[]) entity);
}
if (inputStream == null) {
throw new IllegalStateException(LocalizationMessages.ENTITY_HAS_WRONG_TYPE());
}
if (type == BodyPartEntity.class) {
Expand All @@ -299,8 +311,7 @@ <T> T getEntityAs(final Class<T> type, Type genericType) {
}

try {
return reader.readFrom(type, genericType, annotations, mediaType, headers,
((BodyPartEntity) entity).getInputStream());
return reader.readFrom(type, genericType, annotations, mediaType, headers, inputStream);
} catch (final IOException ioe) {
throw new ProcessingException(LocalizationMessages.ERROR_READING_ENTITY(String.class), ioe);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/*
* Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/

package org.glassfish.jersey.media.multipart;

import jakarta.json.Json;
import jakarta.json.JsonArray;
import jakarta.json.JsonArrayBuilder;
import jakarta.json.JsonObject;
import jakarta.json.JsonObjectBuilder;
import jakarta.json.JsonValue;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.client.ClientRequestContext;
import jakarta.ws.rs.client.ClientRequestFilter;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.EntityPart;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
* Tests in clientFilter before the multipart provider is invoked.
* Check the workers are set.
*
* Modified MP Rest Client TCK tests
*/
public class ClientFilterTests {
/**
* Tests that a single file is upload. The response is a simple JSON response with the file information.
*
* @throws Exception
* if a test error occurs
*/
@Test
public void uploadFile() throws Exception {
try (Client client = createClient()) {
final byte[] content;
try (InputStream in = ClientFilterTests.class.getResourceAsStream("/multipart/test-file1.txt")) {
Assertions.assertNotNull(in, "Could not find /multipart/test-file1.txt");
content = in.readAllBytes();
}
// Send in an InputStream to ensure it works with an InputStream
final List<EntityPart> files = List.of(EntityPart.withFileName("test-file1.txt")
.content(new ByteArrayInputStream(content))
.mediaType(MediaType.APPLICATION_OCTET_STREAM_TYPE)
.build());
try (Response response = client.target("http://localhost").request()
.post(Entity.entity(files, MediaType.MULTIPART_FORM_DATA))) {
Assertions.assertEquals(201, response.getStatus());
final JsonArray jsonArray = response.readEntity(JsonArray.class);
Assertions.assertNotNull(jsonArray);
Assertions.assertEquals(jsonArray.size(), 1);
final JsonObject json = jsonArray.getJsonObject(0);
Assertions.assertEquals(json.getString("name"), "test-file1.txt");
Assertions.assertEquals(json.getString("fileName"), "test-file1.txt");
Assertions.assertEquals(json.getString("content"), "This is a test file for file 1.");
}
}
}

/**
* Tests that two files are upload. The response is a simple JSON response with the file information.
*
* @throws Exception
* if a test error occurs
*/
@Test
public void uploadMultipleFiles() throws Exception {
try (Client client = createClient()) {
final Map<String, byte[]> entityPartContent = new LinkedHashMap<>(2);
try (InputStream in = ClientFilterTests.class.getResourceAsStream("/multipart/test-file1.txt")) {
Assertions.assertNotNull(in, "Could not find /multipart/test-file1.txt");
entityPartContent.put("test-file1.txt", in.readAllBytes());
}
try (InputStream in = ClientFilterTests.class.getResourceAsStream("/multipart/test-file2.txt")) {
Assertions.assertNotNull(in, "Could not find /multipart/test-file2.txt");
entityPartContent.put("test-file2.txt", in.readAllBytes());
}
final List<EntityPart> files = entityPartContent.entrySet()
.stream()
.map((entry) -> {
try {
return EntityPart.withName(entry.getKey())
.fileName(entry.getKey())
.content(entry.getValue())
.mediaType(MediaType.APPLICATION_OCTET_STREAM_TYPE)
.build();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
})
.collect(Collectors.toList());

try (Response response = client.target("http://localhost").request()
.post(Entity.entity(files, MediaType.MULTIPART_FORM_DATA))) {
Assertions.assertEquals(201, response.getStatus());
final JsonArray jsonArray = response.readEntity(JsonArray.class);
Assertions.assertNotNull(jsonArray);
Assertions.assertEquals(jsonArray.size(), 2);
// Don't assume the results are in a specific order
for (JsonValue value : jsonArray) {
final JsonObject json = value.asJsonObject();
if (json.getString("name").equals("test-file1.txt")) {
Assertions.assertEquals(json.getString("fileName"), "test-file1.txt");
Assertions.assertEquals(json.getString("content"), "This is a test file for file 1.");
} else if (json.getString("name").equals("test-file2.txt")) {
Assertions.assertEquals(json.getString("fileName"), "test-file2.txt");
Assertions.assertEquals(json.getString("content"), "This is a test file for file 2.");
} else {
Assertions.fail(String.format("Unexpected entry %s in JSON response: %n%s", json, jsonArray));
}
}
}
}
}

private static Client createClient() {
return ClientBuilder.newClient().register(new FileManagerFilter());
}

public static class FileManagerFilter implements ClientRequestFilter {

@Override
public void filter(final ClientRequestContext requestContext) throws IOException {
if (requestContext.getMethod().equals("POST")) {
// Download the file
@SuppressWarnings("unchecked")
final List<EntityPart> entityParts = (List<EntityPart>) requestContext.getEntity();
final JsonArrayBuilder jsonBuilder = Json.createArrayBuilder();
for (EntityPart part : entityParts) {
final JsonObjectBuilder jsonPartBuilder = Json.createObjectBuilder();
jsonPartBuilder.add("name", part.getName());
if (part.getFileName().isPresent()) {
jsonPartBuilder.add("fileName", part.getFileName().get());
} else {
throw new BadRequestException("No file name for entity part " + part);
}
jsonPartBuilder.add("content", part.getContent(String.class));
jsonBuilder.add(jsonPartBuilder);
}
requestContext.abortWith(Response.status(201).entity(jsonBuilder.build()).build());
} else {
requestContext
.abortWith(Response.status(Response.Status.BAD_REQUEST).entity("Invalid request").build());
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This is a test file for file 1.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This is a test file for file 2.

0 comments on commit ba2654f

Please sign in to comment.