È possibile creare un'annotazione personalizzata. Non entrerò troppo nel modo di farlo, puoi vedere this post o this post. Fondamentalmente si basa su un'infrastruttura diversa dalla solita iniezione di dipendenza con Jersey. Puoi vedere this package dal progetto Jersey. Qui è dove vivono tutti i fornitori di iniezione che gestiscono le iniezioni @XxxParam
. Se esamini il codice sorgente, vedrai che le implementazioni sono abbastanza simili. I due collegamenti che ho fornito sopra seguono lo stesso schema, così come il codice qui sotto.
Quello che ho fatto è stato creato un un'annotazione personalizzata
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @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;
}
}
}
Può sembrare strano che io abbia una fabbrica per creare, ma questo è stato richiesto per l'attuazione del codice qui sotto, in cui ho diviso il valore di la stringa e termina creando una nuova istanza di annotazione per ogni valore di divisione.
Ecco lo ValueFactoryProvider
(che, se avete letto uno degli articoli precedenti, vedrete che è necessario per l'iniezione dei parametri del metodo personalizzato). È una classe grande, solo perché ho messo tutte le classi richieste in una singola classe, seguendo lo schema che vedi nel progetto Jersey.
public 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);
}
}
}
Sarà necessario registrare questa classe Binder
(parte inferiore della classe) con la maglia di usarlo.
Ciò che differenzia questa classe da Jersey QueryParamValueFactoryProvider
è che invece di elaborare un solo valore String dell'annotazione, divide il valore e tenta di estrarre i valori dalla mappa param di query. Il primo valore trovato verrà restituito.Se il parametro è List
o Set
, continua a continuare a cercare tutte le opzioni e ad aggiungerle all'elenco.
Per la maggior parte questo mantiene tutte le funzionalità che ci si aspetta da un'annotazione @XxxParam
. L'unica cosa che è stata difficile da implementare (quindi ho omesso di supportare questo caso d'uso), è costituita da più parametri, ad es.
@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;
}
Io in realtà non credo che dovrebbe essere difficile da attuare, se si ha realmente bisogno, è solo una questione di creazione di un nuovo MultivaluedMap
, la rimozione di un valore se viene trovato. Questo sarebbe implementato nel metodo provide()
dello VaryingParamFactory
precedente. Se hai bisogno di questo caso d'uso, puoi semplicemente usare uno List
o Set
.
Vedere this GitHub Gist (è piuttosto lungo) per un test case completo, utilizzando Jersey Test Framework. Puoi vedere tutti i casi d'uso che ho provato nel QueryTestResource
e dove registro lo Binder
con il ResourceConfig
nel metodo di prova configure()
.