diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/inject/ParamConverters.java b/core-common/src/main/java/org/glassfish/jersey/internal/inject/ParamConverters.java index 8fdfca9de0..3247d2425d 100644 --- a/core-common/src/main/java/org/glassfish/jersey/internal/inject/ParamConverters.java +++ b/core-common/src/main/java/org/glassfish/jersey/internal/inject/ParamConverters.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2018 Payara Foundation and/or its affiliates. * * This program and the accompanying materials are made available under the @@ -279,11 +279,15 @@ public T fromString(String value) { } else { final List ctps = ReflectionHelper.getTypeArgumentAndClass(genericType); final ClassTypePair ctp = (ctps.size() == 1) ? ctps.get(0) : null; - + final boolean empty = value.isEmpty(); for (ParamConverterProvider provider : Providers.getProviders(manager, ParamConverterProvider.class)) { final ParamConverter converter = provider.getConverter(ctp.rawClass(), ctp.type(), annotations); if (converter != null) { - return (T) Optional.of(value).map(s -> converter.fromString(value)); + if (empty) { + return (T) Optional.empty(); + } else { + return (T) Optional.of(value).map(s -> converter.fromString(value)); + } } } /* @@ -322,7 +326,7 @@ public ParamConverter getConverter(Class rawType, Type genericType, An @Override public T fromString(String value) { - if (value == null) { + if (value == null || value.isEmpty()) { return (T) optionals.empty(); } else { return (T) optionals.of(value); diff --git a/tests/e2e-server/src/test/java/org/glassfish/jersey/tests/e2e/server/Issue4790Test.java b/tests/e2e-server/src/test/java/org/glassfish/jersey/tests/e2e/server/Issue4790Test.java new file mode 100644 index 0000000000..dbd455ef3b --- /dev/null +++ b/tests/e2e-server/src/test/java/org/glassfish/jersey/tests/e2e/server/Issue4790Test.java @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2021, 2022 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.tests.e2e.server; + +import static org.junit.Assert.assertEquals; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Application; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.Test; + +public class Issue4790Test extends JerseyTest { + + @Override + protected Application configure() { + return new ResourceConfig(TestResource.class); + } + + @Test + public void testString() { + String paramMissing = target("/string").request().get(String.class); + assertEquals("null", paramMissing); + + String paramProvided = target("/string").queryParam("s", "42").request().get(String.class); + assertEquals("s was 42", paramProvided); + + String paramEmpty = target("/string").queryParam("s", "").request().get(String.class); + assertEquals("s was ", paramEmpty); + } + + @Test + public void testOptionalInteger() { + String paramMissing = target("/optionalInteger").request().get(String.class); + assertEquals("default", paramMissing); + + String paramProvided = target("/optionalInteger").queryParam("i", 42).request().get(String.class); + assertEquals("i was 42", paramProvided); + + Response paramInvalidResponse = target("/optionalInteger").queryParam("i", "not-a-number").request().get(); + assertEquals(404, paramInvalidResponse.getStatus()); + + Response paramEmptyResponse = target("/optionalInteger").queryParam("i", "").request().get(); + assertEquals(200, paramEmptyResponse.getStatus()); + } + + @Test + public void testInteger() { + String paramMissing = target("/integer").request().get(String.class); + assertEquals("null", paramMissing); + + String paramProvided = target("/integer").queryParam("i", 42).request().get(String.class); + assertEquals("i was 42", paramProvided); + + Response paramInvalidResponse = target("/integer").queryParam("i", "not-a-number").request().get(); + assertEquals(404, paramInvalidResponse.getStatus()); + + Response paramEmptyResponse = target("/integer").queryParam("i", "").request().get(); + assertEquals(200, paramEmptyResponse.getStatus()); + } + + @Test + public void testPrimitiveInt() { + String paramMissing = target("/int").request().get(String.class); + assertEquals("i was 0", paramMissing); + + String paramProvided = target("/int").queryParam("i", 42).request().get(String.class); + assertEquals("i was 42", paramProvided); + + Response paramInvalidResponse = target("/int").queryParam("i", "not-a-number").request().get(); + assertEquals(404, paramInvalidResponse.getStatus()); + + String paramEmpty = target("/int").queryParam("i", "").request().get(String.class); + assertEquals("i was 0", paramEmpty); + } + + @Test + public void emptyStringQueryParamReturnsListWithOneNullElement() { + Response response = target("/uuid") + .queryParam("list", "") + .request() + .get(); + assertEquals(200, response.getStatus()); + assertEquals("1: [null]", + response.readEntity(String.class)); + } + + @Test + public void emptyStringQueryParamReturnsListWithNullInside() { + Response response = target("/uuid") + .queryParam("list", "ec0cf621-d744-4a1c-b1d8-4b8a44b3dad7", "", "ac0cf621-d744-4a1c-b1d8-4b8a44b3dad7") + .request() + .get(); + assertEquals(200, response.getStatus()); + assertEquals("3: [ec0cf621-d744-4a1c-b1d8-4b8a44b3dad7, null, ac0cf621-d744-4a1c-b1d8-4b8a44b3dad7]", + response.readEntity(String.class)); + } + + @Test + public void missingQueryParamReturnsEmptyList() { + Response response = target("/uuid") + .request() + .get(); + assertEquals(200, response.getStatus()); + assertEquals("0: []", response.readEntity(String.class)); + } + + @Test + public void filledQueryParamReturnsListWithOneElement() { + Response response = target("/uuid") + .queryParam("list", "ec0cf621-d744-4a1c-b1d8-4b8a44b3dad7") + .request() + .get(); + assertEquals(200, response.getStatus()); + assertEquals("1: [ec0cf621-d744-4a1c-b1d8-4b8a44b3dad7]", response.readEntity(String.class)); + } + + @Produces(MediaType.TEXT_PLAIN) + @Path("/") + public static class TestResource { + @GET + @Path("/optionalInteger") + public String optionalInt(@QueryParam("i") Optional i) { + if (i == null) { + return "null"; + } + return i.map(param -> "i was " + param).orElse("default"); + } + + @GET + @Path("/integer") + public String integer(@QueryParam("i") Integer i) { + if (i == null) { + return "null"; + } + return "i was " + i; + } + + @GET + @Path("/int") + public String primitiveInt(@QueryParam("i") int i) { + return "i was " + i; + } + + @GET + @Path("/uuid") + public String uuid(@QueryParam("list") List list) { + return list.size() + ": " + list.toString(); + } + + @GET + @Path("/string") + public String string(@QueryParam("s") String s) { + if (s == null) { + return "null"; + } + return "s was " + s; + } + + } +} diff --git a/tests/e2e-server/src/test/java/org/glassfish/jersey/tests/e2e/server/OptionalParamConverterTest.java b/tests/e2e-server/src/test/java/org/glassfish/jersey/tests/e2e/server/OptionalParamConverterTest.java index 5306b217a5..257377f007 100644 --- a/tests/e2e-server/src/test/java/org/glassfish/jersey/tests/e2e/server/OptionalParamConverterTest.java +++ b/tests/e2e-server/src/test/java/org/glassfish/jersey/tests/e2e/server/OptionalParamConverterTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2022 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 @@ -156,8 +156,8 @@ public void fromOptionalDate() { Response invalid = target("/OptionalResource/fromDate").queryParam(PARAM_NAME, "invalid").request().get(); assertEquals(200, missing.getStatus()); assertEquals(new Date(1609459200000L), missing.readEntity(Date.class)); - assertEquals(404, empty.getStatus()); - assertFalse(empty.hasEntity()); + assertEquals(200, empty.getStatus()); + assertEquals(new Date(1609459200000L), empty.readEntity(Date.class)); assertEquals(200, notEmpty.getStatus()); assertEquals(new Date(1619870400000L), notEmpty.readEntity(Date.class)); assertEquals(404, invalid.getStatus()); @@ -173,8 +173,8 @@ public void fromOptionalInstant() { Response invalid = target("/OptionalResource/fromInstant").queryParam(PARAM_NAME, "invalid").request().get(); assertEquals(200, missing.getStatus()); assertEquals("2021-01-01T00:00:00Z", missing.readEntity(String.class)); - assertEquals(404, empty.getStatus()); - assertFalse(empty.hasEntity()); + assertEquals(200, empty.getStatus()); + assertEquals("2021-01-01T00:00:00Z", empty.readEntity(String.class)); assertEquals(200, notEmpty.getStatus()); assertEquals("2021-05-01T12:00:00Z", notEmpty.readEntity(String.class)); assertEquals(404, invalid.getStatus()); diff --git a/tests/integration/jersey-2612/src/main/java/org/glassfish/jersey/tests/integration/jersey2612/OptionalParamConverterProvider.java b/tests/integration/jersey-2612/src/main/java/org/glassfish/jersey/tests/integration/jersey2612/OptionalParamConverterProvider.java index 92849cf1cc..589aab6790 100644 --- a/tests/integration/jersey-2612/src/main/java/org/glassfish/jersey/tests/integration/jersey2612/OptionalParamConverterProvider.java +++ b/tests/integration/jersey-2612/src/main/java/org/glassfish/jersey/tests/integration/jersey2612/OptionalParamConverterProvider.java @@ -63,13 +63,16 @@ public String toString(final T value) throws IllegalArgumentException { final Set converterProviders = Providers.getProviders(injectionManager, ParamConverterProvider.class); for (ParamConverterProvider provider : converterProviders) { - @SuppressWarnings("unchecked") final ParamConverter converter = provider.getConverter(ctp.rawClass(), ctp.type(), annotations); if (converter != null) { return new ParamConverter() { @Override public T fromString(final String value) { - return rawType.cast(Optional.ofNullable(value).map(o -> converter.fromString(value))); + if (value.isEmpty()) { + return rawType.cast(Optional.empty()); + } else { + return rawType.cast(Optional.ofNullable(value).map(o -> converter.fromString(value))); + } } @Override