users@jersey.java.net

Re: [Jersey] _at_QueryParam binding with Java Enum

From: Paul Sandoz <Paul.Sandoz_at_Sun.COM>
Date: Thu, 30 Oct 2008 18:04:32 +0100

On Oct 30, 2008, at 4:58 PM, Jeff Schmidt wrote:

> Hi Paul:
>
> Great suggestions! I have incorporated them.
>
> Is it possible for an ExceptionMapper provider to handle exceptions
> thrown by other providers?

It should be possible... but we have to be careful...


> 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.
>

For @*Para when calling a constructor or valueOf method then only
WebApplicationException is passed through all other exceptions thrown
are treated as client errors with a WebApplicationException that wraps
the other exception i.e. we are trying to ensure that in such
circumstances the right thing occurs.

Note that you can map wrapped exceptions provided by WebApplication.
Create a mapper for WebApplicationException, and inject in it the
ExceptionMapper and use that to map the wrapped exception.

I need to review the areas where injectable providers are called
(construction, injection, method invocation). I guess it should be OK
for any injectable provider to thrown an exception to be mapped under
any context of injection.

Paul.

> 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
>>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>
>