users@jersey.java.net

Re: [Jersey] _at_QueryParam binding with Java Enum

From: Jeff Schmidt <jascks_at_gmail.com>
Date: Thu, 30 Oct 2008 09:58:55 -0600

Hi Paul:

Great suggestions! I have incorporated them.

Is it possible for an ExceptionMapper provider to handle exceptions
thrown by other providers? In the provider below, I'd like to throw
one my exceptions, which does not extend WebApplicationException. It
seems they don't get handled in anyway and instead I get the customary
Tomcat stack trace. When I throw such an exception is a resource
class, or some delegate, it does get handled by my ExceptionMapper
provider.

Thanks,

Jeff

On Oct 30, 2008, at 2:17 AM, Paul Sandoz wrote:

> Hi Jeff,
>
> Nice!
>
> Some tips/tricks:
>
> - You can avoid checking reflection information on each call to the
> getValue method by doing the work in
> the constructor or the getInjectable method. This is much more
> efficient and the provider can return null
> such that another injectable may get selected. i.e. different
> enum types that do not match your requirements
> might have different injectable providers.
>
> - A default value could also be obtained if there is no query
> parameter present. See the
> Parameter.getDefaultValue method.
>
> - In 1.0.1 the Injectable.getValue(HttpContext c) method has changed
> to Injectable.getValue().
>
> It seems useful to provider an abstract class for parameter
> injectables to automatically support a default value (if present).
>
> Paul.
>
> On Oct 29, 2008, at 9:07 PM, Jeff Schmidt wrote:
>
>> Thanks a lot Paul. I seem to have a provider working now, and I've
>> learned more about the Jersey runtime in the process. :) Here's
>> what I came up with, using Jersey 1.0. Maybe this can help someone
>> else. If you have other pointers or comments on this
>> implementation, I'd appreciate those as well.
>>
>> The goal is to resolve a string query parameter (could be a path
>> parameter) to a specific constant of a specific enum type. By
>> default, Jersey invokes the Enum.valueOf() method to do this, in
>> which case the parameter value must be the name of a constant. I
>> wish to use alternative naming to better match my XML schema. So,
>> I modified the resource method to accept two query parameters of
>> different Enum types, to make sure this solution works more
>> generally.
>>
>> @GET
>> public AnalysesList getInfoForAllAnalyses(
>> @Context UriInfo uriInfo,
>> @QueryParam("analysistype") AnalysisType analysisType,
>> @QueryParam("analysidduplicate")
>> AnalysisDuplicateIdResolution analysIdDuplicate) {
>>
>> I then defined a @Provider to resolve the parameter values to enum
>> constants. During startup, I can see that these two@ QueryParam
>> annotations where correctly identified by the provider:
>>
>> Parameter class 'class
>> com.mycompany.ipaws.model.analysis.AnalysisType' is an Enum!
>> Parameter class 'class
>> com.mycompany.ipaws.model.analysis.AnalysisDuplicateIdResolution'
>> is an Enum!
>>
>> The following is the provider itself. It uses reflection to invoke
>> the fromValue() method to resolve the parameter value into an enum
>> constant. If that method does not exist, then the standard
>> valueOf() method is invoked. Thus, Enums w/o the fromValue() method
>> will still work in the classic way. For example, AnalysisType is
>> defined as:
>>
>> @XmlEnum
>> @XmlType(name="AnalysisType")
>> 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());
>> }
>> }
>>
>> The value field as well as the fromValue() method were both brought
>> to my attention by looking at classes generated from XML schema via
>> the JAXB xjc command.
>>
>> The provider is defined as follows:
>>
>> @Provider
>> public class QueryParamEnumInjectableProvider implements
>> InjectableProvider<QueryParam, Parameter> {
>>
>> private final @Context HttpContext hc;
>>
>> public QueryParamEnumInjectableProvider(@Context HttpContext
>> hc) {
>> this.hc = hc;
>> }
>>
>> public Scope getScope() {
>> return Scope.PerRequest;
>> }
>>
>> public Injectable<? extends Enum>
>> getInjectable(ComponentContext ic, QueryParam a, Parameter c) {
>>
>> if
>> (java.lang.Enum.class.isAssignableFrom(c.getParameterClass())) {
>> System.out.println("Parameter class '" +
>> c.getParameterClass() + "' is an Enum!");
>> return new QueryParamEnumInjectable(a.value(),
>> c.getParameterClass());
>> }
>> else
>> return null;
>> }
>>
>> protected static class QueryParamEnumInjectable implements
>> Injectable<Enum> {
>>
>> final static String METHOD_NAME_FROM_VALUE = "fromValue";
>> final static Class[] METHOD_ARGS_FROM_VALUE = new Class[]
>> {String.class};
>>
>> final static String METHOD_NAME_VALUE_OF = "valueOf";
>> final static Class[] METHOD_ARGS_VALUE_OF = new Class[]
>> {Class.class, String.class};
>>
>> final protected String _queryParamName;
>> final protected Class _enumClass;
>>
>> QueryParamEnumInjectable(String queryParamName, Class
>> enumClass) {
>> _queryParamName = queryParamName;
>> _enumClass = enumClass;
>> }
>>
>> public Enum getValue(HttpContext context) {
>>
>> final String queryParamValue =
>> context.getUriInfo().getQueryParameters().getFirst(_queryParamName);
>> System.out.println(_queryParamName + "=" +
>> queryParamValue);
>>
>> /*
>> * Query parameter is not mandatory.
>> */
>> if (queryParamValue == null)
>> return null;
>>
>> /*
>> * Invoke optional fromValue method if present.
>> */
>> try {
>> final Method fromValueMethod =
>> _enumClass.getMethod(METHOD_NAME_FROM_VALUE, METHOD_ARGS_FROM_VALUE);
>> final Enum enumInstance =
>> (Enum)fromValueMethod.invoke(null, new Object[] { queryParamValue });
>> System.out.println(String.format("for param name:
>> %s of type: %s, returned enum instance: %s",
>> _queryParamName, _enumClass.getName(),
>> enumInstance));
>> return enumInstance;
>>
>> } catch (InvocationTargetException ex) {
>> System.out.println("Method failed with: " +
>> ex.getCause());
>> throw new NotFoundException();
>>
>> } catch (Exception ex) {
>> //Method does not exist or is otherwise not
>> invocable.
>> }
>>
>> /*
>> * No fromValue method, so invoke standard valueOf method
>> */
>> try {
>> final Method valueOfMethod =
>> _enumClass.getMethod(METHOD_NAME_VALUE_OF, METHOD_ARGS_VALUE_OF);
>> final Enum enumInstance =
>> (Enum)valueOfMethod.invoke(null, new Object[] { _enumClass,
>> queryParamValue });
>> System.out.println(String.format("for param name:
>> %s of type: %s, returned enum instance: %s",
>> _queryParamName, _enumClass.getName(),
>> enumInstance));
>> return enumInstance;
>>
>> } catch (InvocationTargetException ex) {
>> System.out.println("Method failed with: " +
>> ex.getCause());
>> throw new NotFoundException();
>>
>> } catch (Exception ex) {
>> throw new ServerErrorException("Enum failure", ex);
>> }
>> }
>> }
>> }
>>
>> When I invoke the resource method, with Jersey LoggingFilter setup:
>>
>> 2 * In-bound request received
>> 2 > GET http://localhost:8080/pa/ws/v4/rest/partner/analyses?analysistype=Core&analysidduplicate=MEDIAN
>> 2 > host: localhost:8080
>> 2 > user-agent: Jakarta Commons-HttpClient/3.1
>> 2 >
>> analysistype=Core
>> for param name: analysistype of type:
>> com.mycompany.ipaws.model.analysis.AnalysisType, returned enum
>> instance: CORE
>> analysidduplicate=MEDIAN
>> for param name: analysidduplicate of type:
>> com.mycompany.ipaws.model.analysis.AnalysisDuplicateIdResolution,
>> returned enum instance: MEDIAN
>>
>> Thus, the one provider was able to properly resolve the two
>> different enum types, one of which has the fromValue() method
>> defined, and the other does not, making use of the standard Enum
>> valueOf() method.
>>
>> Cheers,
>>
>> Jeff
>> --
>> Jeff Schmidt
>>
>> On Oct 29, 2008, at 5:32 AM, Paul Sandoz wrote:
>>
>>>
>>> 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.
>>>
>>> ---------------------------------------------------------------------
>>> To unsubscribe, e-mail: users-unsubscribe_at_jersey.dev.java.net
>>> For additional commands, e-mail: users-help_at_jersey.dev.java.net
>>>
>>
>>
>>
>>
>>
>>
>