users@jersey.java.net

Re: [Jersey] _at_QueryParam binding with Java Enum

From: Paul Sandoz <Paul.Sandoz_at_Sun.COM>
Date: Wed, 29 Oct 2008 12:32:34 +0100

On Oct 28, 2008, at 10:50 PM, Jeff Schmidt wrote:

> Hello:
>
> This seems like such a basic thing to want, that I'm probably just
> missing something there. I have a method in a resource defined as:
>
> @GET
> public AnalysesList getInfoForAllAnalyses(
> @Context UriInfo uriInfo,
> @QueryParam("analysistype") AnalysisType analysisType) {
>
> ...
> }
>
> AnalysisType is a pretty simple Enum:
>
> @XmlEnum
> public enum AnalysisType {
>
> @XmlEnumValue("Core")
> CORE("Core"),
> @XmlEnumValue("Tox")
> TOX("Tox"),
> @XmlEnumValue("Metabolomics")
> METABOLOMICS("Metabolomics")
> ;
>
> private final String value;
>
> AnalysisType(String v) {
> value = v;
> }
>
> public String value() {
> return value;
> }
>
> public static AnalysisType fromValue(String v) {
> for (AnalysisType c: AnalysisType.values()) {
> if (c.value.equals(v)) {
> return c;
> }
> }
> throw new IllegalArgumentException(v.toString());
> }
>
> /*
> public static AnalysisType valueOf(String typeStr) {
> return AnalysisType.fromValue(typeStr);
> }*/
> }
>
> Note this Enum was originally derived from an XML schema used in a
> SOAP API (using JAXB xjc), and it created the fromValue() method,
> which I assume JAXB uses to bind a String representation to an
> actual Enum instance. I would also like to bind a JAX-RS query
> parameter to this Enum using Jersey. Making a request with the query
> parameter defined as analysistype=CORE works great. However, I want
> to refer to the type as "Core" (analysistype=Core) to match the
> CamelCase XML standard we're using. However, this results in 404
> (Not Found).
>
> I don't want to have to change the Enum itself and dependent code
> to switch from AnalysisType.CORE to AnalysisType.Core. From what I
> can tell, a given Java class can be bound by Jersey if it has a
> static valueOf(String) method. However, Enum will not let me define
> this method as the compiler says its already defined. I'm guessing
> that's how Jersey is able to bind as explained earlier; based on the
> actual name of the Enum elements.

Yes, i think that is what is going on.


> Also, I cannot make a constructor available to Jersey, given it's an
> Enum.
>
> Is there a good way to handle this in Jersey? Perhaps there is some
> kind of provider or resolver I have to implement on my own?
>

The simplest approach is to create a wrapper class supporting the
static valueOf or a constructor taking a String argument.

You can register an injectable for the @QueryParam and the AnalysisType:

     @Provider
     public static class QueryParamInjectableProvider implements
             InjectableProvider<QueryParam, Parameter> {

         private final @Context HttpContext hc;

         public QueryParamInjectableProvider(@Context HttpContext hc) {
             this.hc = hc;
         }

         public ComponentScope getScope() {
             return ComponentScope.PerRequest;
         }

         public Injectable<AnalysisType>
getInjectable(ComponentContext ic,
                 QueryParam a, Parameter c) {
             if (AnalysisType.class != c.getParameterClass())
                 return null;

             final String name = c.getSourceName();
             return new Injectable<AnalysisType>() {
                 public AnalysisType getValue() {
                     String value =
hc.getUriInfo().getQueryParameters().getFirst(name);

                     return AnalysisType.fromValue(name);
                 }
             };
         }
     }

Note that the above does not support default values (in the absence of
the query parameter), but it is easy to add, and it should throw a 404-
based exception if the value is not recognized.

I am also wondering whether it should be possible to add support for
any enum with a particular method like fromValue.

     @Provider
     public static class QueryParamInjectableProvider implements
             InjectableProvider<QueryParam, Parameter> {

         private final @Context HttpContext hc;

         public QueryParamInjectableProvider(@Context HttpContext hc) {
             this.hc = hc;
         }

         public ComponentScope getScope() {
             return ComponentScope.PerRequest;
         }

         public Injectable<? extends Enum>
getInjectable(ComponentContext ic,
                 QueryParam a, Parameter c) {
             if
(AnalysisType.class.isAssignableFrom(c.getParameterClass())
                 return null;

             ....
         }
     }


Hope this helps,
Paul.