Skip to content

Commit 728e753

Browse files
jbescossenivam
authored andcommitted
Add a ParamConverterProvider for java.util.Optional parameters
Signed-off-by: Jorge Bescos Gascon <jorge.bescos.gascon@oracle.com>
1 parent 2df2133 commit 728e753

File tree

2 files changed

+160
-5
lines changed

2 files changed

+160
-5
lines changed

core-common/src/main/java/org/glassfish/jersey/internal/inject/ParamConverters.java

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved.
33
* Copyright (c) 2018 Payara Foundation and/or its affiliates.
44
*
55
* This program and the accompanying materials are made available under the
@@ -21,21 +21,23 @@
2121
import java.lang.reflect.Constructor;
2222
import java.lang.reflect.InvocationTargetException;
2323
import java.lang.reflect.Method;
24+
import java.lang.reflect.ParameterizedType;
2425
import java.lang.reflect.Type;
2526
import java.security.AccessController;
2627
import java.text.ParseException;
2728
import java.util.Date;
29+
import java.util.Optional;
2830

31+
import javax.inject.Inject;
32+
import javax.inject.Singleton;
2933
import javax.ws.rs.ProcessingException;
3034
import javax.ws.rs.WebApplicationException;
3135
import javax.ws.rs.ext.ParamConverter;
3236
import javax.ws.rs.ext.ParamConverterProvider;
3337

34-
import javax.inject.Singleton;
35-
38+
import org.glassfish.jersey.internal.LocalizationMessages;
3639
import org.glassfish.jersey.internal.util.ReflectionHelper;
3740
import org.glassfish.jersey.message.internal.HttpDateFormat;
38-
import org.glassfish.jersey.internal.LocalizationMessages;
3941

4042
/**
4143
* Container of several different {@link ParamConverterProvider param converter providers}
@@ -247,6 +249,59 @@ public String toString(final T value) throws IllegalArgumentException {
247249
}
248250
}
249251

252+
/**
253+
* Provider of {@link ParamConverter param converter} that produce the Optional instance
254+
* by invoking {@link AggregatedProvider}.
255+
*/
256+
@Singleton
257+
public static class OptionalProvider implements ParamConverterProvider {
258+
259+
// Delegates to this provider when the type of Optional is extracted.
260+
private final AggregatedProvider aggregated;
261+
262+
@Inject
263+
public OptionalProvider(AggregatedProvider aggregated) {
264+
this.aggregated = aggregated;
265+
}
266+
267+
@Override
268+
public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType, Annotation[] annotations) {
269+
return (rawType != Optional.class) ? null : new ParamConverter<T>() {
270+
271+
@Override
272+
public T fromString(String value) {
273+
if (value == null) {
274+
return (T) Optional.empty();
275+
} else {
276+
ParameterizedType parametrized = (ParameterizedType) genericType;
277+
Type type = parametrized.getActualTypeArguments()[0];
278+
T val = aggregated.getConverter((Class<T>) type, type, annotations).fromString(value.toString());
279+
if (val != null) {
280+
return (T) Optional.of(val);
281+
} else {
282+
/*
283+
* In this case we don't send Optional.empty() because 'value' is not null.
284+
* But we return null because the provider didn't find how to parse it.
285+
*/
286+
return null;
287+
}
288+
}
289+
}
290+
291+
@Override
292+
public String toString(T value) throws IllegalArgumentException {
293+
/*
294+
* Unfortunately 'orElse' cannot be stored in an Optional. As only one value can
295+
* be stored, it makes no sense that 'value' is Optional. It can just be the value.
296+
* We don't fail here but we don't process it.
297+
*/
298+
return null;
299+
}
300+
};
301+
}
302+
303+
}
304+
250305
/**
251306
* Aggregated {@link ParamConverterProvider param converter provider}.
252307
*/
@@ -267,7 +322,8 @@ public AggregatedProvider() {
267322
new TypeValueOf(),
268323
new CharacterProvider(),
269324
new TypeFromString(),
270-
new StringConstructor()
325+
new StringConstructor(),
326+
new OptionalProvider(this)
271327
};
272328
}
273329

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Copyright (c) 2021 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.tests.e2e.server;
18+
19+
import java.util.List;
20+
import java.util.Optional;
21+
22+
import javax.ws.rs.GET;
23+
import javax.ws.rs.Path;
24+
import javax.ws.rs.QueryParam;
25+
import javax.ws.rs.core.Application;
26+
import javax.ws.rs.core.Response;
27+
28+
import org.glassfish.jersey.server.ResourceConfig;
29+
import org.glassfish.jersey.test.JerseyTest;
30+
import org.junit.Test;
31+
32+
import static org.junit.Assert.assertEquals;
33+
34+
public class OptionalParamConverterTest extends JerseyTest {
35+
36+
private static final String PARAM_NAME = "paramName";
37+
38+
@Path("/OptionalResource")
39+
public static class OptionalResource {
40+
41+
@GET
42+
@Path("/fromString")
43+
public Response fromString(@QueryParam(PARAM_NAME) Optional<String> data) {
44+
return Response.ok(data.orElse("")).build();
45+
}
46+
47+
@GET
48+
@Path("/fromInteger")
49+
public Response fromInteger(@QueryParam(PARAM_NAME) Optional<Integer> data) {
50+
return Response.ok(data.orElse(0)).build();
51+
}
52+
53+
@GET
54+
@Path("/fromList")
55+
public Response fromList(@QueryParam(PARAM_NAME) List<Optional<Integer>> data) {
56+
StringBuilder builder = new StringBuilder("");
57+
for (Optional<Integer> val : data) {
58+
builder.append(val.orElse(0));
59+
}
60+
return Response.ok(builder.toString()).build();
61+
}
62+
}
63+
64+
@Override
65+
protected Application configure() {
66+
return new ResourceConfig(OptionalResource.class);
67+
}
68+
69+
@Test
70+
public void fromOptionalStr() {
71+
Response empty = target("/OptionalResource/fromString").request().get();
72+
Response notEmpty = target("/OptionalResource/fromString").queryParam(PARAM_NAME, "anyValue").request().get();
73+
assertEquals(200, empty.getStatus());
74+
assertEquals("", empty.readEntity(String.class));
75+
assertEquals(200, notEmpty.getStatus());
76+
assertEquals("anyValue", notEmpty.readEntity(String.class));
77+
}
78+
79+
@Test
80+
public void fromOptionalInt() {
81+
Response empty = target("/OptionalResource/fromInteger").request().get();
82+
Response notEmpty = target("/OptionalResource/fromInteger").queryParam(PARAM_NAME, 1).request().get();
83+
assertEquals(200, empty.getStatus());
84+
assertEquals(Integer.valueOf(0), empty.readEntity(Integer.class));
85+
assertEquals(200, notEmpty.getStatus());
86+
assertEquals(Integer.valueOf(1), notEmpty.readEntity(Integer.class));
87+
}
88+
89+
@Test
90+
public void fromOptionalList() {
91+
Response empty = target("/OptionalResource/fromList").request().get();
92+
Response notEmpty = target("/OptionalResource/fromList").queryParam(PARAM_NAME, 1)
93+
.queryParam(PARAM_NAME, 2).request().get();
94+
assertEquals(200, empty.getStatus());
95+
assertEquals("", empty.readEntity(String.class));
96+
assertEquals(200, notEmpty.getStatus());
97+
assertEquals("12", notEmpty.readEntity(String.class));
98+
}
99+
}

0 commit comments

Comments
 (0)