Skip to content

Instantly share code, notes, and snippets.

@psamsotha
Created December 30, 2015 12:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save psamsotha/d9a500e75cf25f30caf3 to your computer and use it in GitHub Desktop.
Save psamsotha/d9a500e75cf25f30caf3 to your computer and use it in GitHub Desktop.
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import org.glassfish.hk2.api.AnnotationLiteral;
import org.glassfish.hk2.api.Factory;
import org.glassfish.hk2.api.InjectionResolver;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.hk2.api.TypeLiteral;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.filter.LoggingFilter;
import org.glassfish.jersey.internal.inject.ExtractorException;
import org.glassfish.jersey.server.ParamException;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.internal.inject.AbstractContainerRequestValueFactory;
import org.glassfish.jersey.server.internal.inject.AbstractValueFactoryProvider;
import org.glassfish.jersey.server.internal.inject.MultivaluedParameterExtractor;
import org.glassfish.jersey.server.internal.inject.MultivaluedParameterExtractorProvider;
import org.glassfish.jersey.server.internal.inject.ParamInjectionResolver;
import org.glassfish.jersey.server.model.Parameter;
import org.glassfish.jersey.server.spi.internal.ValueFactoryProvider;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
/**
* Stack Overflow Question http://stackoverflow.com/q/34526062/2587435
*
* This can be run like any other JUnit test. Only dependency needed:
*
* <dependency>
* <groupId>org.glassfish.jersey.test-framework.providers</groupId>
* <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
* <version>${jersey2.version}</version>
* <scope>test</scope>
* </dependency>
*/
public class VaryingParamTest extends JerseyTest {
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public static @interface VaryingParam {
String value();
@SuppressWarnings("AnnotationAsSuperInterface")
public static class Factory
extends AnnotationLiteral<VaryingParam> implements VaryingParam {
private final String value;
public static VaryingParam create(final String newValue) {
return new Factory(newValue);
}
public Factory(String newValue) {
this.value = newValue;
}
@Override
public String value() {
return this.value;
}
}
}
public static class VaryingParamValueFactoryProvider extends AbstractValueFactoryProvider {
@Inject
public VaryingParamValueFactoryProvider(
final MultivaluedParameterExtractorProvider mpep,
final ServiceLocator locator) {
super(mpep, locator, Parameter.Source.UNKNOWN);
}
@Override
protected Factory<?> createValueFactory(final Parameter parameter) {
VaryingParam annotation = parameter.getAnnotation(VaryingParam.class);
if (annotation == null) {
return null;
}
String value = annotation.value();
if (value == null || value.length() == 0) {
return null;
}
String[] variations = value.split("\\s*\\|\\s*");
return new VaryingParamFactory(variations, parameter);
}
private static Parameter cloneParameter(final Parameter original, final String value) {
Annotation[] annotations = changeVaryingParam(original.getAnnotations(), value);
Parameter clone = Parameter.create(
original.getRawType(),
original.getRawType(),
true,
original.getRawType(),
original.getRawType(),
annotations);
return clone;
}
private static Annotation[] changeVaryingParam(final Annotation[] annos, final String value) {
for (int i = 0; i < annos.length; i++) {
if (annos[i] instanceof VaryingParam) {
annos[i] = VaryingParam.Factory.create(value);
break;
}
}
return annos;
}
private class VaryingParamFactory extends AbstractContainerRequestValueFactory<Object> {
private final String[] variations;
private final Parameter parameter;
private final boolean decode;
private final Class<?> paramType;
private final boolean isList;
private final boolean isSet;
VaryingParamFactory(final String[] variations, final Parameter parameter) {
this.variations = variations;
this.parameter = parameter;
this.decode = !parameter.isEncoded();
this.paramType = parameter.getRawType();
this.isList = paramType == List.class;
this.isSet = paramType == Set.class;
}
@Override
public Object provide() {
MultivaluedParameterExtractor<?> e = null;
try {
Object value = null;
MultivaluedMap<String, String> params
= getContainerRequest().getUriInfo().getQueryParameters(decode);
for (String variant : variations) {
e = get(cloneParameter(parameter, variant));
if (e == null) {
return null;
}
if (isList) {
List list = (List<?>) e.extract(params);
if (value == null) {
value = new ArrayList();
}
((List<?>) value).addAll(list);
} else if (isSet) {
Set set = (Set<?>) e.extract(params);
if (value == null) {
value = new HashSet();
}
((Set<?>) value).addAll(set);
} else {
value = e.extract(params);
if (value != null) {
return value;
}
}
}
return value;
} catch (ExtractorException ex) {
if (e == null) {
throw new ParamException.QueryParamException(ex.getCause(),
parameter.getSourceName(), parameter.getDefaultValue());
} else {
throw new ParamException.QueryParamException(ex.getCause(),
e.getName(), e.getDefaultValueString());
}
}
}
}
private static class Resolver extends ParamInjectionResolver<VaryingParam> {
public Resolver() {
super(VaryingParamValueFactoryProvider.class);
}
}
public static class Binder extends AbstractBinder {
@Override
protected void configure() {
bind(VaryingParamValueFactoryProvider.class)
.to(ValueFactoryProvider.class)
.in(Singleton.class);
bind(VaryingParamValueFactoryProvider.Resolver.class)
.to(new TypeLiteral<InjectionResolver<VaryingParam>>() {
})
.in(Singleton.class);
}
}
}
@Path("test-query")
public static class QueryTestResource {
@GET
@Path("1")
public String getFirstVariant(@VaryingParam("param-1|param-2|param-3") Integer value) {
return String.valueOf(value);
}
@GET
@Path("2")
public String getSecondVariant(@VaryingParam("param-1|param-2|param-3") String value) {
return String.valueOf(value);
}
@GET
@Path("3")
public String getThirdVariant(@VaryingParam("param-1|param-2|param-3") Boolean value) {
return String.valueOf(value);
}
/*
* Currently doesn't support. Uncomment failing `@Test` below to
* see it fail. Use `List` or `Set` instead.
*/
@GET
@Path("multiple")
public String getMultipleVariants(@VaryingParam("param-1|param-2|param-3") String value1,
@VaryingParam("param-1|param-2|param-3") String value2) {
return value1 + ":" + value2;
}
@GET
@Path("default")
public String getDefaultValue(@DefaultValue("default")
@VaryingParam("param-1|param-2|param-3") String value) {
return value;
}
@GET
@Path("list")
public String getListOfValues(@VaryingParam("param-1|param-2|param-3") List<String> values) {
return values.toString();
}
@GET
@Path("list-multiples")
public String getListOfMultiples(@VaryingParam("param-1|param-2|param-3") List<String> values) {
return values.toString();
}
}
@Override
public ResourceConfig configure() {
return new ResourceConfig(QueryTestResource.class)
.register(new LoggingFilter(Logger.getAnonymousLogger(), true))
.register(new VaryingParamValueFactoryProvider.Binder());
}
@Test
public void should_return_first_variant() {
Response response = target("test-query/1").queryParam("param-1", "12345")
.request().get();
assertEquals(200, response.getStatus());
assertEquals("12345", response.readEntity(String.class));
response.close();
}
@Test
public void should_return_second_variant() {
Response response = target("test-query/2").queryParam("param-2", "value")
.request().get();
assertEquals(200, response.getStatus());
assertEquals("value", response.readEntity(String.class));
response.close();
}
@Test
public void should_return_third_variant() {
Response response = target("test-query/3").queryParam("param-3", "true")
.request().get();
assertEquals(200, response.getStatus());
assertEquals("true", response.readEntity(String.class));
response.close();
}
/**
* Fails! But how would you expect it to work? You are saying that the
* parameter value can be any of three, so we just pass you the first one.
*/
//@Test
public void should_return_multiple_variants() {
Response response = target("test-query/multiple")
.queryParam("param-1", "hello")
.queryParam("param-3", "world")
.request().get();
assertEquals(200, response.getStatus());
assertEquals("hello:world", response.readEntity(String.class));
response.close();
}
@Test
public void should_return_default_value() {
Response response = target("test-query/default")
.request().get();
assertEquals(200, response.getStatus());
assertEquals("default", response.readEntity(String.class));
response.close();
}
@Test
public void should_return_list_of_variants() {
Response response = target("test-query/list")
.queryParam("param-1", "hello")
.queryParam("param-3", "world")
.request().get();
assertEquals(200, response.getStatus());
String result = response.readEntity(String.class);
assertTrue(result.contains("hello"));
assertTrue(result.contains("world"));
response.close();
}
@Test
public void should_return_list_multiple_of_variants() {
Response response = target("test-query/list")
.queryParam("param-1", "hello")
.queryParam("param-3", "world")
.queryParam("param-1", "hello1")
.queryParam("param-3", "world2")
.request().get();
assertEquals(200, response.getStatus());
String result = response.readEntity(String.class);
assertTrue(result.contains("hello"));
assertTrue(result.contains("world"));
assertTrue(result.contains("hello1"));
assertTrue(result.contains("world2"));
response.close();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment