Created
December 30, 2015 12:12
-
-
Save psamsotha/d9a500e75cf25f30caf3 to your computer and use it in GitHub Desktop.
Stack Overflow Question http://stackoverflow.com/q/34526062/2587435
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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