Skip to content

Commit

Permalink
Add a ParamConverterProvider for java.util.Optional parameters
Browse files Browse the repository at this point in the history
Signed-off-by: Jorge Bescos Gascon <[email protected]>
  • Loading branch information
jbescos committed Jan 20, 2021
1 parent 500c0d7 commit 7919487
Show file tree
Hide file tree
Showing 2 changed files with 160 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2021 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
Expand All @@ -21,21 +21,23 @@
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.security.AccessController;
import java.text.ParseException;
import java.util.Date;
import java.util.Optional;

import javax.inject.Inject;
import javax.inject.Singleton;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.ext.ParamConverter;
import javax.ws.rs.ext.ParamConverterProvider;

import javax.inject.Singleton;

import org.glassfish.jersey.internal.LocalizationMessages;
import org.glassfish.jersey.internal.util.ReflectionHelper;
import org.glassfish.jersey.message.internal.HttpDateFormat;
import org.glassfish.jersey.internal.LocalizationMessages;

/**
* Container of several different {@link ParamConverterProvider param converter providers}
Expand Down Expand Up @@ -247,6 +249,59 @@ public String toString(final T value) throws IllegalArgumentException {
}
}

/**
* Provider of {@link ParamConverter param converter} that produce the Optional instance
* by invoking {@link AggregatedProvider}.
*/
@Singleton
public static class OptionalProvider implements ParamConverterProvider {

// Delegates to this provider when the type of Optional is extracted.
private final AggregatedProvider aggregated;

@Inject
public OptionalProvider(AggregatedProvider aggregated) {
this.aggregated = aggregated;
}

@Override
public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType, Annotation[] annotations) {
return (rawType != Optional.class) ? null : new ParamConverter<T>() {

@Override
public T fromString(String value) {
if (value == null) {
return (T) Optional.empty();
} else {
ParameterizedType parametrized = (ParameterizedType) genericType;
Type type = parametrized.getActualTypeArguments()[0];
T val = aggregated.getConverter((Class<T>) type, type, annotations).fromString(value.toString());
if (val != null) {
return (T) Optional.of(val);
} else {
/*
* In this case we don't send Optional.empty() because 'value' is not null.
* But we return null because the provider didn't find how to parse it.
*/
return null;
}
}
}

@Override
public String toString(T value) throws IllegalArgumentException {
/*
* Unfortunately 'orElse' cannot be stored in an Optional. As only one value can
* be stored, it makes no sense that 'value' is Optional. It can just be the value.
* We don't fail here but we don't process it.
*/
return null;
}
};
}

}

/**
* Aggregated {@link ParamConverterProvider param converter provider}.
*/
Expand All @@ -267,7 +322,8 @@ public AggregatedProvider() {
new TypeValueOf(),
new CharacterProvider(),
new TypeFromString(),
new StringConstructor()
new StringConstructor(),
new OptionalProvider(this)
};
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright (c) 2021 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 java.util.List;
import java.util.Optional;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Response;

import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;

import static org.junit.Assert.assertEquals;

public class OptionalParamConverterTest extends JerseyTest {

private static final String PARAM_NAME = "paramName";

@Path("/OptionalResource")
public static class OptionalResource {

@GET
@Path("/fromString")
public Response fromString(@QueryParam(PARAM_NAME) Optional<String> data) {
return Response.ok(data.orElse("")).build();
}

@GET
@Path("/fromInteger")
public Response fromInteger(@QueryParam(PARAM_NAME) Optional<Integer> data) {
return Response.ok(data.orElse(0)).build();
}

@GET
@Path("/fromList")
public Response fromList(@QueryParam(PARAM_NAME) List<Optional<Integer>> data) {
StringBuilder builder = new StringBuilder("");
for (Optional<Integer> val : data) {
builder.append(val.orElse(0));
}
return Response.ok(builder.toString()).build();
}
}

@Override
protected Application configure() {
return new ResourceConfig(OptionalResource.class);
}

@Test
public void fromOptionalStr() {
Response empty = target("/OptionalResource/fromString").request().get();
Response notEmpty = target("/OptionalResource/fromString").queryParam(PARAM_NAME, "anyValue").request().get();
assertEquals(200, empty.getStatus());
assertEquals("", empty.readEntity(String.class));
assertEquals(200, notEmpty.getStatus());
assertEquals("anyValue", notEmpty.readEntity(String.class));
}

@Test
public void fromOptionalInt() {
Response empty = target("/OptionalResource/fromInteger").request().get();
Response notEmpty = target("/OptionalResource/fromInteger").queryParam(PARAM_NAME, 1).request().get();
assertEquals(200, empty.getStatus());
assertEquals(Integer.valueOf(0), empty.readEntity(Integer.class));
assertEquals(200, notEmpty.getStatus());
assertEquals(Integer.valueOf(1), notEmpty.readEntity(Integer.class));
}

@Test
public void fromOptionalList() {
Response empty = target("/OptionalResource/fromList").request().get();
Response notEmpty = target("/OptionalResource/fromList").queryParam(PARAM_NAME, 1)
.queryParam(PARAM_NAME, 2).request().get();
assertEquals(200, empty.getStatus());
assertEquals("", empty.readEntity(String.class));
assertEquals(200, notEmpty.getStatus());
assertEquals("12", notEmpty.readEntity(String.class));
}
}

0 comments on commit 7919487

Please sign in to comment.