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