StreamReadConstrainsTest.java
/*
* Copyright (c) 2023, 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.jackson.internal;
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.StreamReadConstraints;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.core.exc.StreamConstraintsException;
import com.fasterxml.jackson.core.json.PackageVersion;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.JsonSerializable;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.TextNode;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.message.MessageProperties;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import javax.annotation.Priority;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Priorities;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.Produces;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.ExceptionMapper;
import java.lang.reflect.Method;
import java.util.List;
public class StreamReadConstrainsTest extends JerseyTest {
private static final String ERROR_MSG_PART = "maximum allowed (";
@Override
protected final Application configure() {
return new ResourceConfig(TestLengthResource.class,
MyStreamReadConstraints.class,
MyStreamReadConstraintsExceptionMapper.class);
}
@Override
protected void configureClient(ClientConfig config) {
config.register(JacksonFeature.class);
}
@Test
void testNumberLength() {
try (Response response = target("len/entity").request()
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON_TYPE)
.post(Entity.entity(new MyEntity(3), MediaType.APPLICATION_JSON_TYPE))) {
Assertions.assertEquals(200, response.getStatus());
JsonNode entity = response.readEntity(JsonNode.class);
Assertions.assertEquals("1234", entity.get("value").asText());
}
try (Response response = target("len/entity").request()
.post(Entity.entity(new MyEntity(8), MediaType.APPLICATION_JSON_TYPE))) {
Assertions.assertEquals(200, response.getStatus());
String errorMsg = response.readEntity(String.class);
Assertions.assertTrue(errorMsg.contains(ERROR_MSG_PART + 4));
}
}
@Test
void testStringLengthUsingProperty() {
testConstraintOnClient(
client()
.property(MessageProperties.JSON_MAX_STRING_LENGTH, 4)
.target(getBaseUri())
.path("len/strlen"),
4
);
}
@Test
void testStringLengthPriorityProperty() {
testConstraintOnClient(
ClientBuilder.newClient()
.register(JacksonFeature.withExceptionMappers().maxStringLength(30))
.property(MessageProperties.JSON_MAX_STRING_LENGTH, "3" /* check string value */)
.target(getBaseUri()).path("len/strlen"),
3);
}
@Test
void testStringLengthUsingFeature() {
testConstraintOnClient(
ClientBuilder.newClient()
.register(JacksonFeature.withExceptionMappers().maxStringLength(3))
.target(getBaseUri())
.path("len/strlen"),
3
);
}
void testConstraintOnClient(WebTarget target, int expectedLength) {
try (Response response = target.request().post(Entity.entity(expectedLength + 1, MediaType.APPLICATION_JSON_TYPE))) {
Assertions.assertEquals(200, response.getStatus());
JsonNode errorMsg = response.readEntity(JsonNode.class);
Assertions.fail("StreamConstraintsException has not been thrown");
} catch (ProcessingException ex) {
if (!StreamConstraintsException.class.isInstance(ex.getCause())) {
throw ex;
}
String errorMsg = ex.getCause().getMessage();
Assertions.assertTrue(errorMsg.contains(ERROR_MSG_PART + String.valueOf(expectedLength)));
}
}
@Test
void testMatchingVersion() {
final Version coreVersion = PackageVersion.VERSION;
final Version jerseyVersion = org.glassfish.jersey.jackson.internal.jackson.jaxrs.json.PackageVersion.VERSION;
StringBuilder message = new StringBuilder();
message.append("Dependency Jackson Version is ")
.append(coreVersion.getMajorVersion())
.append(".")
.append(coreVersion.getMinorVersion());
message.append("\n Repackaged Jackson Version is ")
.append(jerseyVersion.getMajorVersion())
.append(".")
.append(jerseyVersion.getMinorVersion());
Assertions.assertEquals(coreVersion.getMajorVersion(), jerseyVersion.getMajorVersion(), message.toString());
Assertions.assertEquals(coreVersion.getMinorVersion(), jerseyVersion.getMinorVersion(), message.toString());
Assertions.assertEquals(coreVersion.getMajorVersion(), 2,
"update " + DefaultJacksonJaxbJsonProvider.class.getName()
+ " updateFactoryConstraints method to support version " + coreVersion.getMajorVersion());
}
@Test
void testStreamReadConstraintsMethods() {
String message = "There are additional methods in Jackson's StreamReaderConstraints.Builder."
+ " Please update the code in " + DefaultJacksonJaxbJsonProvider.class.getName()
+ " updateFactoryConstraints method";
Method[] method = StreamReadConstraints.Builder.class.getDeclaredMethods();
// 2.17 : five setMax... + build() methods
// 2.18 : six setMax... + build() methods
Assertions.assertEquals(7, method.length, message);
}
@Path("len")
public static class TestLengthResource {
@POST
@Path("number")
@Produces(MediaType.APPLICATION_JSON)
public MyEntity number(int len) {
return new MyEntity(len);
}
@POST
@Path("strlen")
@Produces(MediaType.APPLICATION_JSON)
public JsonNode string(int len) {
return new TextNode(String.valueOf(new MyEntity(len).getValue()));
}
@POST
@Path("entity")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public MyEntity number(MyEntity entity) {
return new MyEntity(4);
}
}
static class MyEntity {
private int value;
// For Jackson
MyEntity() {
}
MyEntity(int length) {
int val = 0;
for (int i = 1, j = 1; i != length + 1; i++, j++) {
if (j == 10) {
j = 0;
}
val = 10 * val + j;
}
this.value = val;
}
@JsonGetter("value")
public int getValue() {
return value;
}
}
static class MyStreamReadConstraintsExceptionMapper implements ExceptionMapper<StreamConstraintsException> {
@Override
public Response toResponse(StreamConstraintsException exception) {
return Response.ok().entity(exception.getMessage()).build();
}
}
static class MyStreamReadConstraints implements ContextResolver<ObjectMapper> {
@Override
public ObjectMapper getContext(Class<?> type) {
final List<Module> modules = ObjectMapper.findModules();
return new ObjectMapper(JsonFactory.builder().streamReadConstraints(
StreamReadConstraints.builder().maxNumberLength(4).build()
).build()).registerModules(modules);
}
}
}