You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@cxf.apache.org by janb <jb...@talend.com> on 2012/11/27 11:12:45 UTC

REST Method selection for different QueryParam's

Hi @all,

I'm wondering about the selection strategy in CXF for different Query
Parameter. The documentation [1] does not cover this at all.

A simple system-test provided the impression to me, that CXF has no valid
selection strategy in place for handling different query parameters. Is this
assumption correct? Should I create a Jira ticket for a better support?

Here is my sample code. No matter which parameter have been provided within
my URL, only and always the first method was selected by CXF.

@Path("/paramTest")
public class MySimpleService {

    @GET
    public String getFoo(@QueryParam("foo") String foo){
        return "foo:" + foo;
    }

    @GET
    public String getFooBar(@QueryParam("foo") String foo,
@QueryParam("bar") String bar){
        return "foo:" + foo + " bar:" + bar;
    }

    @GET
    public String getTest(@QueryParam("test") String test){
        return "test:" + test;
    }
}

[1]
http://cxf.apache.org/docs/jax-rs-basics.html#JAX-RSBasics-Overviewoftheselectionalgorithm.



--
View this message in context: http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187.html
Sent from the cxf-user mailing list archive at Nabble.com.

Re: REST Method selection for different QueryParam's

Posted by Sergey Beryozkin <sb...@gmail.com>.
Hi Jan -

I've modified the wiki as per the last email:

https://cwiki.apache.org/confluence/display/CXF20DOC/JAX-RS+Basics#JAX-RSBasics-Customselectionbetweenmultipleresources

Hope you are OK with us restricting it to the query parameters only :-).
It's definitely can be enhanced to tackle more involved combinations - 
please feel free to experiment and provide the feedback, but at the 
moment it feels like we'll better off limiting the scope of such a 
custom comparator a bit :-),
Let it sit there at the wiki a bit and I guess we can eventually add it 
to CXF if there is some demand for it.

By the way the rate algo is quite cool :-)

Thanks, Sergey


On 29/11/12 19:26, Sergey Beryozkin wrote:
> Hi Jan
>
> OK, thanks...
>
> To be honest I'd just limit it to checking query parameters only.
> As I said, there may be so many variations there, a different
> combination of query, header, form, matrix, parameters, in a single
> method, or say one method accepting 2 query params, another - two header
> params, or one method - 1 query and 2 header while second method 1 query
> and 2 header params and so forth and it just not possible IMHO to write
> a generic provider which will meaningfully work for different users...
>
> Note it makes the JAX-RS code unportable too, which may not be not a
> major issue, why would anyone want to migrate from CXF ? (:-)) but it is
> a major concern for many users - to make sure they can write a portable
> code...
>
> Yes, I think it is been quite a few times when users asked about the
> query parameters affecting the selection, and it seems like a
> possibility to expect that it can be tricky to convert some existing
> code when say expects two query parameters to JAX-RS and get it
> selected, so I'm open to having a utility comparator dedicated
> specifically to taking Query params into the consideration but that is
> as far as its capabilities are concerned...
>
> Would you agree ? If yes - please give it another try, drop the support
> for headers, and also, try JAXRSUtils.getStructuredParams, it will
> return a properly populated multivalued map which will help with
> removing your own custom parsing code
>
> Thanks, Sergey
>
>
> On 29/11/12 19:09, janb wrote:
>> Hi Sergey,
>>
>> I think we are getting close to a real solution ;-)
>>
>> Your advice was very helpful, to optimize the code. I now also added
>> support for headers, so they will also be relevant for the rating.
>>
>> Here is my code:
>>
>> package org.apache.syncope.core.rest;
>>
>> import java.io.UnsupportedEncodingException;
>> import java.net.URLDecoder;
>> import java.util.HashSet;
>> import java.util.List;
>> import java.util.Set;
>>
>> import org.apache.cxf.jaxrs.ext.ResourceComparator;
>> import org.apache.cxf.jaxrs.model.ClassResourceInfo;
>> import org.apache.cxf.jaxrs.model.OperationResourceInfo;
>> import org.apache.cxf.jaxrs.model.OperationResourceInfoComparator;
>> import org.apache.cxf.jaxrs.model.Parameter;
>> import org.apache.cxf.message.Message;
>>
>> public class QueryResourceInfoComperator extends
>> OperationResourceInfoComparator implements
>> ResourceComparator {
>>
>> public QueryResourceInfoComperator() {
>> super(null, null);
>> }
>>
>> @Override
>> public int compare(ClassResourceInfo cri1, ClassResourceInfo cri2,
>> Message message) {
>> // Leave Class selection to CXF
>> return 0;
>> }
>>
>> @Override
>> public int compare(OperationResourceInfo oper1, OperationResourceInfo
>> oper2, Message message) {
>>
>> // Check if CXF can make a decision
>> int cxfResult = super.compare(oper1, oper2);
>> if (cxfResult != 0)
>> return cxfResult;
>>
>> int op1Counter = getMatchingRate(oper1, message);
>> int op2Counter = getMatchingRate(oper2, message);
>>
>> return op1Counter == op2Counter
>> ? 0
>> : op1Counter< op2Counter
>> ? 1
>> : -1;
>> }
>>
>> /**
>> * This method calculates a number indicating a good or bad match between
>> * values provided within the request and expected method parameters. A
>> * higher number means a better match.
>> *
>> * @param operation
>> * The operation to be rated, based on contained parameterInfo
>> * values.
>> * @param message
>> * A message containing query and header values from user request
>> * @return A positive or negative number, indicating a good match between
>> * query and method
>> */
>> protected int getMatchingRate(OperationResourceInfo operation, Message
>> message) {
>>
>> List<Parameter> params = operation.getParameters();
>> if (params == null || params.size() == 0)
>> return 0;
>>
>> // Get Request QueryParams
>> Set<String> qParams = getParams((String)
>> message.get(Message.QUERY_STRING));
>> // Get Request Headers
>> java.util.Map<String, String> qHeader = (java.util.Map<String,
>> String>) message
>> .get(Message.PROTOCOL_HEADERS);
>>
>> int rate = 0;
>> for (Parameter p : params) {
>> switch (p.getType()) {
>> case QUERY:
>> if (qParams.contains(p.getName()))
>> rate += 2;
>> else if (p.getDefaultValue() == null)
>> rate -= 1;
>> break;
>> case HEADER:
>> if (qHeader.containsKey(p.getName()))
>> rate += 2;
>> else if (p.getDefaultValue() == null)
>> rate -= 1;
>> break;
>> default:
>> break;
>> }
>> }
>> return rate;
>> }
>>
>> /**
>> * @param query
>> * URL Query Example: 'key=value&key2=value2'
>> * @return A Set of all keys, contained within query.
>> */
>> protected Set<String> getParams(String query) {
>> Set<String> params = new HashSet<String>();
>> if (query == null || query.length() == 0)
>> return params;
>>
>> try {
>> for (String param : query.split("&")) {
>> String pair[] = param.split("=");
>> String key = URLDecoder.decode(pair[0], "UTF-8");
>> params.add(key);
>> }
>> } catch (UnsupportedEncodingException e) {
>> e.printStackTrace();
>> }
>> return params;
>> }
>> }
>>
>>
>> I guess it would be nice, if CXF could provide the query params in a
>> Map instead of a String, then my getParams(String query) method would
>> be obsolete.
>>
>> Regards.
>> Jan
>>
>> From: Sergey Beryozkin-5 [via CXF]
>> [mailto:ml-node+s547215n5719465h66@n5.nabble.com]
>> Sent: Donnerstag, 29. November 2012 19:02
>> To: Jan Bernhardt
>> Subject: Re: REST Method selection for different QueryParam's
>>
>> On 29/11/12 17:58, Sergey Beryozkin wrote:
>>> Hi Jan
>>>
>>> OK, thanks for all the info, looks promising...
>>> It just occurred to me, you do not have to check the annotations,
>>> OperationResourceInfo has it all cached, get the list of parameters, and
>>> check parameter types (can be query. matrix, etc), this will be much
>>> faster too, please give it a try...The parameter will have an index into
>>> an array of the Method parameter array, Method can be found from
>>> ori.getMethodToInvoke()
>>
>> Actually, you don't even need a method. Parameter will have name,
>> default value, and parameter type
>>
>> Sergey
>>
>>>
>>> Cheers, Sergey
>>>
>>>
>>>
>>> On 29/11/12 17:23, janb wrote:
>>>> Hi Sergey,
>>>>
>>>> my Comperator is not simply checking the number of parameters, but
>>>> rather the matching of a query parameter name!
>>>>
>>>> Here are a couple an examples related to the sample code on the
>>>> mentioned wiki page:
>>>>
>>>> Query:
>>>> http://localhost:8080/paramTest?foo=Hello
>>>>
>>>> Rating:
>>>>
>>>> +2 : getFoo(@QueryParam("foo") String foo)
>>>>
>>>> +1 : getFooBar(@QueryParam("foo") String foo, @QueryParam("bar")
>>>> String bar)
>>>>
>>>> // Some addition samples
>>>>
>>>> 0 : getFooBar()
>>>>
>>>> -1 : getFooBar(@QueryParam("bar") String bar)
>>>>
>>>>
>>>>
>>>> So the first method would be selected.
>>>>
>>>>
>>>> Query:
>>>> http://localhost:8080/paramTest?bar=Hello
>>>>
>>>> Rating:
>>>>
>>>> -1 : getFoo(@QueryParam("foo") String foo)
>>>>
>>>> +1 : getFooBar(@QueryParam("foo") String foo, @QueryParam("bar")
>>>> String bar)
>>>>
>>>> // Some addition samples
>>>>
>>>> 0 : getFooBar()
>>>>
>>>> +2 : getFooBar(@QueryParam("bar") String bar)
>>>>
>>>>
>>>> As you can see form the number, the last one is the perfect match,
>>>> hence it would be selected. If this method would not exist the second
>>>> would still be the second best match, and so forth...
>>>>
>>>> Query:
>>>> http://localhost:8080/paramTest?something=Hello
>>>>
>>>> Rating:
>>>>
>>>> -1 : getFoo(@QueryParam("foo") String foo)
>>>>
>>>> -2 : getFooBar(@QueryParam("foo") String foo, @QueryParam("bar")
>>>> String bar)
>>>>
>>>> // Some addition samples
>>>>
>>>> 0 : getFooBar()
>>>>
>>>> -1 : getFooBar(@QueryParam("bar") String bar)
>>>>
>>>>
>>>> Parameter something is not in any of the methods, therefore
>>>> getFooBar()would be the best choice.
>>>>
>>>> Query:
>>>> http://localhost:8080/paramTest
>>>>
>>>> Rating:
>>>>
>>>> -1 : getFoo(@QueryParam("foo") String foo)
>>>>
>>>> -2 : getFooBar(@QueryParam("foo") String foo, @QueryParam("bar")
>>>> String bar)
>>>>
>>>> // Some addition samples
>>>>
>>>> 0 : getFooBar()
>>>>
>>>> -1 : getFooBar(@QueryParam("bar") String bar)
>>>>
>>>>
>>>> If no parameters are provided, all method expecting parameters will
>>>> get a negative rating, hence getFooBar() will be selected, which would
>>>> be the perfect match.
>>>>
>>>> So from my understanding, this selection algorithm always provides the
>>>> best choice, in cases where several methods match the same path and
>>>> type. And I think headers could also be handled in the same way. If a
>>>> header is present in a method signature, but this header is not
>>>> provided within the request, this method will get -1 in the rating,
>>>> otherwise if a header is requested and also provided in a request the
>>>> rating will get +2. At the end the method with the highest score will
>>>> be selected, since it has most matches and fewest mismatches compared
>>>> to all other available methods.
>>>>
>>>> WDYT?
>>>>
>>>> By the way, I updated some parts of the code, since they did not
>>>> handle Annotations within an Interface. Here is the refactored code to
>>>> update the wiki page:
>>>>
>>>> package org.apache.syncope.core.rest;
>>>>
>>>> import java.io.UnsupportedEncodingException;
>>>> import java.lang.annotation.Annotation;
>>>> import java.net.URLDecoder;
>>>> import java.util.HashMap;
>>>> import java.util.HashSet;
>>>> import java.util.Map;
>>>> import java.util.Set;
>>>>
>>>> import javax.ws.rs.DefaultValue;
>>>> import javax.ws.rs.QueryParam;
>>>>
>>>> import org.apache.cxf.jaxrs.ext.ResourceComparator;
>>>> import org.apache.cxf.jaxrs.model.ClassResourceInfo;
>>>> import org.apache.cxf.jaxrs.model.OperationResourceInfo;
>>>> import org.apache.cxf.jaxrs.model.OperationResourceInfoComparator;
>>>> import org.apache.cxf.message.Message;
>>>>
>>>> public class QueryResourceInfoComperator extends
>>>> OperationResourceInfoComparator implements
>>>> ResourceComparator {
>>>>
>>>> public QueryResourceInfoComperator() {
>>>> super(null, null);
>>>> }
>>>>
>>>> @Override
>>>> public int compare(ClassResourceInfo cri1, ClassResourceInfo cri2,
>>>> Message message) {
>>>> // Leave Class selection to CXF
>>>> return 0;
>>>> }
>>>>
>>>> @Override
>>>> public int compare(OperationResourceInfo oper1, OperationResourceInfo
>>>> oper2, Message message) {
>>>>
>>>> // Check if CXF can make a decision
>>>> int cxfResult = super.compare(oper1, oper2);
>>>> if (cxfResult != 0)
>>>> return cxfResult;
>>>>
>>>> // Compare QueryParam annotations
>>>> Set<String> qParams = getParams((String)
>>>> message.get(Message.QUERY_STRING));
>>>> Map<String, Boolean> op1Annos = getAnnotations(oper1);
>>>> Map<String, Boolean> op2Annos = getAnnotations(oper2);
>>>>
>>>> int op1Counter = getMatchingRate(op1Annos, qParams);
>>>> int op2Counter = getMatchingRate(op2Annos, qParams);
>>>>
>>>> return op1Counter == op2Counter
>>>> ? 0
>>>> : op1Counter< op2Counter
>>>> ? 1
>>>> : -1;
>>>> }
>>>>
>>>> /**
>>>> * This method calculates a number indicating a good or bad match
>>>> between
>>>> * queryParams from request and annotated method parameters. A higher
>>>> number
>>>> * means a better match.
>>>> *
>>>> * @param annotations
>>>> * Map contains name of QueryParam method parameters as a key and
>>>> * a Boolean value indicating an existing default value for this
>>>> * parameter.
>>>> * @param queryParams
>>>> * A Set of query parameters provided within the request
>>>> * @return A positive or negative number, indicating a good match
>>>> between
>>>> * query and method
>>>> */
>>>> protected int getMatchingRate(Map<String, Boolean> annotations,
>>>> Set<String> queryParams) {
>>>> int rate = 0;
>>>> for (String anno : annotations.keySet()) {
>>>> if (queryParams.contains(anno)) {
>>>> // URL query matches one method parameter
>>>> rate += 2;
>>>> } else if (!annotations.get(anno).booleanValue()) {
>>>> // No default value exists for method parameter
>>>> rate -= 1;
>>>> }
>>>> }
>>>> return rate;
>>>> }
>>>>
>>>> /**
>>>> * @param opInfo
>>>> * OperationInfo to check for parameter annotations
>>>> * @return Key in Map is QueryParam name, and Value indicates if a
>>>> default
>>>> * value is present.
>>>> */
>>>> protected Map<String, Boolean> getAnnotations(OperationResourceInfo
>>>> opInfo) {
>>>> Map<String, Boolean> opAnnos = new HashMap<String, Boolean>();
>>>> opAnnos.putAll(getAnnotations(opInfo.getAnnotatedMethod().getParameterAnnotations()));
>>>>
>>>>
>>>> opAnnos.putAll(getAnnotations(opInfo.getMethodToInvoke().getParameterAnnotations()));
>>>>
>>>>
>>>> return opAnnos;
>>>> }
>>>>
>>>> /**
>>>> * @param opParamAnnos
>>>> * Array containing all annotations for all method parameters
>>>> * @return Key in Map is QueryParam name, and Value indicates if a
>>>> default
>>>> * value is present.
>>>> */
>>>> protected Map<String, Boolean> getAnnotations(Annotation[][]
>>>> opParamAnnos) {
>>>> Map<String, Boolean> parameterAnnos = new HashMap<String, Boolean>();
>>>>
>>>> if (opParamAnnos.length == 0)
>>>> return parameterAnnos;
>>>>
>>>> for (Annotation[] pAnnos : opParamAnnos) {
>>>> if (pAnnos.length> 0) {
>>>> QueryParam qParam = null;
>>>> DefaultValue dValue = null;
>>>> for (Annotation anno : pAnnos) {
>>>> if (anno instanceof QueryParam)
>>>> qParam = (QueryParam) anno;
>>>> if (anno instanceof DefaultValue)
>>>> dValue = (DefaultValue) anno;
>>>> }
>>>> parameterAnnos.put(qParam.value(), Boolean.valueOf((dValue != null)));
>>>> }
>>>> }
>>>> return parameterAnnos;
>>>> }
>>>>
>>>> /**
>>>> * @param query
>>>> * URL Query
>>>> * @return A Set of all keys, contained within query.
>>>> */
>>>> protected Set<String> getParams(String query) {
>>>> Set<String> params = new HashSet<String>();
>>>> if (query == null || query.length() == 0)
>>>> return params;
>>>>
>>>> try {
>>>> for (String param : query.split("&")) {
>>>> String pair[] = param.split("=");
>>>> String key = URLDecoder.decode(pair[0], "UTF-8");
>>>> params.add(key);
>>>> }
>>>> } catch (UnsupportedEncodingException e) {
>>>> e.printStackTrace();
>>>> }
>>>> return params;
>>>> }
>>>> }
>>>>
>>>>
>>>> Best regards
>>>> Jan
>>>>
>>>> From: Sergey Beryozkin-5 [via CXF]
>>>> [mailto:[hidden email]</user/SendEmail.jtp?type=node&node=5719465&i=0>]
>>>> Sent: Donnerstag, 29. November 2012 17:51
>>>> To: Jan Bernhardt
>>>> Subject: Re: REST Method selection for different QueryParam's
>>>>
>>>> Hi Jan
>>>>
>>>> I've posted it to the
>>>> wiki:https://cwiki.apache.org/confluence/display/CXF20DOC/JAX-RS+Basics#JAX-RSBasics-Customselectionbetweenmultipleresources
>>>>
>>>>
>>>>
>>>> I guess we might indeed can add a more general comparator which would
>>>> select the methods based on the number of optional parameters (of any
>>>> type such as query, header, and the combination, etc), be they
>>>> single or
>>>> repetitive.
>>>>
>>>> I'm just yet sure if that will work well or not, example,
>>>> what if we have a single query parameter but it has the name which is
>>>> not expected by a method expecting a single parameter, etc... May be we
>>>> will tune it in time :-)
>>>>
>>>> Thanks, Sergey
>>>>
>>>>
>>>>
>>>> On 27/11/12 16:40, Sergey Beryozkin wrote:
>>>>
>>>>> Hi Jan
>>>>>
>>>>> This looks very neat, more comments below
>>>>> On 27/11/12 15:25, janb wrote:
>>>>>> Hi Sergey,
>>>>>>
>>>>>> Thank you for your reply. If just developed a generic sample that
>>>>>> uses
>>>>>> CXF standard class and operation selection algorithms, but extends it
>>>>>> with a QueryParam selection algorithm. If all other conditions in CXF
>>>>>> match and hence CXF cannot decide which method to select best, my
>>>>>> QueryParam selection algorithm comes into place. This algorithm
>>>>>> chooses the method with most matches between provided parameters in
>>>>>> query and matching QueryParam annotations within the method.
>>>>>> Additional (not matching) QueryParam annotations will decrease the
>>>>>> matching rate, while method parameters with a default value will be
>>>>>> ignored (in the rating).
>>>>>>
>>>>>> If you think this code is of any value for CXF, please feel free to
>>>>>> extend the current selection strategy with my QueryParam selection
>>>>>> algorithm. I guess the best place for my algorithm would be another
>>>>>> JAXRSUtils method, like
>>>>>> JAXRSUtils.compareQueryParams(...) to be called at the end of
>>>>>> OperationResourceInfoComparator.compare(...).
>>>>>>
>>>>>>
>>>>>> public class QueryResourceInfoComperator extends
>>>>>> OperationResourceInfoComparator implements
>>>>>> ResourceComparator {
>>>>>>
>>>>>> public QueryResourceInfoComperator() {
>>>>>> super(null, null);
>>>>>> }
>>>>>>
>>>>>> @Override
>>>>>> public int compare(ClassResourceInfo cri1, ClassResourceInfo cri2,
>>>>>> Message message) {
>>>>>> // Leave Class selection to CXF
>>>>>> return 0;
>>>>>> }
>>>>>>
>>>>>> @Override
>>>>>> public int compare(OperationResourceInfo oper1, OperationResourceInfo
>>>>>> oper2, Message message) {
>>>>>>
>>>>>> // Check if CXF can make a decision
>>>>>> int cxfResult = super.compare(oper1, oper2);
>>>>>> if (cxfResult != 0)
>>>>>> return cxfResult;
>>>>>>
>>>>>> // Compare QueryParam annotations
>>>>>> Set<String> qParams = getParams((String)
>>>>>> message.get(Message.QUERY_STRING));
>>>>>> int op1Counter =
>>>>>> getMatchingRate(getAnnotations(oper1.getMethodToInvoke().getParameterAnnotations()),
>>>>>>
>>>>>>
>>>>>>
>>>>>> qParams);
>>>>>> int op2Counter =
>>>>>> getMatchingRate(getAnnotations(oper2.getMethodToInvoke().getParameterAnnotations()),
>>>>>>
>>>>>>
>>>>>>
>>>>>> qParams);
>>>>>>
>>>>>> return op1Counter == op2Counter
>>>>>> ? 0
>>>>>> : op1Counter< op2Counter
>>>>>> ? 1
>>>>>> : -1;
>>>>>> }
>>>>>>
>>>>>> /**
>>>>>> * This method calculates a number indicating a good or bad match
>>>>>> between
>>>>>> * queryParams from request and annotated method parameters. A higher
>>>>>> number
>>>>>> * means a better match.
>>>>>> *
>>>>>> * @param annotations
>>>>>> * Map contains name of QueryParam method parameters as a key and
>>>>>> * a Boolean value indicating an existing default value for this
>>>>>> * parameter.
>>>>>> * @param queryParams
>>>>>> * A Set of query parameters provided within the request
>>>>>> * @return A positive or negative number, indicating a good match
>>>>>> between
>>>>>> * query and method
>>>>>> */
>>>>>> protected int getMatchingRate(Map<String, Boolean> annotations,
>>>>>> Set<String> queryParams) {
>>>>>> int rate = 0;
>>>>>> for (String anno : annotations.keySet()) {
>>>>>> if (queryParams.contains(anno)) {
>>>>>> // URL query matches one method parameter
>>>>>> rate += 2;
>>>>>> } else if (!annotations.get(anno).booleanValue()) {
>>>>>> // No default value exists for method parameter
>>>>>> rate -= 1;
>>>>>> }
>>>>>> }
>>>>>> return rate;
>>>>>> }
>>>>>>
>>>>>> /**
>>>>>> * @param opParamAnnos
>>>>>> * Array containing all annotations for all method parameters
>>>>>> * @return Key in Map is QueryParam name, and Value indicates if a
>>>>>> default
>>>>>> * value is present.
>>>>>> */
>>>>>> protected Map<String, Boolean> getAnnotations(Annotation[][]
>>>>>> opParamAnnos) {
>>>>>> Map<String, Boolean> parameterAnnos = new HashMap<String, Boolean>();
>>>>>>
>>>>>> if (opParamAnnos.length == 0)
>>>>>> return parameterAnnos;
>>>>>>
>>>>>> for (Annotation[] pAnnos : opParamAnnos) {
>>>>>> QueryParam qParam = null;
>>>>>> DefaultValue dValue = null;
>>>>>> for (Annotation anno : pAnnos) {
>>>>>> if (anno instanceof QueryParam)
>>>>>> qParam = (QueryParam) anno;
>>>>>> if (anno instanceof DefaultValue)
>>>>>> dValue = (DefaultValue) anno;
>>>>>> }
>>>>>> parameterAnnos.put(qParam.value(), Boolean.valueOf((dValue !=
>>>>>> null)));
>>>>>> }
>>>>>>
>>>>>> return parameterAnnos;
>>>>>> }
>>>>>>
>>>>>> /**
>>>>>> * @param query
>>>>>> * URL Query
>>>>>> * @return A Set of all keys, contained within query.
>>>>>> */
>>>>>> protected Set<String> getParams(String query) {
>>>>>> Set<String> params = new HashSet<String>();
>>>>>> if (query == null || query.length() == 0)
>>>>>> return params;
>>>>>>
>>>>>> try {
>>>>>> for (String param : query.split("&")) {
>>>>>> String pair[] = param.split("=");
>>>>>> String key = URLDecoder.decode(pair[0], "UTF-8");
>>>>>> params.add(key);
>>>>>> }
>>>>>> } catch (UnsupportedEncodingException e) {
>>>>>> e.printStackTrace();
>>>>>> }
>>>>>> return params;
>>>>>> }
>>>>>> }
>>>>>>
>>>>>
>>>>> I think it is difficult to generalize given that another user may
>>>>> want a
>>>>> similar support for the selection based on matrix/header/form
>>>>> parameters, and the other complexity is that we can have repeating
>>>>> parameters, example, "a=1&a=2" query, etc.
>>>>>
>>>>> However it definitely makes sense to update the docs and show what
>>>>> does
>>>>> it mean to customize the selection algo, so what I will do is I will
>>>>> update the page and paste the code - lets see may be we can push
>>>>> some of
>>>>> the code to CXF eventually
>>>>>
>>>>> thanks, Sergey
>>>>>
>>>>>> Best regards.
>>>>>> Jan
>>>>>>
>>>>>> From: Sergey Beryozkin-5 [via CXF]
>>>>>> [mailto:[hidden
>>>>>> email]</user/SendEmail.jtp?type=node&node=5719454&i=0>]
>>>>>> Sent: Dienstag, 27. November 2012 11:31
>>>>>> To: Jan Bernhardt
>>>>>> Subject: Re: REST Method selection for different QueryParam's
>>>>>>
>>>>>> Hi Jan
>>>>>> On 27/11/12 10:12, janb wrote:
>>>>>>
>>>>>>> Hi @all,
>>>>>>>
>>>>>>> I'm wondering about the selection strategy in CXF for different
>>>>>>> Query
>>>>>>> Parameter. The documentation [1] does not cover this at all.
>>>>>>>
>>>>>>> A simple system-test provided the impression to me, that CXF has no
>>>>>>> valid
>>>>>>> selection strategy in place for handling different query parameters.
>>>>>>> Is this
>>>>>>> assumption correct? Should I create a Jira ticket for a better
>>>>>>> support?
>>>>>>>
>>>>>>> Here is my sample code. No matter which parameter have been provided
>>>>>>> within
>>>>>>> my URL, only and always the first method was selected by CXF.
>>>>>>>
>>>>>>> @Path("/paramTest")
>>>>>>> public class MySimpleService {
>>>>>>>
>>>>>>> @GET
>>>>>>> public String getFoo(@QueryParam("foo") String foo){
>>>>>>> return "foo:" + foo;
>>>>>>> }
>>>>>>>
>>>>>>> @GET
>>>>>>> public String getFooBar(@QueryParam("foo") String foo,
>>>>>>> @QueryParam("bar") String bar){
>>>>>>> return "foo:" + foo + " bar:" + bar;
>>>>>>> }
>>>>>>>
>>>>>>> @GET
>>>>>>> public String getTest(@QueryParam("test") String test){
>>>>>>> return "test:" + test;
>>>>>>> }
>>>>>>> }
>>>>>>>
>>>>>>> [1]
>>>>>>> http://cxf.apache.org/docs/jax-rs-basics.html#JAX-RSBasics-Overviewoftheselectionalgorithm.
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>
>>>>>> Only URI path segment, HTTP method and in/out media types are taken
>>>>>> into
>>>>>> consideration when selecting the candidates. If you prefer to have
>>>>>> individual methods having the same HTTP Method/Uri Path/Media
>>>>>> Types but
>>>>>> with specific query parameters then the only way to get it managed
>>>>>> is to
>>>>>> use a CXF ResourceComparator where, in this case, you can affect the
>>>>>> ordering of specific resource methods by checking the current
>>>>>> Message.QUERY_STRING available on the CXF Message
>>>>>>
>>>>>> A simpler alternative is to have a single method and work with
>>>>>> UriInfo, say
>>>>>>
>>>>>> @Context
>>>>>> private UriInfo ui;
>>>>>>
>>>>>> @GET
>>>>>> public String getFooOrBar() {
>>>>>> MultivaluedMap<String, String> params = ui.getQueryParameters();
>>>>>> String foo = params.getFirst("foo");
>>>>>> String bar = params.getFirst("foo");
>>>>>> // etc
>>>>>> }
>>>>>>
>>>>>> HTH, Sergey
>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> --
>>>>>>> View this message in context:
>>>>>>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187.html
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> Sent from the cxf-user mailing list archive at Nabble.com.
>>>>>>
>>>>>>
>>>>>> --
>>>>>> Sergey Beryozkin
>>>>>>
>>>>>> Talend Community Coders
>>>>>> http://coders.talend.com/
>>>>>>
>>>>>> Blog: http://sberyozkin.blogspot.com
>>>>>>
>>>>>> ________________________________
>>>>>> If you reply to this email, your message will be added to the
>>>>>> discussion below:
>>>>>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719190.html
>>>>>>
>>>>>>
>>>>>>
>>>>>> To unsubscribe from REST Method selection for different QueryParam's,
>>>>>> click
>>>>>> here<
>>>>>>
>>>>>> NAML<http://cxf.547215.n5.nabble.com/template/NamlServlet.jtp?macro=macro_viewer&id=instant_html%21nabble%3Aemail.naml&base=nabble.naml.namespaces.BasicNamespace-nabble.view.web.template.NabbleNamespace-nabble.view.web.template.NodeNamespace&breadcrumbs=notify_subscribers%21nabble%3Aemail.naml-instant_emails%21nabble%3Aemail.naml-send_instant_email%21nabble%3Aemail.naml>
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>> --
>>>>>> View this message in context:
>>>>>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719219.html
>>>>>>
>>>>>>
>>>>>>
>>>>>> Sent from the cxf-user mailing list archive at Nabble.com.
>>>>>
>>>>>
>>>>
>>>>
>>>> --
>>>> Sergey Beryozkin
>>>>
>>>> Talend Community Coders
>>>> http://coders.talend.com/
>>>>
>>>> Blog: http://sberyozkin.blogspot.com
>>>>
>>>> ________________________________
>>>> If you reply to this email, your message will be added to the
>>>> discussion below:
>>>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719454.html
>>>>
>>>>
>>>> To unsubscribe from REST Method selection for different QueryParam's,
>>>> click
>>>> here<
>>>>
>>>> NAML<http://cxf.547215.n5.nabble.com/template/NamlServlet.jtp?macro=macro_viewer&id=instant_html%21nabble%3Aemail.naml&base=nabble.naml.namespaces.BasicNamespace-nabble.view.web.template.NabbleNamespace-nabble.view.web.template.NodeNamespace&breadcrumbs=notify_subscribers%21nabble%3Aemail.naml-instant_emails%21nabble%3Aemail.naml-send_instant_email%21nabble%3Aemail.naml>
>>>>
>>>>
>>>>
>>>>
>>>>
>>>>
>>>> --
>>>> View this message in context:
>>>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719458.html
>>>>
>>>>
>>>> Sent from the cxf-user mailing list archive at Nabble.com.
>>>
>>>
>>
>>
>> --
>> Sergey Beryozkin
>>
>> Talend Community Coders
>> http://coders.talend.com/
>>
>> Blog: http://sberyozkin.blogspot.com
>>
>> ________________________________
>> If you reply to this email, your message will be added to the
>> discussion below:
>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719465.html
>>
>> To unsubscribe from REST Method selection for different QueryParam's,
>> click
>> here<http://cxf.547215.n5.nabble.com/template/NamlServlet.jtp?macro=unsubscribe_by_code&node=5719187&code=amJlcm5oYXJkdEB0YWxlbmQuY29tfDU3MTkxODd8LTEzMDQ4ODk1MjM=>.
>>
>> NAML<http://cxf.547215.n5.nabble.com/template/NamlServlet.jtp?macro=macro_viewer&id=instant_html%21nabble%3Aemail.naml&base=nabble.naml.namespaces.BasicNamespace-nabble.view.web.template.NabbleNamespace-nabble.view.web.template.NodeNamespace&breadcrumbs=notify_subscribers%21nabble%3Aemail.naml-instant_emails%21nabble%3Aemail.naml-send_instant_email%21nabble%3Aemail.naml>
>>
>>
>>
>>
>>
>> --
>> View this message in context:
>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719471.html
>>
>> Sent from the cxf-user mailing list archive at Nabble.com.
>
>


-- 
Sergey Beryozkin

Talend Community Coders
http://coders.talend.com/

Blog: http://sberyozkin.blogspot.com

Re: REST Method selection for different QueryParam's

Posted by Sergey Beryozkin <sb...@gmail.com>.
Hi
On 29/11/12 19:26, Sergey Beryozkin wrote:
> Hi Jan
>
> OK, thanks...
>
> To be honest I'd just limit it to checking query parameters only.
> As I said, there may be so many variations there, a different
> combination of query, header, form, matrix, parameters, in a single
> method, or say one method accepting 2 query params, another - two header
> params, or one method - 1 query and 2 header while second method 1 query
> and 2 header params and so forth and it just not possible IMHO to write
> a generic provider which will meaningfully work for different users...
>
> Note it makes the JAX-RS code unportable too, which may not be not a
> major issue, why would anyone want to migrate from CXF ? (:-)) but it is
> a major concern for many users - to make sure they can write a portable
> code...
>
> Yes, I think it is been quite a few times when users asked about the
> query parameters affecting the selection, and it seems like a
> possibility to expect that it can be tricky to convert some existing
> code when say expects two query parameters to JAX-RS and get it
> selected, so I'm open to having a utility comparator dedicated
> specifically to taking Query params into the consideration but that is
> as far as its capabilities are concerned...
>
> Would you agree ? If yes - please give it another try, drop the support
> for headers, and also, try JAXRSUtils.getStructuredParams, it will
> return a properly populated multivalued map which will help with
> removing your own custom parsing code
>
By the way, I'd also ignore the fact there could be multiple values for 
the same query parameter, that would also simplify things IMHO

Thanks, Sergey

> Thanks, Sergey
>
>
> On 29/11/12 19:09, janb wrote:
>> Hi Sergey,
>>
>> I think we are getting close to a real solution ;-)
>>
>> Your advice was very helpful, to optimize the code. I now also added
>> support for headers, so they will also be relevant for the rating.
>>
>> Here is my code:
>>
>> package org.apache.syncope.core.rest;
>>
>> import java.io.UnsupportedEncodingException;
>> import java.net.URLDecoder;
>> import java.util.HashSet;
>> import java.util.List;
>> import java.util.Set;
>>
>> import org.apache.cxf.jaxrs.ext.ResourceComparator;
>> import org.apache.cxf.jaxrs.model.ClassResourceInfo;
>> import org.apache.cxf.jaxrs.model.OperationResourceInfo;
>> import org.apache.cxf.jaxrs.model.OperationResourceInfoComparator;
>> import org.apache.cxf.jaxrs.model.Parameter;
>> import org.apache.cxf.message.Message;
>>
>> public class QueryResourceInfoComperator extends
>> OperationResourceInfoComparator implements
>> ResourceComparator {
>>
>> public QueryResourceInfoComperator() {
>> super(null, null);
>> }
>>
>> @Override
>> public int compare(ClassResourceInfo cri1, ClassResourceInfo cri2,
>> Message message) {
>> // Leave Class selection to CXF
>> return 0;
>> }
>>
>> @Override
>> public int compare(OperationResourceInfo oper1, OperationResourceInfo
>> oper2, Message message) {
>>
>> // Check if CXF can make a decision
>> int cxfResult = super.compare(oper1, oper2);
>> if (cxfResult != 0)
>> return cxfResult;
>>
>> int op1Counter = getMatchingRate(oper1, message);
>> int op2Counter = getMatchingRate(oper2, message);
>>
>> return op1Counter == op2Counter
>> ? 0
>> : op1Counter< op2Counter
>> ? 1
>> : -1;
>> }
>>
>> /**
>> * This method calculates a number indicating a good or bad match between
>> * values provided within the request and expected method parameters. A
>> * higher number means a better match.
>> *
>> * @param operation
>> * The operation to be rated, based on contained parameterInfo
>> * values.
>> * @param message
>> * A message containing query and header values from user request
>> * @return A positive or negative number, indicating a good match between
>> * query and method
>> */
>> protected int getMatchingRate(OperationResourceInfo operation, Message
>> message) {
>>
>> List<Parameter> params = operation.getParameters();
>> if (params == null || params.size() == 0)
>> return 0;
>>
>> // Get Request QueryParams
>> Set<String> qParams = getParams((String)
>> message.get(Message.QUERY_STRING));
>> // Get Request Headers
>> java.util.Map<String, String> qHeader = (java.util.Map<String,
>> String>) message
>> .get(Message.PROTOCOL_HEADERS);
>>
>> int rate = 0;
>> for (Parameter p : params) {
>> switch (p.getType()) {
>> case QUERY:
>> if (qParams.contains(p.getName()))
>> rate += 2;
>> else if (p.getDefaultValue() == null)
>> rate -= 1;
>> break;
>> case HEADER:
>> if (qHeader.containsKey(p.getName()))
>> rate += 2;
>> else if (p.getDefaultValue() == null)
>> rate -= 1;
>> break;
>> default:
>> break;
>> }
>> }
>> return rate;
>> }
>>
>> /**
>> * @param query
>> * URL Query Example: 'key=value&key2=value2'
>> * @return A Set of all keys, contained within query.
>> */
>> protected Set<String> getParams(String query) {
>> Set<String> params = new HashSet<String>();
>> if (query == null || query.length() == 0)
>> return params;
>>
>> try {
>> for (String param : query.split("&")) {
>> String pair[] = param.split("=");
>> String key = URLDecoder.decode(pair[0], "UTF-8");
>> params.add(key);
>> }
>> } catch (UnsupportedEncodingException e) {
>> e.printStackTrace();
>> }
>> return params;
>> }
>> }
>>
>>
>> I guess it would be nice, if CXF could provide the query params in a
>> Map instead of a String, then my getParams(String query) method would
>> be obsolete.
>>
>> Regards.
>> Jan
>>
>> From: Sergey Beryozkin-5 [via CXF]
>> [mailto:ml-node+s547215n5719465h66@n5.nabble.com]
>> Sent: Donnerstag, 29. November 2012 19:02
>> To: Jan Bernhardt
>> Subject: Re: REST Method selection for different QueryParam's
>>
>> On 29/11/12 17:58, Sergey Beryozkin wrote:
>>> Hi Jan
>>>
>>> OK, thanks for all the info, looks promising...
>>> It just occurred to me, you do not have to check the annotations,
>>> OperationResourceInfo has it all cached, get the list of parameters, and
>>> check parameter types (can be query. matrix, etc), this will be much
>>> faster too, please give it a try...The parameter will have an index into
>>> an array of the Method parameter array, Method can be found from
>>> ori.getMethodToInvoke()
>>
>> Actually, you don't even need a method. Parameter will have name,
>> default value, and parameter type
>>
>> Sergey
>>
>>>
>>> Cheers, Sergey
>>>
>>>
>>>
>>> On 29/11/12 17:23, janb wrote:
>>>> Hi Sergey,
>>>>
>>>> my Comperator is not simply checking the number of parameters, but
>>>> rather the matching of a query parameter name!
>>>>
>>>> Here are a couple an examples related to the sample code on the
>>>> mentioned wiki page:
>>>>
>>>> Query:
>>>> http://localhost:8080/paramTest?foo=Hello
>>>>
>>>> Rating:
>>>>
>>>> +2 : getFoo(@QueryParam("foo") String foo)
>>>>
>>>> +1 : getFooBar(@QueryParam("foo") String foo, @QueryParam("bar")
>>>> String bar)
>>>>
>>>> // Some addition samples
>>>>
>>>> 0 : getFooBar()
>>>>
>>>> -1 : getFooBar(@QueryParam("bar") String bar)
>>>>
>>>>
>>>>
>>>> So the first method would be selected.
>>>>
>>>>
>>>> Query:
>>>> http://localhost:8080/paramTest?bar=Hello
>>>>
>>>> Rating:
>>>>
>>>> -1 : getFoo(@QueryParam("foo") String foo)
>>>>
>>>> +1 : getFooBar(@QueryParam("foo") String foo, @QueryParam("bar")
>>>> String bar)
>>>>
>>>> // Some addition samples
>>>>
>>>> 0 : getFooBar()
>>>>
>>>> +2 : getFooBar(@QueryParam("bar") String bar)
>>>>
>>>>
>>>> As you can see form the number, the last one is the perfect match,
>>>> hence it would be selected. If this method would not exist the second
>>>> would still be the second best match, and so forth...
>>>>
>>>> Query:
>>>> http://localhost:8080/paramTest?something=Hello
>>>>
>>>> Rating:
>>>>
>>>> -1 : getFoo(@QueryParam("foo") String foo)
>>>>
>>>> -2 : getFooBar(@QueryParam("foo") String foo, @QueryParam("bar")
>>>> String bar)
>>>>
>>>> // Some addition samples
>>>>
>>>> 0 : getFooBar()
>>>>
>>>> -1 : getFooBar(@QueryParam("bar") String bar)
>>>>
>>>>
>>>> Parameter something is not in any of the methods, therefore
>>>> getFooBar()would be the best choice.
>>>>
>>>> Query:
>>>> http://localhost:8080/paramTest
>>>>
>>>> Rating:
>>>>
>>>> -1 : getFoo(@QueryParam("foo") String foo)
>>>>
>>>> -2 : getFooBar(@QueryParam("foo") String foo, @QueryParam("bar")
>>>> String bar)
>>>>
>>>> // Some addition samples
>>>>
>>>> 0 : getFooBar()
>>>>
>>>> -1 : getFooBar(@QueryParam("bar") String bar)
>>>>
>>>>
>>>> If no parameters are provided, all method expecting parameters will
>>>> get a negative rating, hence getFooBar() will be selected, which would
>>>> be the perfect match.
>>>>
>>>> So from my understanding, this selection algorithm always provides the
>>>> best choice, in cases where several methods match the same path and
>>>> type. And I think headers could also be handled in the same way. If a
>>>> header is present in a method signature, but this header is not
>>>> provided within the request, this method will get -1 in the rating,
>>>> otherwise if a header is requested and also provided in a request the
>>>> rating will get +2. At the end the method with the highest score will
>>>> be selected, since it has most matches and fewest mismatches compared
>>>> to all other available methods.
>>>>
>>>> WDYT?
>>>>
>>>> By the way, I updated some parts of the code, since they did not
>>>> handle Annotations within an Interface. Here is the refactored code to
>>>> update the wiki page:
>>>>
>>>> package org.apache.syncope.core.rest;
>>>>
>>>> import java.io.UnsupportedEncodingException;
>>>> import java.lang.annotation.Annotation;
>>>> import java.net.URLDecoder;
>>>> import java.util.HashMap;
>>>> import java.util.HashSet;
>>>> import java.util.Map;
>>>> import java.util.Set;
>>>>
>>>> import javax.ws.rs.DefaultValue;
>>>> import javax.ws.rs.QueryParam;
>>>>
>>>> import org.apache.cxf.jaxrs.ext.ResourceComparator;
>>>> import org.apache.cxf.jaxrs.model.ClassResourceInfo;
>>>> import org.apache.cxf.jaxrs.model.OperationResourceInfo;
>>>> import org.apache.cxf.jaxrs.model.OperationResourceInfoComparator;
>>>> import org.apache.cxf.message.Message;
>>>>
>>>> public class QueryResourceInfoComperator extends
>>>> OperationResourceInfoComparator implements
>>>> ResourceComparator {
>>>>
>>>> public QueryResourceInfoComperator() {
>>>> super(null, null);
>>>> }
>>>>
>>>> @Override
>>>> public int compare(ClassResourceInfo cri1, ClassResourceInfo cri2,
>>>> Message message) {
>>>> // Leave Class selection to CXF
>>>> return 0;
>>>> }
>>>>
>>>> @Override
>>>> public int compare(OperationResourceInfo oper1, OperationResourceInfo
>>>> oper2, Message message) {
>>>>
>>>> // Check if CXF can make a decision
>>>> int cxfResult = super.compare(oper1, oper2);
>>>> if (cxfResult != 0)
>>>> return cxfResult;
>>>>
>>>> // Compare QueryParam annotations
>>>> Set<String> qParams = getParams((String)
>>>> message.get(Message.QUERY_STRING));
>>>> Map<String, Boolean> op1Annos = getAnnotations(oper1);
>>>> Map<String, Boolean> op2Annos = getAnnotations(oper2);
>>>>
>>>> int op1Counter = getMatchingRate(op1Annos, qParams);
>>>> int op2Counter = getMatchingRate(op2Annos, qParams);
>>>>
>>>> return op1Counter == op2Counter
>>>> ? 0
>>>> : op1Counter< op2Counter
>>>> ? 1
>>>> : -1;
>>>> }
>>>>
>>>> /**
>>>> * This method calculates a number indicating a good or bad match
>>>> between
>>>> * queryParams from request and annotated method parameters. A higher
>>>> number
>>>> * means a better match.
>>>> *
>>>> * @param annotations
>>>> * Map contains name of QueryParam method parameters as a key and
>>>> * a Boolean value indicating an existing default value for this
>>>> * parameter.
>>>> * @param queryParams
>>>> * A Set of query parameters provided within the request
>>>> * @return A positive or negative number, indicating a good match
>>>> between
>>>> * query and method
>>>> */
>>>> protected int getMatchingRate(Map<String, Boolean> annotations,
>>>> Set<String> queryParams) {
>>>> int rate = 0;
>>>> for (String anno : annotations.keySet()) {
>>>> if (queryParams.contains(anno)) {
>>>> // URL query matches one method parameter
>>>> rate += 2;
>>>> } else if (!annotations.get(anno).booleanValue()) {
>>>> // No default value exists for method parameter
>>>> rate -= 1;
>>>> }
>>>> }
>>>> return rate;
>>>> }
>>>>
>>>> /**
>>>> * @param opInfo
>>>> * OperationInfo to check for parameter annotations
>>>> * @return Key in Map is QueryParam name, and Value indicates if a
>>>> default
>>>> * value is present.
>>>> */
>>>> protected Map<String, Boolean> getAnnotations(OperationResourceInfo
>>>> opInfo) {
>>>> Map<String, Boolean> opAnnos = new HashMap<String, Boolean>();
>>>> opAnnos.putAll(getAnnotations(opInfo.getAnnotatedMethod().getParameterAnnotations()));
>>>>
>>>>
>>>> opAnnos.putAll(getAnnotations(opInfo.getMethodToInvoke().getParameterAnnotations()));
>>>>
>>>>
>>>> return opAnnos;
>>>> }
>>>>
>>>> /**
>>>> * @param opParamAnnos
>>>> * Array containing all annotations for all method parameters
>>>> * @return Key in Map is QueryParam name, and Value indicates if a
>>>> default
>>>> * value is present.
>>>> */
>>>> protected Map<String, Boolean> getAnnotations(Annotation[][]
>>>> opParamAnnos) {
>>>> Map<String, Boolean> parameterAnnos = new HashMap<String, Boolean>();
>>>>
>>>> if (opParamAnnos.length == 0)
>>>> return parameterAnnos;
>>>>
>>>> for (Annotation[] pAnnos : opParamAnnos) {
>>>> if (pAnnos.length> 0) {
>>>> QueryParam qParam = null;
>>>> DefaultValue dValue = null;
>>>> for (Annotation anno : pAnnos) {
>>>> if (anno instanceof QueryParam)
>>>> qParam = (QueryParam) anno;
>>>> if (anno instanceof DefaultValue)
>>>> dValue = (DefaultValue) anno;
>>>> }
>>>> parameterAnnos.put(qParam.value(), Boolean.valueOf((dValue != null)));
>>>> }
>>>> }
>>>> return parameterAnnos;
>>>> }
>>>>
>>>> /**
>>>> * @param query
>>>> * URL Query
>>>> * @return A Set of all keys, contained within query.
>>>> */
>>>> protected Set<String> getParams(String query) {
>>>> Set<String> params = new HashSet<String>();
>>>> if (query == null || query.length() == 0)
>>>> return params;
>>>>
>>>> try {
>>>> for (String param : query.split("&")) {
>>>> String pair[] = param.split("=");
>>>> String key = URLDecoder.decode(pair[0], "UTF-8");
>>>> params.add(key);
>>>> }
>>>> } catch (UnsupportedEncodingException e) {
>>>> e.printStackTrace();
>>>> }
>>>> return params;
>>>> }
>>>> }
>>>>
>>>>
>>>> Best regards
>>>> Jan
>>>>
>>>> From: Sergey Beryozkin-5 [via CXF]
>>>> [mailto:[hidden email]</user/SendEmail.jtp?type=node&node=5719465&i=0>]
>>>> Sent: Donnerstag, 29. November 2012 17:51
>>>> To: Jan Bernhardt
>>>> Subject: Re: REST Method selection for different QueryParam's
>>>>
>>>> Hi Jan
>>>>
>>>> I've posted it to the
>>>> wiki:https://cwiki.apache.org/confluence/display/CXF20DOC/JAX-RS+Basics#JAX-RSBasics-Customselectionbetweenmultipleresources
>>>>
>>>>
>>>>
>>>> I guess we might indeed can add a more general comparator which would
>>>> select the methods based on the number of optional parameters (of any
>>>> type such as query, header, and the combination, etc), be they
>>>> single or
>>>> repetitive.
>>>>
>>>> I'm just yet sure if that will work well or not, example,
>>>> what if we have a single query parameter but it has the name which is
>>>> not expected by a method expecting a single parameter, etc... May be we
>>>> will tune it in time :-)
>>>>
>>>> Thanks, Sergey
>>>>
>>>>
>>>>
>>>> On 27/11/12 16:40, Sergey Beryozkin wrote:
>>>>
>>>>> Hi Jan
>>>>>
>>>>> This looks very neat, more comments below
>>>>> On 27/11/12 15:25, janb wrote:
>>>>>> Hi Sergey,
>>>>>>
>>>>>> Thank you for your reply. If just developed a generic sample that
>>>>>> uses
>>>>>> CXF standard class and operation selection algorithms, but extends it
>>>>>> with a QueryParam selection algorithm. If all other conditions in CXF
>>>>>> match and hence CXF cannot decide which method to select best, my
>>>>>> QueryParam selection algorithm comes into place. This algorithm
>>>>>> chooses the method with most matches between provided parameters in
>>>>>> query and matching QueryParam annotations within the method.
>>>>>> Additional (not matching) QueryParam annotations will decrease the
>>>>>> matching rate, while method parameters with a default value will be
>>>>>> ignored (in the rating).
>>>>>>
>>>>>> If you think this code is of any value for CXF, please feel free to
>>>>>> extend the current selection strategy with my QueryParam selection
>>>>>> algorithm. I guess the best place for my algorithm would be another
>>>>>> JAXRSUtils method, like
>>>>>> JAXRSUtils.compareQueryParams(...) to be called at the end of
>>>>>> OperationResourceInfoComparator.compare(...).
>>>>>>
>>>>>>
>>>>>> public class QueryResourceInfoComperator extends
>>>>>> OperationResourceInfoComparator implements
>>>>>> ResourceComparator {
>>>>>>
>>>>>> public QueryResourceInfoComperator() {
>>>>>> super(null, null);
>>>>>> }
>>>>>>
>>>>>> @Override
>>>>>> public int compare(ClassResourceInfo cri1, ClassResourceInfo cri2,
>>>>>> Message message) {
>>>>>> // Leave Class selection to CXF
>>>>>> return 0;
>>>>>> }
>>>>>>
>>>>>> @Override
>>>>>> public int compare(OperationResourceInfo oper1, OperationResourceInfo
>>>>>> oper2, Message message) {
>>>>>>
>>>>>> // Check if CXF can make a decision
>>>>>> int cxfResult = super.compare(oper1, oper2);
>>>>>> if (cxfResult != 0)
>>>>>> return cxfResult;
>>>>>>
>>>>>> // Compare QueryParam annotations
>>>>>> Set<String> qParams = getParams((String)
>>>>>> message.get(Message.QUERY_STRING));
>>>>>> int op1Counter =
>>>>>> getMatchingRate(getAnnotations(oper1.getMethodToInvoke().getParameterAnnotations()),
>>>>>>
>>>>>>
>>>>>>
>>>>>> qParams);
>>>>>> int op2Counter =
>>>>>> getMatchingRate(getAnnotations(oper2.getMethodToInvoke().getParameterAnnotations()),
>>>>>>
>>>>>>
>>>>>>
>>>>>> qParams);
>>>>>>
>>>>>> return op1Counter == op2Counter
>>>>>> ? 0
>>>>>> : op1Counter< op2Counter
>>>>>> ? 1
>>>>>> : -1;
>>>>>> }
>>>>>>
>>>>>> /**
>>>>>> * This method calculates a number indicating a good or bad match
>>>>>> between
>>>>>> * queryParams from request and annotated method parameters. A higher
>>>>>> number
>>>>>> * means a better match.
>>>>>> *
>>>>>> * @param annotations
>>>>>> * Map contains name of QueryParam method parameters as a key and
>>>>>> * a Boolean value indicating an existing default value for this
>>>>>> * parameter.
>>>>>> * @param queryParams
>>>>>> * A Set of query parameters provided within the request
>>>>>> * @return A positive or negative number, indicating a good match
>>>>>> between
>>>>>> * query and method
>>>>>> */
>>>>>> protected int getMatchingRate(Map<String, Boolean> annotations,
>>>>>> Set<String> queryParams) {
>>>>>> int rate = 0;
>>>>>> for (String anno : annotations.keySet()) {
>>>>>> if (queryParams.contains(anno)) {
>>>>>> // URL query matches one method parameter
>>>>>> rate += 2;
>>>>>> } else if (!annotations.get(anno).booleanValue()) {
>>>>>> // No default value exists for method parameter
>>>>>> rate -= 1;
>>>>>> }
>>>>>> }
>>>>>> return rate;
>>>>>> }
>>>>>>
>>>>>> /**
>>>>>> * @param opParamAnnos
>>>>>> * Array containing all annotations for all method parameters
>>>>>> * @return Key in Map is QueryParam name, and Value indicates if a
>>>>>> default
>>>>>> * value is present.
>>>>>> */
>>>>>> protected Map<String, Boolean> getAnnotations(Annotation[][]
>>>>>> opParamAnnos) {
>>>>>> Map<String, Boolean> parameterAnnos = new HashMap<String, Boolean>();
>>>>>>
>>>>>> if (opParamAnnos.length == 0)
>>>>>> return parameterAnnos;
>>>>>>
>>>>>> for (Annotation[] pAnnos : opParamAnnos) {
>>>>>> QueryParam qParam = null;
>>>>>> DefaultValue dValue = null;
>>>>>> for (Annotation anno : pAnnos) {
>>>>>> if (anno instanceof QueryParam)
>>>>>> qParam = (QueryParam) anno;
>>>>>> if (anno instanceof DefaultValue)
>>>>>> dValue = (DefaultValue) anno;
>>>>>> }
>>>>>> parameterAnnos.put(qParam.value(), Boolean.valueOf((dValue !=
>>>>>> null)));
>>>>>> }
>>>>>>
>>>>>> return parameterAnnos;
>>>>>> }
>>>>>>
>>>>>> /**
>>>>>> * @param query
>>>>>> * URL Query
>>>>>> * @return A Set of all keys, contained within query.
>>>>>> */
>>>>>> protected Set<String> getParams(String query) {
>>>>>> Set<String> params = new HashSet<String>();
>>>>>> if (query == null || query.length() == 0)
>>>>>> return params;
>>>>>>
>>>>>> try {
>>>>>> for (String param : query.split("&")) {
>>>>>> String pair[] = param.split("=");
>>>>>> String key = URLDecoder.decode(pair[0], "UTF-8");
>>>>>> params.add(key);
>>>>>> }
>>>>>> } catch (UnsupportedEncodingException e) {
>>>>>> e.printStackTrace();
>>>>>> }
>>>>>> return params;
>>>>>> }
>>>>>> }
>>>>>>
>>>>>
>>>>> I think it is difficult to generalize given that another user may
>>>>> want a
>>>>> similar support for the selection based on matrix/header/form
>>>>> parameters, and the other complexity is that we can have repeating
>>>>> parameters, example, "a=1&a=2" query, etc.
>>>>>
>>>>> However it definitely makes sense to update the docs and show what
>>>>> does
>>>>> it mean to customize the selection algo, so what I will do is I will
>>>>> update the page and paste the code - lets see may be we can push
>>>>> some of
>>>>> the code to CXF eventually
>>>>>
>>>>> thanks, Sergey
>>>>>
>>>>>> Best regards.
>>>>>> Jan
>>>>>>
>>>>>> From: Sergey Beryozkin-5 [via CXF]
>>>>>> [mailto:[hidden
>>>>>> email]</user/SendEmail.jtp?type=node&node=5719454&i=0>]
>>>>>> Sent: Dienstag, 27. November 2012 11:31
>>>>>> To: Jan Bernhardt
>>>>>> Subject: Re: REST Method selection for different QueryParam's
>>>>>>
>>>>>> Hi Jan
>>>>>> On 27/11/12 10:12, janb wrote:
>>>>>>
>>>>>>> Hi @all,
>>>>>>>
>>>>>>> I'm wondering about the selection strategy in CXF for different
>>>>>>> Query
>>>>>>> Parameter. The documentation [1] does not cover this at all.
>>>>>>>
>>>>>>> A simple system-test provided the impression to me, that CXF has no
>>>>>>> valid
>>>>>>> selection strategy in place for handling different query parameters.
>>>>>>> Is this
>>>>>>> assumption correct? Should I create a Jira ticket for a better
>>>>>>> support?
>>>>>>>
>>>>>>> Here is my sample code. No matter which parameter have been provided
>>>>>>> within
>>>>>>> my URL, only and always the first method was selected by CXF.
>>>>>>>
>>>>>>> @Path("/paramTest")
>>>>>>> public class MySimpleService {
>>>>>>>
>>>>>>> @GET
>>>>>>> public String getFoo(@QueryParam("foo") String foo){
>>>>>>> return "foo:" + foo;
>>>>>>> }
>>>>>>>
>>>>>>> @GET
>>>>>>> public String getFooBar(@QueryParam("foo") String foo,
>>>>>>> @QueryParam("bar") String bar){
>>>>>>> return "foo:" + foo + " bar:" + bar;
>>>>>>> }
>>>>>>>
>>>>>>> @GET
>>>>>>> public String getTest(@QueryParam("test") String test){
>>>>>>> return "test:" + test;
>>>>>>> }
>>>>>>> }
>>>>>>>
>>>>>>> [1]
>>>>>>> http://cxf.apache.org/docs/jax-rs-basics.html#JAX-RSBasics-Overviewoftheselectionalgorithm.
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>
>>>>>> Only URI path segment, HTTP method and in/out media types are taken
>>>>>> into
>>>>>> consideration when selecting the candidates. If you prefer to have
>>>>>> individual methods having the same HTTP Method/Uri Path/Media
>>>>>> Types but
>>>>>> with specific query parameters then the only way to get it managed
>>>>>> is to
>>>>>> use a CXF ResourceComparator where, in this case, you can affect the
>>>>>> ordering of specific resource methods by checking the current
>>>>>> Message.QUERY_STRING available on the CXF Message
>>>>>>
>>>>>> A simpler alternative is to have a single method and work with
>>>>>> UriInfo, say
>>>>>>
>>>>>> @Context
>>>>>> private UriInfo ui;
>>>>>>
>>>>>> @GET
>>>>>> public String getFooOrBar() {
>>>>>> MultivaluedMap<String, String> params = ui.getQueryParameters();
>>>>>> String foo = params.getFirst("foo");
>>>>>> String bar = params.getFirst("foo");
>>>>>> // etc
>>>>>> }
>>>>>>
>>>>>> HTH, Sergey
>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> --
>>>>>>> View this message in context:
>>>>>>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187.html
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> Sent from the cxf-user mailing list archive at Nabble.com.
>>>>>>
>>>>>>
>>>>>> --
>>>>>> Sergey Beryozkin
>>>>>>
>>>>>> Talend Community Coders
>>>>>> http://coders.talend.com/
>>>>>>
>>>>>> Blog: http://sberyozkin.blogspot.com
>>>>>>
>>>>>> ________________________________
>>>>>> If you reply to this email, your message will be added to the
>>>>>> discussion below:
>>>>>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719190.html
>>>>>>
>>>>>>
>>>>>>
>>>>>> To unsubscribe from REST Method selection for different QueryParam's,
>>>>>> click
>>>>>> here<
>>>>>>
>>>>>> NAML<http://cxf.547215.n5.nabble.com/template/NamlServlet.jtp?macro=macro_viewer&id=instant_html%21nabble%3Aemail.naml&base=nabble.naml.namespaces.BasicNamespace-nabble.view.web.template.NabbleNamespace-nabble.view.web.template.NodeNamespace&breadcrumbs=notify_subscribers%21nabble%3Aemail.naml-instant_emails%21nabble%3Aemail.naml-send_instant_email%21nabble%3Aemail.naml>
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>> --
>>>>>> View this message in context:
>>>>>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719219.html
>>>>>>
>>>>>>
>>>>>>
>>>>>> Sent from the cxf-user mailing list archive at Nabble.com.
>>>>>
>>>>>
>>>>
>>>>
>>>> --
>>>> Sergey Beryozkin
>>>>
>>>> Talend Community Coders
>>>> http://coders.talend.com/
>>>>
>>>> Blog: http://sberyozkin.blogspot.com
>>>>
>>>> ________________________________
>>>> If you reply to this email, your message will be added to the
>>>> discussion below:
>>>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719454.html
>>>>
>>>>
>>>> To unsubscribe from REST Method selection for different QueryParam's,
>>>> click
>>>> here<
>>>>
>>>> NAML<http://cxf.547215.n5.nabble.com/template/NamlServlet.jtp?macro=macro_viewer&id=instant_html%21nabble%3Aemail.naml&base=nabble.naml.namespaces.BasicNamespace-nabble.view.web.template.NabbleNamespace-nabble.view.web.template.NodeNamespace&breadcrumbs=notify_subscribers%21nabble%3Aemail.naml-instant_emails%21nabble%3Aemail.naml-send_instant_email%21nabble%3Aemail.naml>
>>>>
>>>>
>>>>
>>>>
>>>>
>>>>
>>>> --
>>>> View this message in context:
>>>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719458.html
>>>>
>>>>
>>>> Sent from the cxf-user mailing list archive at Nabble.com.
>>>
>>>
>>
>>
>> --
>> Sergey Beryozkin
>>
>> Talend Community Coders
>> http://coders.talend.com/
>>
>> Blog: http://sberyozkin.blogspot.com
>>
>> ________________________________
>> If you reply to this email, your message will be added to the
>> discussion below:
>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719465.html
>>
>> To unsubscribe from REST Method selection for different QueryParam's,
>> click
>> here<http://cxf.547215.n5.nabble.com/template/NamlServlet.jtp?macro=unsubscribe_by_code&node=5719187&code=amJlcm5oYXJkdEB0YWxlbmQuY29tfDU3MTkxODd8LTEzMDQ4ODk1MjM=>.
>>
>> NAML<http://cxf.547215.n5.nabble.com/template/NamlServlet.jtp?macro=macro_viewer&id=instant_html%21nabble%3Aemail.naml&base=nabble.naml.namespaces.BasicNamespace-nabble.view.web.template.NabbleNamespace-nabble.view.web.template.NodeNamespace&breadcrumbs=notify_subscribers%21nabble%3Aemail.naml-instant_emails%21nabble%3Aemail.naml-send_instant_email%21nabble%3Aemail.naml>
>>
>>
>>
>>
>>
>> --
>> View this message in context:
>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719471.html
>>
>> Sent from the cxf-user mailing list archive at Nabble.com.
>
>


-- 
Sergey Beryozkin

Talend Community Coders
http://coders.talend.com/

Blog: http://sberyozkin.blogspot.com

Re: REST Method selection for different QueryParam's

Posted by Sergey Beryozkin <sb...@gmail.com>.
Hi Jan

OK, thanks...

To be honest I'd just limit it to checking query parameters only.
As I said, there may be so many variations there, a different 
combination of query, header, form, matrix, parameters, in a single 
method, or say one method accepting 2 query params, another - two header 
params, or one method - 1 query and 2 header while second method 1 query 
and 2 header params and so forth and it just not possible IMHO to write 
a generic provider which will meaningfully work for different users...

Note it makes the JAX-RS code unportable too, which may not be not a 
major issue, why would anyone want to migrate from CXF ? (:-)) but it is 
a major concern for many users - to make sure they can write a portable 
code...

Yes, I think it is been quite a few times when users asked about the 
query parameters affecting the selection, and it seems like a 
possibility to expect that it can be tricky to convert some existing 
code when say expects two query parameters to JAX-RS and get it 
selected, so I'm open to having a utility comparator dedicated 
specifically to taking Query params into the consideration but that is 
as far as its capabilities are concerned...

Would you agree ? If yes - please give it another try, drop the support 
for headers, and also, try JAXRSUtils.getStructuredParams, it will 
return a properly populated multivalued map which will help with 
removing your own custom parsing code

Thanks, Sergey


On 29/11/12 19:09, janb wrote:
> Hi Sergey,
>
> I think we are getting close to a real solution ;-)
>
> Your advice was very helpful, to optimize the code. I now also added support for headers, so they will also be relevant for the rating.
>
> Here is my code:
>
> package org.apache.syncope.core.rest;
>
> import java.io.UnsupportedEncodingException;
> import java.net.URLDecoder;
> import java.util.HashSet;
> import java.util.List;
> import java.util.Set;
>
> import org.apache.cxf.jaxrs.ext.ResourceComparator;
> import org.apache.cxf.jaxrs.model.ClassResourceInfo;
> import org.apache.cxf.jaxrs.model.OperationResourceInfo;
> import org.apache.cxf.jaxrs.model.OperationResourceInfoComparator;
> import org.apache.cxf.jaxrs.model.Parameter;
> import org.apache.cxf.message.Message;
>
> public class QueryResourceInfoComperator extends OperationResourceInfoComparator implements
>          ResourceComparator {
>
>      public QueryResourceInfoComperator() {
>          super(null, null);
>      }
>
>      @Override
>      public int compare(ClassResourceInfo cri1, ClassResourceInfo cri2, Message message) {
>          // Leave Class selection to CXF
>          return 0;
>      }
>
>      @Override
>      public int compare(OperationResourceInfo oper1, OperationResourceInfo oper2, Message message) {
>
>          // Check if CXF can make a decision
>          int cxfResult = super.compare(oper1, oper2);
>          if (cxfResult != 0)
>              return cxfResult;
>
>          int op1Counter = getMatchingRate(oper1, message);
>          int op2Counter = getMatchingRate(oper2, message);
>
>          return op1Counter == op2Counter
>                  ? 0
>                  : op1Counter<  op2Counter
>                          ? 1
>                          : -1;
>      }
>
>      /**
>       * This method calculates a number indicating a good or bad match between
>       * values provided within the request and expected method parameters. A
>       * higher number means a better match.
>       *
>       * @param operation
>       *            The operation to be rated, based on contained parameterInfo
>       *            values.
>       * @param message
>       *            A message containing query and header values from user request
>       * @return A positive or negative number, indicating a good match between
>       *         query and method
>       */
>      protected int getMatchingRate(OperationResourceInfo operation, Message message) {
>
>          List<Parameter>  params = operation.getParameters();
>          if (params == null || params.size() == 0)
>              return 0;
>
>          // Get Request QueryParams
>          Set<String>  qParams = getParams((String) message.get(Message.QUERY_STRING));
>          // Get Request Headers
>          java.util.Map<String, String>  qHeader = (java.util.Map<String, String>) message
>                  .get(Message.PROTOCOL_HEADERS);
>
>          int rate = 0;
>          for (Parameter p : params) {
>              switch (p.getType()) {
>              case QUERY:
>                  if (qParams.contains(p.getName()))
>                      rate += 2;
>                  else if (p.getDefaultValue() == null)
>                      rate -= 1;
>                  break;
>              case HEADER:
>                  if (qHeader.containsKey(p.getName()))
>                      rate += 2;
>                  else if (p.getDefaultValue() == null)
>                      rate -= 1;
>                  break;
>              default:
>                  break;
>              }
>          }
>          return rate;
>      }
>
>      /**
>       * @param query
>       *            URL Query Example: 'key=value&key2=value2'
>       * @return A Set of all keys, contained within query.
>       */
>      protected Set<String>  getParams(String query) {
>          Set<String>  params = new HashSet<String>();
>          if (query == null || query.length() == 0)
>              return params;
>
>          try {
>              for (String param : query.split("&")) {
>                  String pair[] = param.split("=");
>                  String key = URLDecoder.decode(pair[0], "UTF-8");
>                  params.add(key);
>              }
>          } catch (UnsupportedEncodingException e) {
>              e.printStackTrace();
>          }
>          return params;
>      }
> }
>
>
> I guess it would be nice, if CXF could provide the query params in a Map instead of a String, then my getParams(String query) method would be obsolete.
>
> Regards.
> Jan
>
> From: Sergey Beryozkin-5 [via CXF] [mailto:ml-node+s547215n5719465h66@n5.nabble.com]
> Sent: Donnerstag, 29. November 2012 19:02
> To: Jan Bernhardt
> Subject: Re: REST Method selection for different QueryParam's
>
> On 29/11/12 17:58, Sergey Beryozkin wrote:
>> Hi Jan
>>
>> OK, thanks for all the info, looks promising...
>> It just occurred to me, you do not have to check the annotations,
>> OperationResourceInfo has it all cached, get the list of parameters, and
>> check parameter types (can be query. matrix, etc), this will be much
>> faster too, please give it a try...The parameter will have an index into
>> an array of the Method parameter array, Method can be found from
>> ori.getMethodToInvoke()
>
> Actually, you don't even need a method. Parameter will have name,
> default value, and parameter type
>
> Sergey
>
>>
>> Cheers, Sergey
>>
>>
>>
>> On 29/11/12 17:23, janb wrote:
>>> Hi Sergey,
>>>
>>> my Comperator is not simply checking the number of parameters, but
>>> rather the matching of a query parameter name!
>>>
>>> Here are a couple an examples related to the sample code on the
>>> mentioned wiki page:
>>>
>>> Query:
>>> http://localhost:8080/paramTest?foo=Hello
>>>
>>> Rating:
>>>
>>> +2 : getFoo(@QueryParam("foo") String foo)
>>>
>>> +1 : getFooBar(@QueryParam("foo") String foo, @QueryParam("bar")
>>> String bar)
>>>
>>> // Some addition samples
>>>
>>> 0 : getFooBar()
>>>
>>> -1 : getFooBar(@QueryParam("bar") String bar)
>>>
>>>
>>>
>>> So the first method would be selected.
>>>
>>>
>>> Query:
>>> http://localhost:8080/paramTest?bar=Hello
>>>
>>> Rating:
>>>
>>> -1 : getFoo(@QueryParam("foo") String foo)
>>>
>>> +1 : getFooBar(@QueryParam("foo") String foo, @QueryParam("bar")
>>> String bar)
>>>
>>> // Some addition samples
>>>
>>> 0 : getFooBar()
>>>
>>> +2 : getFooBar(@QueryParam("bar") String bar)
>>>
>>>
>>> As you can see form the number, the last one is the perfect match,
>>> hence it would be selected. If this method would not exist the second
>>> would still be the second best match, and so forth...
>>>
>>> Query:
>>> http://localhost:8080/paramTest?something=Hello
>>>
>>> Rating:
>>>
>>> -1 : getFoo(@QueryParam("foo") String foo)
>>>
>>> -2 : getFooBar(@QueryParam("foo") String foo, @QueryParam("bar")
>>> String bar)
>>>
>>> // Some addition samples
>>>
>>> 0 : getFooBar()
>>>
>>> -1 : getFooBar(@QueryParam("bar") String bar)
>>>
>>>
>>> Parameter something is not in any of the methods, therefore
>>> getFooBar()would be the best choice.
>>>
>>> Query:
>>> http://localhost:8080/paramTest
>>>
>>> Rating:
>>>
>>> -1 : getFoo(@QueryParam("foo") String foo)
>>>
>>> -2 : getFooBar(@QueryParam("foo") String foo, @QueryParam("bar")
>>> String bar)
>>>
>>> // Some addition samples
>>>
>>> 0 : getFooBar()
>>>
>>> -1 : getFooBar(@QueryParam("bar") String bar)
>>>
>>>
>>> If no parameters are provided, all method expecting parameters will
>>> get a negative rating, hence getFooBar() will be selected, which would
>>> be the perfect match.
>>>
>>> So from my understanding, this selection algorithm always provides the
>>> best choice, in cases where several methods match the same path and
>>> type. And I think headers could also be handled in the same way. If a
>>> header is present in a method signature, but this header is not
>>> provided within the request, this method will get -1 in the rating,
>>> otherwise if a header is requested and also provided in a request the
>>> rating will get +2. At the end the method with the highest score will
>>> be selected, since it has most matches and fewest mismatches compared
>>> to all other available methods.
>>>
>>> WDYT?
>>>
>>> By the way, I updated some parts of the code, since they did not
>>> handle Annotations within an Interface. Here is the refactored code to
>>> update the wiki page:
>>>
>>> package org.apache.syncope.core.rest;
>>>
>>> import java.io.UnsupportedEncodingException;
>>> import java.lang.annotation.Annotation;
>>> import java.net.URLDecoder;
>>> import java.util.HashMap;
>>> import java.util.HashSet;
>>> import java.util.Map;
>>> import java.util.Set;
>>>
>>> import javax.ws.rs.DefaultValue;
>>> import javax.ws.rs.QueryParam;
>>>
>>> import org.apache.cxf.jaxrs.ext.ResourceComparator;
>>> import org.apache.cxf.jaxrs.model.ClassResourceInfo;
>>> import org.apache.cxf.jaxrs.model.OperationResourceInfo;
>>> import org.apache.cxf.jaxrs.model.OperationResourceInfoComparator;
>>> import org.apache.cxf.message.Message;
>>>
>>> public class QueryResourceInfoComperator extends
>>> OperationResourceInfoComparator implements
>>> ResourceComparator {
>>>
>>> public QueryResourceInfoComperator() {
>>> super(null, null);
>>> }
>>>
>>> @Override
>>> public int compare(ClassResourceInfo cri1, ClassResourceInfo cri2,
>>> Message message) {
>>> // Leave Class selection to CXF
>>> return 0;
>>> }
>>>
>>> @Override
>>> public int compare(OperationResourceInfo oper1, OperationResourceInfo
>>> oper2, Message message) {
>>>
>>> // Check if CXF can make a decision
>>> int cxfResult = super.compare(oper1, oper2);
>>> if (cxfResult != 0)
>>> return cxfResult;
>>>
>>> // Compare QueryParam annotations
>>> Set<String>  qParams = getParams((String)
>>> message.get(Message.QUERY_STRING));
>>> Map<String, Boolean>  op1Annos = getAnnotations(oper1);
>>> Map<String, Boolean>  op2Annos = getAnnotations(oper2);
>>>
>>> int op1Counter = getMatchingRate(op1Annos, qParams);
>>> int op2Counter = getMatchingRate(op2Annos, qParams);
>>>
>>> return op1Counter == op2Counter
>>> ? 0
>>> : op1Counter<  op2Counter
>>> ? 1
>>> : -1;
>>> }
>>>
>>> /**
>>> * This method calculates a number indicating a good or bad match between
>>> * queryParams from request and annotated method parameters. A higher
>>> number
>>> * means a better match.
>>> *
>>> * @param annotations
>>> * Map contains name of QueryParam method parameters as a key and
>>> * a Boolean value indicating an existing default value for this
>>> * parameter.
>>> * @param queryParams
>>> * A Set of query parameters provided within the request
>>> * @return A positive or negative number, indicating a good match between
>>> * query and method
>>> */
>>> protected int getMatchingRate(Map<String, Boolean>  annotations,
>>> Set<String>  queryParams) {
>>> int rate = 0;
>>> for (String anno : annotations.keySet()) {
>>> if (queryParams.contains(anno)) {
>>> // URL query matches one method parameter
>>> rate += 2;
>>> } else if (!annotations.get(anno).booleanValue()) {
>>> // No default value exists for method parameter
>>> rate -= 1;
>>> }
>>> }
>>> return rate;
>>> }
>>>
>>> /**
>>> * @param opInfo
>>> * OperationInfo to check for parameter annotations
>>> * @return Key in Map is QueryParam name, and Value indicates if a default
>>> * value is present.
>>> */
>>> protected Map<String, Boolean>  getAnnotations(OperationResourceInfo
>>> opInfo) {
>>> Map<String, Boolean>  opAnnos = new HashMap<String, Boolean>();
>>> opAnnos.putAll(getAnnotations(opInfo.getAnnotatedMethod().getParameterAnnotations()));
>>>
>>> opAnnos.putAll(getAnnotations(opInfo.getMethodToInvoke().getParameterAnnotations()));
>>>
>>> return opAnnos;
>>> }
>>>
>>> /**
>>> * @param opParamAnnos
>>> * Array containing all annotations for all method parameters
>>> * @return Key in Map is QueryParam name, and Value indicates if a default
>>> * value is present.
>>> */
>>> protected Map<String, Boolean>  getAnnotations(Annotation[][]
>>> opParamAnnos) {
>>> Map<String, Boolean>  parameterAnnos = new HashMap<String, Boolean>();
>>>
>>> if (opParamAnnos.length == 0)
>>> return parameterAnnos;
>>>
>>> for (Annotation[] pAnnos : opParamAnnos) {
>>> if (pAnnos.length>  0) {
>>> QueryParam qParam = null;
>>> DefaultValue dValue = null;
>>> for (Annotation anno : pAnnos) {
>>> if (anno instanceof QueryParam)
>>> qParam = (QueryParam) anno;
>>> if (anno instanceof DefaultValue)
>>> dValue = (DefaultValue) anno;
>>> }
>>> parameterAnnos.put(qParam.value(), Boolean.valueOf((dValue != null)));
>>> }
>>> }
>>> return parameterAnnos;
>>> }
>>>
>>> /**
>>> * @param query
>>> * URL Query
>>> * @return A Set of all keys, contained within query.
>>> */
>>> protected Set<String>  getParams(String query) {
>>> Set<String>  params = new HashSet<String>();
>>> if (query == null || query.length() == 0)
>>> return params;
>>>
>>> try {
>>> for (String param : query.split("&")) {
>>> String pair[] = param.split("=");
>>> String key = URLDecoder.decode(pair[0], "UTF-8");
>>> params.add(key);
>>> }
>>> } catch (UnsupportedEncodingException e) {
>>> e.printStackTrace();
>>> }
>>> return params;
>>> }
>>> }
>>>
>>>
>>> Best regards
>>> Jan
>>>
>>> From: Sergey Beryozkin-5 [via CXF]
>>> [mailto:[hidden email]</user/SendEmail.jtp?type=node&node=5719465&i=0>]
>>> Sent: Donnerstag, 29. November 2012 17:51
>>> To: Jan Bernhardt
>>> Subject: Re: REST Method selection for different QueryParam's
>>>
>>> Hi Jan
>>>
>>> I've posted it to the
>>> wiki:https://cwiki.apache.org/confluence/display/CXF20DOC/JAX-RS+Basics#JAX-RSBasics-Customselectionbetweenmultipleresources
>>>
>>>
>>> I guess we might indeed can add a more general comparator which would
>>> select the methods based on the number of optional parameters (of any
>>> type such as query, header, and the combination, etc), be they single or
>>> repetitive.
>>>
>>> I'm just yet sure if that will work well or not, example,
>>> what if we have a single query parameter but it has the name which is
>>> not expected by a method expecting a single parameter, etc... May be we
>>> will tune it in time :-)
>>>
>>> Thanks, Sergey
>>>
>>>
>>>
>>> On 27/11/12 16:40, Sergey Beryozkin wrote:
>>>
>>>> Hi Jan
>>>>
>>>> This looks very neat, more comments below
>>>> On 27/11/12 15:25, janb wrote:
>>>>> Hi Sergey,
>>>>>
>>>>> Thank you for your reply. If just developed a generic sample that uses
>>>>> CXF standard class and operation selection algorithms, but extends it
>>>>> with a QueryParam selection algorithm. If all other conditions in CXF
>>>>> match and hence CXF cannot decide which method to select best, my
>>>>> QueryParam selection algorithm comes into place. This algorithm
>>>>> chooses the method with most matches between provided parameters in
>>>>> query and matching QueryParam annotations within the method.
>>>>> Additional (not matching) QueryParam annotations will decrease the
>>>>> matching rate, while method parameters with a default value will be
>>>>> ignored (in the rating).
>>>>>
>>>>> If you think this code is of any value for CXF, please feel free to
>>>>> extend the current selection strategy with my QueryParam selection
>>>>> algorithm. I guess the best place for my algorithm would be another
>>>>> JAXRSUtils method, like
>>>>> JAXRSUtils.compareQueryParams(...) to be called at the end of
>>>>> OperationResourceInfoComparator.compare(...).
>>>>>
>>>>>
>>>>> public class QueryResourceInfoComperator extends
>>>>> OperationResourceInfoComparator implements
>>>>> ResourceComparator {
>>>>>
>>>>> public QueryResourceInfoComperator() {
>>>>> super(null, null);
>>>>> }
>>>>>
>>>>> @Override
>>>>> public int compare(ClassResourceInfo cri1, ClassResourceInfo cri2,
>>>>> Message message) {
>>>>> // Leave Class selection to CXF
>>>>> return 0;
>>>>> }
>>>>>
>>>>> @Override
>>>>> public int compare(OperationResourceInfo oper1, OperationResourceInfo
>>>>> oper2, Message message) {
>>>>>
>>>>> // Check if CXF can make a decision
>>>>> int cxfResult = super.compare(oper1, oper2);
>>>>> if (cxfResult != 0)
>>>>> return cxfResult;
>>>>>
>>>>> // Compare QueryParam annotations
>>>>> Set<String>  qParams = getParams((String)
>>>>> message.get(Message.QUERY_STRING));
>>>>> int op1Counter =
>>>>> getMatchingRate(getAnnotations(oper1.getMethodToInvoke().getParameterAnnotations()),
>>>>>
>>>>>
>>>>> qParams);
>>>>> int op2Counter =
>>>>> getMatchingRate(getAnnotations(oper2.getMethodToInvoke().getParameterAnnotations()),
>>>>>
>>>>>
>>>>> qParams);
>>>>>
>>>>> return op1Counter == op2Counter
>>>>> ? 0
>>>>> : op1Counter<  op2Counter
>>>>> ? 1
>>>>> : -1;
>>>>> }
>>>>>
>>>>> /**
>>>>> * This method calculates a number indicating a good or bad match
>>>>> between
>>>>> * queryParams from request and annotated method parameters. A higher
>>>>> number
>>>>> * means a better match.
>>>>> *
>>>>> * @param annotations
>>>>> * Map contains name of QueryParam method parameters as a key and
>>>>> * a Boolean value indicating an existing default value for this
>>>>> * parameter.
>>>>> * @param queryParams
>>>>> * A Set of query parameters provided within the request
>>>>> * @return A positive or negative number, indicating a good match
>>>>> between
>>>>> * query and method
>>>>> */
>>>>> protected int getMatchingRate(Map<String, Boolean>  annotations,
>>>>> Set<String>  queryParams) {
>>>>> int rate = 0;
>>>>> for (String anno : annotations.keySet()) {
>>>>> if (queryParams.contains(anno)) {
>>>>> // URL query matches one method parameter
>>>>> rate += 2;
>>>>> } else if (!annotations.get(anno).booleanValue()) {
>>>>> // No default value exists for method parameter
>>>>> rate -= 1;
>>>>> }
>>>>> }
>>>>> return rate;
>>>>> }
>>>>>
>>>>> /**
>>>>> * @param opParamAnnos
>>>>> * Array containing all annotations for all method parameters
>>>>> * @return Key in Map is QueryParam name, and Value indicates if a
>>>>> default
>>>>> * value is present.
>>>>> */
>>>>> protected Map<String, Boolean>  getAnnotations(Annotation[][]
>>>>> opParamAnnos) {
>>>>> Map<String, Boolean>  parameterAnnos = new HashMap<String, Boolean>();
>>>>>
>>>>> if (opParamAnnos.length == 0)
>>>>> return parameterAnnos;
>>>>>
>>>>> for (Annotation[] pAnnos : opParamAnnos) {
>>>>> QueryParam qParam = null;
>>>>> DefaultValue dValue = null;
>>>>> for (Annotation anno : pAnnos) {
>>>>> if (anno instanceof QueryParam)
>>>>> qParam = (QueryParam) anno;
>>>>> if (anno instanceof DefaultValue)
>>>>> dValue = (DefaultValue) anno;
>>>>> }
>>>>> parameterAnnos.put(qParam.value(), Boolean.valueOf((dValue != null)));
>>>>> }
>>>>>
>>>>> return parameterAnnos;
>>>>> }
>>>>>
>>>>> /**
>>>>> * @param query
>>>>> * URL Query
>>>>> * @return A Set of all keys, contained within query.
>>>>> */
>>>>> protected Set<String>  getParams(String query) {
>>>>> Set<String>  params = new HashSet<String>();
>>>>> if (query == null || query.length() == 0)
>>>>> return params;
>>>>>
>>>>> try {
>>>>> for (String param : query.split("&")) {
>>>>> String pair[] = param.split("=");
>>>>> String key = URLDecoder.decode(pair[0], "UTF-8");
>>>>> params.add(key);
>>>>> }
>>>>> } catch (UnsupportedEncodingException e) {
>>>>> e.printStackTrace();
>>>>> }
>>>>> return params;
>>>>> }
>>>>> }
>>>>>
>>>>
>>>> I think it is difficult to generalize given that another user may want a
>>>> similar support for the selection based on matrix/header/form
>>>> parameters, and the other complexity is that we can have repeating
>>>> parameters, example, "a=1&a=2" query, etc.
>>>>
>>>> However it definitely makes sense to update the docs and show what does
>>>> it mean to customize the selection algo, so what I will do is I will
>>>> update the page and paste the code - lets see may be we can push some of
>>>> the code to CXF eventually
>>>>
>>>> thanks, Sergey
>>>>
>>>>> Best regards.
>>>>> Jan
>>>>>
>>>>> From: Sergey Beryozkin-5 [via CXF]
>>>>> [mailto:[hidden email]</user/SendEmail.jtp?type=node&node=5719454&i=0>]
>>>>> Sent: Dienstag, 27. November 2012 11:31
>>>>> To: Jan Bernhardt
>>>>> Subject: Re: REST Method selection for different QueryParam's
>>>>>
>>>>> Hi Jan
>>>>> On 27/11/12 10:12, janb wrote:
>>>>>
>>>>>> Hi @all,
>>>>>>
>>>>>> I'm wondering about the selection strategy in CXF for different Query
>>>>>> Parameter. The documentation [1] does not cover this at all.
>>>>>>
>>>>>> A simple system-test provided the impression to me, that CXF has no
>>>>>> valid
>>>>>> selection strategy in place for handling different query parameters.
>>>>>> Is this
>>>>>> assumption correct? Should I create a Jira ticket for a better
>>>>>> support?
>>>>>>
>>>>>> Here is my sample code. No matter which parameter have been provided
>>>>>> within
>>>>>> my URL, only and always the first method was selected by CXF.
>>>>>>
>>>>>> @Path("/paramTest")
>>>>>> public class MySimpleService {
>>>>>>
>>>>>> @GET
>>>>>> public String getFoo(@QueryParam("foo") String foo){
>>>>>> return "foo:" + foo;
>>>>>> }
>>>>>>
>>>>>> @GET
>>>>>> public String getFooBar(@QueryParam("foo") String foo,
>>>>>> @QueryParam("bar") String bar){
>>>>>> return "foo:" + foo + " bar:" + bar;
>>>>>> }
>>>>>>
>>>>>> @GET
>>>>>> public String getTest(@QueryParam("test") String test){
>>>>>> return "test:" + test;
>>>>>> }
>>>>>> }
>>>>>>
>>>>>> [1]
>>>>>> http://cxf.apache.org/docs/jax-rs-basics.html#JAX-RSBasics-Overviewoftheselectionalgorithm.
>>>>>>
>>>>>>
>>>>>>
>>>>>
>>>>> Only URI path segment, HTTP method and in/out media types are taken
>>>>> into
>>>>> consideration when selecting the candidates. If you prefer to have
>>>>> individual methods having the same HTTP Method/Uri Path/Media Types but
>>>>> with specific query parameters then the only way to get it managed
>>>>> is to
>>>>> use a CXF ResourceComparator where, in this case, you can affect the
>>>>> ordering of specific resource methods by checking the current
>>>>> Message.QUERY_STRING available on the CXF Message
>>>>>
>>>>> A simpler alternative is to have a single method and work with
>>>>> UriInfo, say
>>>>>
>>>>> @Context
>>>>> private UriInfo ui;
>>>>>
>>>>> @GET
>>>>> public String getFooOrBar() {
>>>>> MultivaluedMap<String, String>  params = ui.getQueryParameters();
>>>>> String foo = params.getFirst("foo");
>>>>> String bar = params.getFirst("foo");
>>>>> // etc
>>>>> }
>>>>>
>>>>> HTH, Sergey
>>>>>
>>>>>>
>>>>>>
>>>>>> --
>>>>>> View this message in context:
>>>>>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187.html
>>>>>>
>>>>>>
>>>>>> Sent from the cxf-user mailing list archive at Nabble.com.
>>>>>
>>>>>
>>>>> --
>>>>> Sergey Beryozkin
>>>>>
>>>>> Talend Community Coders
>>>>> http://coders.talend.com/
>>>>>
>>>>> Blog: http://sberyozkin.blogspot.com
>>>>>
>>>>> ________________________________
>>>>> If you reply to this email, your message will be added to the
>>>>> discussion below:
>>>>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719190.html
>>>>>
>>>>>
>>>>> To unsubscribe from REST Method selection for different QueryParam's,
>>>>> click
>>>>> here<
>>>>>
>>>>> NAML<http://cxf.547215.n5.nabble.com/template/NamlServlet.jtp?macro=macro_viewer&id=instant_html%21nabble%3Aemail.naml&base=nabble.naml.namespaces.BasicNamespace-nabble.view.web.template.NabbleNamespace-nabble.view.web.template.NodeNamespace&breadcrumbs=notify_subscribers%21nabble%3Aemail.naml-instant_emails%21nabble%3Aemail.naml-send_instant_email%21nabble%3Aemail.naml>
>>>>>
>>>>>
>>>>>
>>>>>
>>>>>
>>>>>
>>>>> --
>>>>> View this message in context:
>>>>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719219.html
>>>>>
>>>>>
>>>>> Sent from the cxf-user mailing list archive at Nabble.com.
>>>>
>>>>
>>>
>>>
>>> --
>>> Sergey Beryozkin
>>>
>>> Talend Community Coders
>>> http://coders.talend.com/
>>>
>>> Blog: http://sberyozkin.blogspot.com
>>>
>>> ________________________________
>>> If you reply to this email, your message will be added to the
>>> discussion below:
>>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719454.html
>>>
>>> To unsubscribe from REST Method selection for different QueryParam's,
>>> click
>>> here<
>>>
>>> NAML<http://cxf.547215.n5.nabble.com/template/NamlServlet.jtp?macro=macro_viewer&id=instant_html%21nabble%3Aemail.naml&base=nabble.naml.namespaces.BasicNamespace-nabble.view.web.template.NabbleNamespace-nabble.view.web.template.NodeNamespace&breadcrumbs=notify_subscribers%21nabble%3Aemail.naml-instant_emails%21nabble%3Aemail.naml-send_instant_email%21nabble%3Aemail.naml>
>>>
>>>
>>>
>>>
>>>
>>> --
>>> View this message in context:
>>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719458.html
>>>
>>> Sent from the cxf-user mailing list archive at Nabble.com.
>>
>>
>
>
> --
> Sergey Beryozkin
>
> Talend Community Coders
> http://coders.talend.com/
>
> Blog: http://sberyozkin.blogspot.com
>
> ________________________________
> If you reply to this email, your message will be added to the discussion below:
> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719465.html
> To unsubscribe from REST Method selection for different QueryParam's, click here<http://cxf.547215.n5.nabble.com/template/NamlServlet.jtp?macro=unsubscribe_by_code&node=5719187&code=amJlcm5oYXJkdEB0YWxlbmQuY29tfDU3MTkxODd8LTEzMDQ4ODk1MjM=>.
> NAML<http://cxf.547215.n5.nabble.com/template/NamlServlet.jtp?macro=macro_viewer&id=instant_html%21nabble%3Aemail.naml&base=nabble.naml.namespaces.BasicNamespace-nabble.view.web.template.NabbleNamespace-nabble.view.web.template.NodeNamespace&breadcrumbs=notify_subscribers%21nabble%3Aemail.naml-instant_emails%21nabble%3Aemail.naml-send_instant_email%21nabble%3Aemail.naml>
>
>
>
>
> --
> View this message in context: http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719471.html
> Sent from the cxf-user mailing list archive at Nabble.com.


-- 
Sergey Beryozkin

Talend Community Coders
http://coders.talend.com/

Blog: http://sberyozkin.blogspot.com

RE: REST Method selection for different QueryParam's

Posted by janb <jb...@talend.com>.
Hi Sergey,

I think we are getting close to a real solution ;-)

Your advice was very helpful, to optimize the code. I now also added support for headers, so they will also be relevant for the rating.

Here is my code:

package org.apache.syncope.core.rest;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.cxf.jaxrs.ext.ResourceComparator;
import org.apache.cxf.jaxrs.model.ClassResourceInfo;
import org.apache.cxf.jaxrs.model.OperationResourceInfo;
import org.apache.cxf.jaxrs.model.OperationResourceInfoComparator;
import org.apache.cxf.jaxrs.model.Parameter;
import org.apache.cxf.message.Message;

public class QueryResourceInfoComperator extends OperationResourceInfoComparator implements
        ResourceComparator {

    public QueryResourceInfoComperator() {
        super(null, null);
    }

    @Override
    public int compare(ClassResourceInfo cri1, ClassResourceInfo cri2, Message message) {
        // Leave Class selection to CXF
        return 0;
    }

    @Override
    public int compare(OperationResourceInfo oper1, OperationResourceInfo oper2, Message message) {

        // Check if CXF can make a decision
        int cxfResult = super.compare(oper1, oper2);
        if (cxfResult != 0)
            return cxfResult;

        int op1Counter = getMatchingRate(oper1, message);
        int op2Counter = getMatchingRate(oper2, message);

        return op1Counter == op2Counter
                ? 0
                : op1Counter < op2Counter
                        ? 1
                        : -1;
    }

    /**
     * This method calculates a number indicating a good or bad match between
     * values provided within the request and expected method parameters. A
     * higher number means a better match.
     *
     * @param operation
     *            The operation to be rated, based on contained parameterInfo
     *            values.
     * @param message
     *            A message containing query and header values from user request
     * @return A positive or negative number, indicating a good match between
     *         query and method
     */
    protected int getMatchingRate(OperationResourceInfo operation, Message message) {

        List<Parameter> params = operation.getParameters();
        if (params == null || params.size() == 0)
            return 0;

        // Get Request QueryParams
        Set<String> qParams = getParams((String) message.get(Message.QUERY_STRING));
        // Get Request Headers
        java.util.Map<String, String> qHeader = (java.util.Map<String, String>) message
                .get(Message.PROTOCOL_HEADERS);

        int rate = 0;
        for (Parameter p : params) {
            switch (p.getType()) {
            case QUERY:
                if (qParams.contains(p.getName()))
                    rate += 2;
                else if (p.getDefaultValue() == null)
                    rate -= 1;
                break;
            case HEADER:
                if (qHeader.containsKey(p.getName()))
                    rate += 2;
                else if (p.getDefaultValue() == null)
                    rate -= 1;
                break;
            default:
                break;
            }
        }
        return rate;
    }

    /**
     * @param query
     *            URL Query Example: 'key=value&key2=value2'
     * @return A Set of all keys, contained within query.
     */
    protected Set<String> getParams(String query) {
        Set<String> params = new HashSet<String>();
        if (query == null || query.length() == 0)
            return params;

        try {
            for (String param : query.split("&")) {
                String pair[] = param.split("=");
                String key = URLDecoder.decode(pair[0], "UTF-8");
                params.add(key);
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return params;
    }
}


I guess it would be nice, if CXF could provide the query params in a Map instead of a String, then my getParams(String query) method would be obsolete.

Regards.
Jan

From: Sergey Beryozkin-5 [via CXF] [mailto:ml-node+s547215n5719465h66@n5.nabble.com]
Sent: Donnerstag, 29. November 2012 19:02
To: Jan Bernhardt
Subject: Re: REST Method selection for different QueryParam's

On 29/11/12 17:58, Sergey Beryozkin wrote:
> Hi Jan
>
> OK, thanks for all the info, looks promising...
> It just occurred to me, you do not have to check the annotations,
> OperationResourceInfo has it all cached, get the list of parameters, and
> check parameter types (can be query. matrix, etc), this will be much
> faster too, please give it a try...The parameter will have an index into
> an array of the Method parameter array, Method can be found from
> ori.getMethodToInvoke()

Actually, you don't even need a method. Parameter will have name,
default value, and parameter type

Sergey

>
> Cheers, Sergey
>
>
>
> On 29/11/12 17:23, janb wrote:
>> Hi Sergey,
>>
>> my Comperator is not simply checking the number of parameters, but
>> rather the matching of a query parameter name!
>>
>> Here are a couple an examples related to the sample code on the
>> mentioned wiki page:
>>
>> Query:
>> http://localhost:8080/paramTest?foo=Hello
>>
>> Rating:
>>
>> +2 : getFoo(@QueryParam("foo") String foo)
>>
>> +1 : getFooBar(@QueryParam("foo") String foo, @QueryParam("bar")
>> String bar)
>>
>> // Some addition samples
>>
>> 0 : getFooBar()
>>
>> -1 : getFooBar(@QueryParam("bar") String bar)
>>
>>
>>
>> So the first method would be selected.
>>
>>
>> Query:
>> http://localhost:8080/paramTest?bar=Hello
>>
>> Rating:
>>
>> -1 : getFoo(@QueryParam("foo") String foo)
>>
>> +1 : getFooBar(@QueryParam("foo") String foo, @QueryParam("bar")
>> String bar)
>>
>> // Some addition samples
>>
>> 0 : getFooBar()
>>
>> +2 : getFooBar(@QueryParam("bar") String bar)
>>
>>
>> As you can see form the number, the last one is the perfect match,
>> hence it would be selected. If this method would not exist the second
>> would still be the second best match, and so forth...
>>
>> Query:
>> http://localhost:8080/paramTest?something=Hello
>>
>> Rating:
>>
>> -1 : getFoo(@QueryParam("foo") String foo)
>>
>> -2 : getFooBar(@QueryParam("foo") String foo, @QueryParam("bar")
>> String bar)
>>
>> // Some addition samples
>>
>> 0 : getFooBar()
>>
>> -1 : getFooBar(@QueryParam("bar") String bar)
>>
>>
>> Parameter something is not in any of the methods, therefore
>> getFooBar()would be the best choice.
>>
>> Query:
>> http://localhost:8080/paramTest
>>
>> Rating:
>>
>> -1 : getFoo(@QueryParam("foo") String foo)
>>
>> -2 : getFooBar(@QueryParam("foo") String foo, @QueryParam("bar")
>> String bar)
>>
>> // Some addition samples
>>
>> 0 : getFooBar()
>>
>> -1 : getFooBar(@QueryParam("bar") String bar)
>>
>>
>> If no parameters are provided, all method expecting parameters will
>> get a negative rating, hence getFooBar() will be selected, which would
>> be the perfect match.
>>
>> So from my understanding, this selection algorithm always provides the
>> best choice, in cases where several methods match the same path and
>> type. And I think headers could also be handled in the same way. If a
>> header is present in a method signature, but this header is not
>> provided within the request, this method will get -1 in the rating,
>> otherwise if a header is requested and also provided in a request the
>> rating will get +2. At the end the method with the highest score will
>> be selected, since it has most matches and fewest mismatches compared
>> to all other available methods.
>>
>> WDYT?
>>
>> By the way, I updated some parts of the code, since they did not
>> handle Annotations within an Interface. Here is the refactored code to
>> update the wiki page:
>>
>> package org.apache.syncope.core.rest;
>>
>> import java.io.UnsupportedEncodingException;
>> import java.lang.annotation.Annotation;
>> import java.net.URLDecoder;
>> import java.util.HashMap;
>> import java.util.HashSet;
>> import java.util.Map;
>> import java.util.Set;
>>
>> import javax.ws.rs.DefaultValue;
>> import javax.ws.rs.QueryParam;
>>
>> import org.apache.cxf.jaxrs.ext.ResourceComparator;
>> import org.apache.cxf.jaxrs.model.ClassResourceInfo;
>> import org.apache.cxf.jaxrs.model.OperationResourceInfo;
>> import org.apache.cxf.jaxrs.model.OperationResourceInfoComparator;
>> import org.apache.cxf.message.Message;
>>
>> public class QueryResourceInfoComperator extends
>> OperationResourceInfoComparator implements
>> ResourceComparator {
>>
>> public QueryResourceInfoComperator() {
>> super(null, null);
>> }
>>
>> @Override
>> public int compare(ClassResourceInfo cri1, ClassResourceInfo cri2,
>> Message message) {
>> // Leave Class selection to CXF
>> return 0;
>> }
>>
>> @Override
>> public int compare(OperationResourceInfo oper1, OperationResourceInfo
>> oper2, Message message) {
>>
>> // Check if CXF can make a decision
>> int cxfResult = super.compare(oper1, oper2);
>> if (cxfResult != 0)
>> return cxfResult;
>>
>> // Compare QueryParam annotations
>> Set<String> qParams = getParams((String)
>> message.get(Message.QUERY_STRING));
>> Map<String, Boolean> op1Annos = getAnnotations(oper1);
>> Map<String, Boolean> op2Annos = getAnnotations(oper2);
>>
>> int op1Counter = getMatchingRate(op1Annos, qParams);
>> int op2Counter = getMatchingRate(op2Annos, qParams);
>>
>> return op1Counter == op2Counter
>> ? 0
>> : op1Counter< op2Counter
>> ? 1
>> : -1;
>> }
>>
>> /**
>> * This method calculates a number indicating a good or bad match between
>> * queryParams from request and annotated method parameters. A higher
>> number
>> * means a better match.
>> *
>> * @param annotations
>> * Map contains name of QueryParam method parameters as a key and
>> * a Boolean value indicating an existing default value for this
>> * parameter.
>> * @param queryParams
>> * A Set of query parameters provided within the request
>> * @return A positive or negative number, indicating a good match between
>> * query and method
>> */
>> protected int getMatchingRate(Map<String, Boolean> annotations,
>> Set<String> queryParams) {
>> int rate = 0;
>> for (String anno : annotations.keySet()) {
>> if (queryParams.contains(anno)) {
>> // URL query matches one method parameter
>> rate += 2;
>> } else if (!annotations.get(anno).booleanValue()) {
>> // No default value exists for method parameter
>> rate -= 1;
>> }
>> }
>> return rate;
>> }
>>
>> /**
>> * @param opInfo
>> * OperationInfo to check for parameter annotations
>> * @return Key in Map is QueryParam name, and Value indicates if a default
>> * value is present.
>> */
>> protected Map<String, Boolean> getAnnotations(OperationResourceInfo
>> opInfo) {
>> Map<String, Boolean> opAnnos = new HashMap<String, Boolean>();
>> opAnnos.putAll(getAnnotations(opInfo.getAnnotatedMethod().getParameterAnnotations()));
>>
>> opAnnos.putAll(getAnnotations(opInfo.getMethodToInvoke().getParameterAnnotations()));
>>
>> return opAnnos;
>> }
>>
>> /**
>> * @param opParamAnnos
>> * Array containing all annotations for all method parameters
>> * @return Key in Map is QueryParam name, and Value indicates if a default
>> * value is present.
>> */
>> protected Map<String, Boolean> getAnnotations(Annotation[][]
>> opParamAnnos) {
>> Map<String, Boolean> parameterAnnos = new HashMap<String, Boolean>();
>>
>> if (opParamAnnos.length == 0)
>> return parameterAnnos;
>>
>> for (Annotation[] pAnnos : opParamAnnos) {
>> if (pAnnos.length> 0) {
>> QueryParam qParam = null;
>> DefaultValue dValue = null;
>> for (Annotation anno : pAnnos) {
>> if (anno instanceof QueryParam)
>> qParam = (QueryParam) anno;
>> if (anno instanceof DefaultValue)
>> dValue = (DefaultValue) anno;
>> }
>> parameterAnnos.put(qParam.value(), Boolean.valueOf((dValue != null)));
>> }
>> }
>> return parameterAnnos;
>> }
>>
>> /**
>> * @param query
>> * URL Query
>> * @return A Set of all keys, contained within query.
>> */
>> protected Set<String> getParams(String query) {
>> Set<String> params = new HashSet<String>();
>> if (query == null || query.length() == 0)
>> return params;
>>
>> try {
>> for (String param : query.split("&")) {
>> String pair[] = param.split("=");
>> String key = URLDecoder.decode(pair[0], "UTF-8");
>> params.add(key);
>> }
>> } catch (UnsupportedEncodingException e) {
>> e.printStackTrace();
>> }
>> return params;
>> }
>> }
>>
>>
>> Best regards
>> Jan
>>
>> From: Sergey Beryozkin-5 [via CXF]
>> [mailto:[hidden email]</user/SendEmail.jtp?type=node&node=5719465&i=0>]
>> Sent: Donnerstag, 29. November 2012 17:51
>> To: Jan Bernhardt
>> Subject: Re: REST Method selection for different QueryParam's
>>
>> Hi Jan
>>
>> I've posted it to the
>> wiki:https://cwiki.apache.org/confluence/display/CXF20DOC/JAX-RS+Basics#JAX-RSBasics-Customselectionbetweenmultipleresources
>>
>>
>> I guess we might indeed can add a more general comparator which would
>> select the methods based on the number of optional parameters (of any
>> type such as query, header, and the combination, etc), be they single or
>> repetitive.
>>
>> I'm just yet sure if that will work well or not, example,
>> what if we have a single query parameter but it has the name which is
>> not expected by a method expecting a single parameter, etc... May be we
>> will tune it in time :-)
>>
>> Thanks, Sergey
>>
>>
>>
>> On 27/11/12 16:40, Sergey Beryozkin wrote:
>>
>>> Hi Jan
>>>
>>> This looks very neat, more comments below
>>> On 27/11/12 15:25, janb wrote:
>>>> Hi Sergey,
>>>>
>>>> Thank you for your reply. If just developed a generic sample that uses
>>>> CXF standard class and operation selection algorithms, but extends it
>>>> with a QueryParam selection algorithm. If all other conditions in CXF
>>>> match and hence CXF cannot decide which method to select best, my
>>>> QueryParam selection algorithm comes into place. This algorithm
>>>> chooses the method with most matches between provided parameters in
>>>> query and matching QueryParam annotations within the method.
>>>> Additional (not matching) QueryParam annotations will decrease the
>>>> matching rate, while method parameters with a default value will be
>>>> ignored (in the rating).
>>>>
>>>> If you think this code is of any value for CXF, please feel free to
>>>> extend the current selection strategy with my QueryParam selection
>>>> algorithm. I guess the best place for my algorithm would be another
>>>> JAXRSUtils method, like
>>>> JAXRSUtils.compareQueryParams(...) to be called at the end of
>>>> OperationResourceInfoComparator.compare(...).
>>>>
>>>>
>>>> public class QueryResourceInfoComperator extends
>>>> OperationResourceInfoComparator implements
>>>> ResourceComparator {
>>>>
>>>> public QueryResourceInfoComperator() {
>>>> super(null, null);
>>>> }
>>>>
>>>> @Override
>>>> public int compare(ClassResourceInfo cri1, ClassResourceInfo cri2,
>>>> Message message) {
>>>> // Leave Class selection to CXF
>>>> return 0;
>>>> }
>>>>
>>>> @Override
>>>> public int compare(OperationResourceInfo oper1, OperationResourceInfo
>>>> oper2, Message message) {
>>>>
>>>> // Check if CXF can make a decision
>>>> int cxfResult = super.compare(oper1, oper2);
>>>> if (cxfResult != 0)
>>>> return cxfResult;
>>>>
>>>> // Compare QueryParam annotations
>>>> Set<String> qParams = getParams((String)
>>>> message.get(Message.QUERY_STRING));
>>>> int op1Counter =
>>>> getMatchingRate(getAnnotations(oper1.getMethodToInvoke().getParameterAnnotations()),
>>>>
>>>>
>>>> qParams);
>>>> int op2Counter =
>>>> getMatchingRate(getAnnotations(oper2.getMethodToInvoke().getParameterAnnotations()),
>>>>
>>>>
>>>> qParams);
>>>>
>>>> return op1Counter == op2Counter
>>>> ? 0
>>>> : op1Counter< op2Counter
>>>> ? 1
>>>> : -1;
>>>> }
>>>>
>>>> /**
>>>> * This method calculates a number indicating a good or bad match
>>>> between
>>>> * queryParams from request and annotated method parameters. A higher
>>>> number
>>>> * means a better match.
>>>> *
>>>> * @param annotations
>>>> * Map contains name of QueryParam method parameters as a key and
>>>> * a Boolean value indicating an existing default value for this
>>>> * parameter.
>>>> * @param queryParams
>>>> * A Set of query parameters provided within the request
>>>> * @return A positive or negative number, indicating a good match
>>>> between
>>>> * query and method
>>>> */
>>>> protected int getMatchingRate(Map<String, Boolean> annotations,
>>>> Set<String> queryParams) {
>>>> int rate = 0;
>>>> for (String anno : annotations.keySet()) {
>>>> if (queryParams.contains(anno)) {
>>>> // URL query matches one method parameter
>>>> rate += 2;
>>>> } else if (!annotations.get(anno).booleanValue()) {
>>>> // No default value exists for method parameter
>>>> rate -= 1;
>>>> }
>>>> }
>>>> return rate;
>>>> }
>>>>
>>>> /**
>>>> * @param opParamAnnos
>>>> * Array containing all annotations for all method parameters
>>>> * @return Key in Map is QueryParam name, and Value indicates if a
>>>> default
>>>> * value is present.
>>>> */
>>>> protected Map<String, Boolean> getAnnotations(Annotation[][]
>>>> opParamAnnos) {
>>>> Map<String, Boolean> parameterAnnos = new HashMap<String, Boolean>();
>>>>
>>>> if (opParamAnnos.length == 0)
>>>> return parameterAnnos;
>>>>
>>>> for (Annotation[] pAnnos : opParamAnnos) {
>>>> QueryParam qParam = null;
>>>> DefaultValue dValue = null;
>>>> for (Annotation anno : pAnnos) {
>>>> if (anno instanceof QueryParam)
>>>> qParam = (QueryParam) anno;
>>>> if (anno instanceof DefaultValue)
>>>> dValue = (DefaultValue) anno;
>>>> }
>>>> parameterAnnos.put(qParam.value(), Boolean.valueOf((dValue != null)));
>>>> }
>>>>
>>>> return parameterAnnos;
>>>> }
>>>>
>>>> /**
>>>> * @param query
>>>> * URL Query
>>>> * @return A Set of all keys, contained within query.
>>>> */
>>>> protected Set<String> getParams(String query) {
>>>> Set<String> params = new HashSet<String>();
>>>> if (query == null || query.length() == 0)
>>>> return params;
>>>>
>>>> try {
>>>> for (String param : query.split("&")) {
>>>> String pair[] = param.split("=");
>>>> String key = URLDecoder.decode(pair[0], "UTF-8");
>>>> params.add(key);
>>>> }
>>>> } catch (UnsupportedEncodingException e) {
>>>> e.printStackTrace();
>>>> }
>>>> return params;
>>>> }
>>>> }
>>>>
>>>
>>> I think it is difficult to generalize given that another user may want a
>>> similar support for the selection based on matrix/header/form
>>> parameters, and the other complexity is that we can have repeating
>>> parameters, example, "a=1&a=2" query, etc.
>>>
>>> However it definitely makes sense to update the docs and show what does
>>> it mean to customize the selection algo, so what I will do is I will
>>> update the page and paste the code - lets see may be we can push some of
>>> the code to CXF eventually
>>>
>>> thanks, Sergey
>>>
>>>> Best regards.
>>>> Jan
>>>>
>>>> From: Sergey Beryozkin-5 [via CXF]
>>>> [mailto:[hidden email]</user/SendEmail.jtp?type=node&node=5719454&i=0>]
>>>> Sent: Dienstag, 27. November 2012 11:31
>>>> To: Jan Bernhardt
>>>> Subject: Re: REST Method selection for different QueryParam's
>>>>
>>>> Hi Jan
>>>> On 27/11/12 10:12, janb wrote:
>>>>
>>>>> Hi @all,
>>>>>
>>>>> I'm wondering about the selection strategy in CXF for different Query
>>>>> Parameter. The documentation [1] does not cover this at all.
>>>>>
>>>>> A simple system-test provided the impression to me, that CXF has no
>>>>> valid
>>>>> selection strategy in place for handling different query parameters.
>>>>> Is this
>>>>> assumption correct? Should I create a Jira ticket for a better
>>>>> support?
>>>>>
>>>>> Here is my sample code. No matter which parameter have been provided
>>>>> within
>>>>> my URL, only and always the first method was selected by CXF.
>>>>>
>>>>> @Path("/paramTest")
>>>>> public class MySimpleService {
>>>>>
>>>>> @GET
>>>>> public String getFoo(@QueryParam("foo") String foo){
>>>>> return "foo:" + foo;
>>>>> }
>>>>>
>>>>> @GET
>>>>> public String getFooBar(@QueryParam("foo") String foo,
>>>>> @QueryParam("bar") String bar){
>>>>> return "foo:" + foo + " bar:" + bar;
>>>>> }
>>>>>
>>>>> @GET
>>>>> public String getTest(@QueryParam("test") String test){
>>>>> return "test:" + test;
>>>>> }
>>>>> }
>>>>>
>>>>> [1]
>>>>> http://cxf.apache.org/docs/jax-rs-basics.html#JAX-RSBasics-Overviewoftheselectionalgorithm.
>>>>>
>>>>>
>>>>>
>>>>
>>>> Only URI path segment, HTTP method and in/out media types are taken
>>>> into
>>>> consideration when selecting the candidates. If you prefer to have
>>>> individual methods having the same HTTP Method/Uri Path/Media Types but
>>>> with specific query parameters then the only way to get it managed
>>>> is to
>>>> use a CXF ResourceComparator where, in this case, you can affect the
>>>> ordering of specific resource methods by checking the current
>>>> Message.QUERY_STRING available on the CXF Message
>>>>
>>>> A simpler alternative is to have a single method and work with
>>>> UriInfo, say
>>>>
>>>> @Context
>>>> private UriInfo ui;
>>>>
>>>> @GET
>>>> public String getFooOrBar() {
>>>> MultivaluedMap<String, String> params = ui.getQueryParameters();
>>>> String foo = params.getFirst("foo");
>>>> String bar = params.getFirst("foo");
>>>> // etc
>>>> }
>>>>
>>>> HTH, Sergey
>>>>
>>>>>
>>>>>
>>>>> --
>>>>> View this message in context:
>>>>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187.html
>>>>>
>>>>>
>>>>> Sent from the cxf-user mailing list archive at Nabble.com.
>>>>
>>>>
>>>> --
>>>> Sergey Beryozkin
>>>>
>>>> Talend Community Coders
>>>> http://coders.talend.com/
>>>>
>>>> Blog: http://sberyozkin.blogspot.com
>>>>
>>>> ________________________________
>>>> If you reply to this email, your message will be added to the
>>>> discussion below:
>>>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719190.html
>>>>
>>>>
>>>> To unsubscribe from REST Method selection for different QueryParam's,
>>>> click
>>>> here<
>>>>
>>>> NAML<http://cxf.547215.n5.nabble.com/template/NamlServlet.jtp?macro=macro_viewer&id=instant_html%21nabble%3Aemail.naml&base=nabble.naml.namespaces.BasicNamespace-nabble.view.web.template.NabbleNamespace-nabble.view.web.template.NodeNamespace&breadcrumbs=notify_subscribers%21nabble%3Aemail.naml-instant_emails%21nabble%3Aemail.naml-send_instant_email%21nabble%3Aemail.naml>
>>>>
>>>>
>>>>
>>>>
>>>>
>>>>
>>>> --
>>>> View this message in context:
>>>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719219.html
>>>>
>>>>
>>>> Sent from the cxf-user mailing list archive at Nabble.com.
>>>
>>>
>>
>>
>> --
>> Sergey Beryozkin
>>
>> Talend Community Coders
>> http://coders.talend.com/
>>
>> Blog: http://sberyozkin.blogspot.com
>>
>> ________________________________
>> If you reply to this email, your message will be added to the
>> discussion below:
>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719454.html
>>
>> To unsubscribe from REST Method selection for different QueryParam's,
>> click
>> here<
>>
>> NAML<http://cxf.547215.n5.nabble.com/template/NamlServlet.jtp?macro=macro_viewer&id=instant_html%21nabble%3Aemail.naml&base=nabble.naml.namespaces.BasicNamespace-nabble.view.web.template.NabbleNamespace-nabble.view.web.template.NodeNamespace&breadcrumbs=notify_subscribers%21nabble%3Aemail.naml-instant_emails%21nabble%3Aemail.naml-send_instant_email%21nabble%3Aemail.naml>
>>
>>
>>
>>
>>
>> --
>> View this message in context:
>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719458.html
>>
>> Sent from the cxf-user mailing list archive at Nabble.com.
>
>


--
Sergey Beryozkin

Talend Community Coders
http://coders.talend.com/

Blog: http://sberyozkin.blogspot.com

________________________________
If you reply to this email, your message will be added to the discussion below:
http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719465.html
To unsubscribe from REST Method selection for different QueryParam's, click here<http://cxf.547215.n5.nabble.com/template/NamlServlet.jtp?macro=unsubscribe_by_code&node=5719187&code=amJlcm5oYXJkdEB0YWxlbmQuY29tfDU3MTkxODd8LTEzMDQ4ODk1MjM=>.
NAML<http://cxf.547215.n5.nabble.com/template/NamlServlet.jtp?macro=macro_viewer&id=instant_html%21nabble%3Aemail.naml&base=nabble.naml.namespaces.BasicNamespace-nabble.view.web.template.NabbleNamespace-nabble.view.web.template.NodeNamespace&breadcrumbs=notify_subscribers%21nabble%3Aemail.naml-instant_emails%21nabble%3Aemail.naml-send_instant_email%21nabble%3Aemail.naml>




--
View this message in context: http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719471.html
Sent from the cxf-user mailing list archive at Nabble.com.

RE: REST Method selection for different QueryParam's

Posted by janb <jb...@talend.com>.
Hi Sergey,

That's a good hint, I will update my Comperator and report back to you.

Regards.
Jan

From: Sergey Beryozkin-5 [via CXF] [mailto:ml-node+s547215n5719465h66@n5.nabble.com]
Sent: Donnerstag, 29. November 2012 19:02
To: Jan Bernhardt
Subject: Re: REST Method selection for different QueryParam's

On 29/11/12 17:58, Sergey Beryozkin wrote:
> Hi Jan
>
> OK, thanks for all the info, looks promising...
> It just occurred to me, you do not have to check the annotations,
> OperationResourceInfo has it all cached, get the list of parameters, and
> check parameter types (can be query. matrix, etc), this will be much
> faster too, please give it a try...The parameter will have an index into
> an array of the Method parameter array, Method can be found from
> ori.getMethodToInvoke()

Actually, you don't even need a method. Parameter will have name,
default value, and parameter type

Sergey

>
> Cheers, Sergey
>
>
>
> On 29/11/12 17:23, janb wrote:
>> Hi Sergey,
>>
>> my Comperator is not simply checking the number of parameters, but
>> rather the matching of a query parameter name!
>>
>> Here are a couple an examples related to the sample code on the
>> mentioned wiki page:
>>
>> Query:
>> http://localhost:8080/paramTest?foo=Hello
>>
>> Rating:
>>
>> +2 : getFoo(@QueryParam("foo") String foo)
>>
>> +1 : getFooBar(@QueryParam("foo") String foo, @QueryParam("bar")
>> String bar)
>>
>> // Some addition samples
>>
>> 0 : getFooBar()
>>
>> -1 : getFooBar(@QueryParam("bar") String bar)
>>
>>
>>
>> So the first method would be selected.
>>
>>
>> Query:
>> http://localhost:8080/paramTest?bar=Hello
>>
>> Rating:
>>
>> -1 : getFoo(@QueryParam("foo") String foo)
>>
>> +1 : getFooBar(@QueryParam("foo") String foo, @QueryParam("bar")
>> String bar)
>>
>> // Some addition samples
>>
>> 0 : getFooBar()
>>
>> +2 : getFooBar(@QueryParam("bar") String bar)
>>
>>
>> As you can see form the number, the last one is the perfect match,
>> hence it would be selected. If this method would not exist the second
>> would still be the second best match, and so forth...
>>
>> Query:
>> http://localhost:8080/paramTest?something=Hello
>>
>> Rating:
>>
>> -1 : getFoo(@QueryParam("foo") String foo)
>>
>> -2 : getFooBar(@QueryParam("foo") String foo, @QueryParam("bar")
>> String bar)
>>
>> // Some addition samples
>>
>> 0 : getFooBar()
>>
>> -1 : getFooBar(@QueryParam("bar") String bar)
>>
>>
>> Parameter something is not in any of the methods, therefore
>> getFooBar()would be the best choice.
>>
>> Query:
>> http://localhost:8080/paramTest
>>
>> Rating:
>>
>> -1 : getFoo(@QueryParam("foo") String foo)
>>
>> -2 : getFooBar(@QueryParam("foo") String foo, @QueryParam("bar")
>> String bar)
>>
>> // Some addition samples
>>
>> 0 : getFooBar()
>>
>> -1 : getFooBar(@QueryParam("bar") String bar)
>>
>>
>> If no parameters are provided, all method expecting parameters will
>> get a negative rating, hence getFooBar() will be selected, which would
>> be the perfect match.
>>
>> So from my understanding, this selection algorithm always provides the
>> best choice, in cases where several methods match the same path and
>> type. And I think headers could also be handled in the same way. If a
>> header is present in a method signature, but this header is not
>> provided within the request, this method will get -1 in the rating,
>> otherwise if a header is requested and also provided in a request the
>> rating will get +2. At the end the method with the highest score will
>> be selected, since it has most matches and fewest mismatches compared
>> to all other available methods.
>>
>> WDYT?
>>
>> By the way, I updated some parts of the code, since they did not
>> handle Annotations within an Interface. Here is the refactored code to
>> update the wiki page:
>>
>> package org.apache.syncope.core.rest;
>>
>> import java.io.UnsupportedEncodingException;
>> import java.lang.annotation.Annotation;
>> import java.net.URLDecoder;
>> import java.util.HashMap;
>> import java.util.HashSet;
>> import java.util.Map;
>> import java.util.Set;
>>
>> import javax.ws.rs.DefaultValue;
>> import javax.ws.rs.QueryParam;
>>
>> import org.apache.cxf.jaxrs.ext.ResourceComparator;
>> import org.apache.cxf.jaxrs.model.ClassResourceInfo;
>> import org.apache.cxf.jaxrs.model.OperationResourceInfo;
>> import org.apache.cxf.jaxrs.model.OperationResourceInfoComparator;
>> import org.apache.cxf.message.Message;
>>
>> public class QueryResourceInfoComperator extends
>> OperationResourceInfoComparator implements
>> ResourceComparator {
>>
>> public QueryResourceInfoComperator() {
>> super(null, null);
>> }
>>
>> @Override
>> public int compare(ClassResourceInfo cri1, ClassResourceInfo cri2,
>> Message message) {
>> // Leave Class selection to CXF
>> return 0;
>> }
>>
>> @Override
>> public int compare(OperationResourceInfo oper1, OperationResourceInfo
>> oper2, Message message) {
>>
>> // Check if CXF can make a decision
>> int cxfResult = super.compare(oper1, oper2);
>> if (cxfResult != 0)
>> return cxfResult;
>>
>> // Compare QueryParam annotations
>> Set<String> qParams = getParams((String)
>> message.get(Message.QUERY_STRING));
>> Map<String, Boolean> op1Annos = getAnnotations(oper1);
>> Map<String, Boolean> op2Annos = getAnnotations(oper2);
>>
>> int op1Counter = getMatchingRate(op1Annos, qParams);
>> int op2Counter = getMatchingRate(op2Annos, qParams);
>>
>> return op1Counter == op2Counter
>> ? 0
>> : op1Counter< op2Counter
>> ? 1
>> : -1;
>> }
>>
>> /**
>> * This method calculates a number indicating a good or bad match between
>> * queryParams from request and annotated method parameters. A higher
>> number
>> * means a better match.
>> *
>> * @param annotations
>> * Map contains name of QueryParam method parameters as a key and
>> * a Boolean value indicating an existing default value for this
>> * parameter.
>> * @param queryParams
>> * A Set of query parameters provided within the request
>> * @return A positive or negative number, indicating a good match between
>> * query and method
>> */
>> protected int getMatchingRate(Map<String, Boolean> annotations,
>> Set<String> queryParams) {
>> int rate = 0;
>> for (String anno : annotations.keySet()) {
>> if (queryParams.contains(anno)) {
>> // URL query matches one method parameter
>> rate += 2;
>> } else if (!annotations.get(anno).booleanValue()) {
>> // No default value exists for method parameter
>> rate -= 1;
>> }
>> }
>> return rate;
>> }
>>
>> /**
>> * @param opInfo
>> * OperationInfo to check for parameter annotations
>> * @return Key in Map is QueryParam name, and Value indicates if a default
>> * value is present.
>> */
>> protected Map<String, Boolean> getAnnotations(OperationResourceInfo
>> opInfo) {
>> Map<String, Boolean> opAnnos = new HashMap<String, Boolean>();
>> opAnnos.putAll(getAnnotations(opInfo.getAnnotatedMethod().getParameterAnnotations()));
>>
>> opAnnos.putAll(getAnnotations(opInfo.getMethodToInvoke().getParameterAnnotations()));
>>
>> return opAnnos;
>> }
>>
>> /**
>> * @param opParamAnnos
>> * Array containing all annotations for all method parameters
>> * @return Key in Map is QueryParam name, and Value indicates if a default
>> * value is present.
>> */
>> protected Map<String, Boolean> getAnnotations(Annotation[][]
>> opParamAnnos) {
>> Map<String, Boolean> parameterAnnos = new HashMap<String, Boolean>();
>>
>> if (opParamAnnos.length == 0)
>> return parameterAnnos;
>>
>> for (Annotation[] pAnnos : opParamAnnos) {
>> if (pAnnos.length> 0) {
>> QueryParam qParam = null;
>> DefaultValue dValue = null;
>> for (Annotation anno : pAnnos) {
>> if (anno instanceof QueryParam)
>> qParam = (QueryParam) anno;
>> if (anno instanceof DefaultValue)
>> dValue = (DefaultValue) anno;
>> }
>> parameterAnnos.put(qParam.value(), Boolean.valueOf((dValue != null)));
>> }
>> }
>> return parameterAnnos;
>> }
>>
>> /**
>> * @param query
>> * URL Query
>> * @return A Set of all keys, contained within query.
>> */
>> protected Set<String> getParams(String query) {
>> Set<String> params = new HashSet<String>();
>> if (query == null || query.length() == 0)
>> return params;
>>
>> try {
>> for (String param : query.split("&")) {
>> String pair[] = param.split("=");
>> String key = URLDecoder.decode(pair[0], "UTF-8");
>> params.add(key);
>> }
>> } catch (UnsupportedEncodingException e) {
>> e.printStackTrace();
>> }
>> return params;
>> }
>> }
>>
>>
>> Best regards
>> Jan
>>
>> From: Sergey Beryozkin-5 [via CXF]
>> [mailto:[hidden email]</user/SendEmail.jtp?type=node&node=5719465&i=0>]
>> Sent: Donnerstag, 29. November 2012 17:51
>> To: Jan Bernhardt
>> Subject: Re: REST Method selection for different QueryParam's
>>
>> Hi Jan
>>
>> I've posted it to the
>> wiki:https://cwiki.apache.org/confluence/display/CXF20DOC/JAX-RS+Basics#JAX-RSBasics-Customselectionbetweenmultipleresources
>>
>>
>> I guess we might indeed can add a more general comparator which would
>> select the methods based on the number of optional parameters (of any
>> type such as query, header, and the combination, etc), be they single or
>> repetitive.
>>
>> I'm just yet sure if that will work well or not, example,
>> what if we have a single query parameter but it has the name which is
>> not expected by a method expecting a single parameter, etc... May be we
>> will tune it in time :-)
>>
>> Thanks, Sergey
>>
>>
>>
>> On 27/11/12 16:40, Sergey Beryozkin wrote:
>>
>>> Hi Jan
>>>
>>> This looks very neat, more comments below
>>> On 27/11/12 15:25, janb wrote:
>>>> Hi Sergey,
>>>>
>>>> Thank you for your reply. If just developed a generic sample that uses
>>>> CXF standard class and operation selection algorithms, but extends it
>>>> with a QueryParam selection algorithm. If all other conditions in CXF
>>>> match and hence CXF cannot decide which method to select best, my
>>>> QueryParam selection algorithm comes into place. This algorithm
>>>> chooses the method with most matches between provided parameters in
>>>> query and matching QueryParam annotations within the method.
>>>> Additional (not matching) QueryParam annotations will decrease the
>>>> matching rate, while method parameters with a default value will be
>>>> ignored (in the rating).
>>>>
>>>> If you think this code is of any value for CXF, please feel free to
>>>> extend the current selection strategy with my QueryParam selection
>>>> algorithm. I guess the best place for my algorithm would be another
>>>> JAXRSUtils method, like
>>>> JAXRSUtils.compareQueryParams(...) to be called at the end of
>>>> OperationResourceInfoComparator.compare(...).
>>>>
>>>>
>>>> public class QueryResourceInfoComperator extends
>>>> OperationResourceInfoComparator implements
>>>> ResourceComparator {
>>>>
>>>> public QueryResourceInfoComperator() {
>>>> super(null, null);
>>>> }
>>>>
>>>> @Override
>>>> public int compare(ClassResourceInfo cri1, ClassResourceInfo cri2,
>>>> Message message) {
>>>> // Leave Class selection to CXF
>>>> return 0;
>>>> }
>>>>
>>>> @Override
>>>> public int compare(OperationResourceInfo oper1, OperationResourceInfo
>>>> oper2, Message message) {
>>>>
>>>> // Check if CXF can make a decision
>>>> int cxfResult = super.compare(oper1, oper2);
>>>> if (cxfResult != 0)
>>>> return cxfResult;
>>>>
>>>> // Compare QueryParam annotations
>>>> Set<String> qParams = getParams((String)
>>>> message.get(Message.QUERY_STRING));
>>>> int op1Counter =
>>>> getMatchingRate(getAnnotations(oper1.getMethodToInvoke().getParameterAnnotations()),
>>>>
>>>>
>>>> qParams);
>>>> int op2Counter =
>>>> getMatchingRate(getAnnotations(oper2.getMethodToInvoke().getParameterAnnotations()),
>>>>
>>>>
>>>> qParams);
>>>>
>>>> return op1Counter == op2Counter
>>>> ? 0
>>>> : op1Counter< op2Counter
>>>> ? 1
>>>> : -1;
>>>> }
>>>>
>>>> /**
>>>> * This method calculates a number indicating a good or bad match
>>>> between
>>>> * queryParams from request and annotated method parameters. A higher
>>>> number
>>>> * means a better match.
>>>> *
>>>> * @param annotations
>>>> * Map contains name of QueryParam method parameters as a key and
>>>> * a Boolean value indicating an existing default value for this
>>>> * parameter.
>>>> * @param queryParams
>>>> * A Set of query parameters provided within the request
>>>> * @return A positive or negative number, indicating a good match
>>>> between
>>>> * query and method
>>>> */
>>>> protected int getMatchingRate(Map<String, Boolean> annotations,
>>>> Set<String> queryParams) {
>>>> int rate = 0;
>>>> for (String anno : annotations.keySet()) {
>>>> if (queryParams.contains(anno)) {
>>>> // URL query matches one method parameter
>>>> rate += 2;
>>>> } else if (!annotations.get(anno).booleanValue()) {
>>>> // No default value exists for method parameter
>>>> rate -= 1;
>>>> }
>>>> }
>>>> return rate;
>>>> }
>>>>
>>>> /**
>>>> * @param opParamAnnos
>>>> * Array containing all annotations for all method parameters
>>>> * @return Key in Map is QueryParam name, and Value indicates if a
>>>> default
>>>> * value is present.
>>>> */
>>>> protected Map<String, Boolean> getAnnotations(Annotation[][]
>>>> opParamAnnos) {
>>>> Map<String, Boolean> parameterAnnos = new HashMap<String, Boolean>();
>>>>
>>>> if (opParamAnnos.length == 0)
>>>> return parameterAnnos;
>>>>
>>>> for (Annotation[] pAnnos : opParamAnnos) {
>>>> QueryParam qParam = null;
>>>> DefaultValue dValue = null;
>>>> for (Annotation anno : pAnnos) {
>>>> if (anno instanceof QueryParam)
>>>> qParam = (QueryParam) anno;
>>>> if (anno instanceof DefaultValue)
>>>> dValue = (DefaultValue) anno;
>>>> }
>>>> parameterAnnos.put(qParam.value(), Boolean.valueOf((dValue != null)));
>>>> }
>>>>
>>>> return parameterAnnos;
>>>> }
>>>>
>>>> /**
>>>> * @param query
>>>> * URL Query
>>>> * @return A Set of all keys, contained within query.
>>>> */
>>>> protected Set<String> getParams(String query) {
>>>> Set<String> params = new HashSet<String>();
>>>> if (query == null || query.length() == 0)
>>>> return params;
>>>>
>>>> try {
>>>> for (String param : query.split("&")) {
>>>> String pair[] = param.split("=");
>>>> String key = URLDecoder.decode(pair[0], "UTF-8");
>>>> params.add(key);
>>>> }
>>>> } catch (UnsupportedEncodingException e) {
>>>> e.printStackTrace();
>>>> }
>>>> return params;
>>>> }
>>>> }
>>>>
>>>
>>> I think it is difficult to generalize given that another user may want a
>>> similar support for the selection based on matrix/header/form
>>> parameters, and the other complexity is that we can have repeating
>>> parameters, example, "a=1&a=2" query, etc.
>>>
>>> However it definitely makes sense to update the docs and show what does
>>> it mean to customize the selection algo, so what I will do is I will
>>> update the page and paste the code - lets see may be we can push some of
>>> the code to CXF eventually
>>>
>>> thanks, Sergey
>>>
>>>> Best regards.
>>>> Jan
>>>>
>>>> From: Sergey Beryozkin-5 [via CXF]
>>>> [mailto:[hidden email]</user/SendEmail.jtp?type=node&node=5719454&i=0>]
>>>> Sent: Dienstag, 27. November 2012 11:31
>>>> To: Jan Bernhardt
>>>> Subject: Re: REST Method selection for different QueryParam's
>>>>
>>>> Hi Jan
>>>> On 27/11/12 10:12, janb wrote:
>>>>
>>>>> Hi @all,
>>>>>
>>>>> I'm wondering about the selection strategy in CXF for different Query
>>>>> Parameter. The documentation [1] does not cover this at all.
>>>>>
>>>>> A simple system-test provided the impression to me, that CXF has no
>>>>> valid
>>>>> selection strategy in place for handling different query parameters.
>>>>> Is this
>>>>> assumption correct? Should I create a Jira ticket for a better
>>>>> support?
>>>>>
>>>>> Here is my sample code. No matter which parameter have been provided
>>>>> within
>>>>> my URL, only and always the first method was selected by CXF.
>>>>>
>>>>> @Path("/paramTest")
>>>>> public class MySimpleService {
>>>>>
>>>>> @GET
>>>>> public String getFoo(@QueryParam("foo") String foo){
>>>>> return "foo:" + foo;
>>>>> }
>>>>>
>>>>> @GET
>>>>> public String getFooBar(@QueryParam("foo") String foo,
>>>>> @QueryParam("bar") String bar){
>>>>> return "foo:" + foo + " bar:" + bar;
>>>>> }
>>>>>
>>>>> @GET
>>>>> public String getTest(@QueryParam("test") String test){
>>>>> return "test:" + test;
>>>>> }
>>>>> }
>>>>>
>>>>> [1]
>>>>> http://cxf.apache.org/docs/jax-rs-basics.html#JAX-RSBasics-Overviewoftheselectionalgorithm.
>>>>>
>>>>>
>>>>>
>>>>
>>>> Only URI path segment, HTTP method and in/out media types are taken
>>>> into
>>>> consideration when selecting the candidates. If you prefer to have
>>>> individual methods having the same HTTP Method/Uri Path/Media Types but
>>>> with specific query parameters then the only way to get it managed
>>>> is to
>>>> use a CXF ResourceComparator where, in this case, you can affect the
>>>> ordering of specific resource methods by checking the current
>>>> Message.QUERY_STRING available on the CXF Message
>>>>
>>>> A simpler alternative is to have a single method and work with
>>>> UriInfo, say
>>>>
>>>> @Context
>>>> private UriInfo ui;
>>>>
>>>> @GET
>>>> public String getFooOrBar() {
>>>> MultivaluedMap<String, String> params = ui.getQueryParameters();
>>>> String foo = params.getFirst("foo");
>>>> String bar = params.getFirst("foo");
>>>> // etc
>>>> }
>>>>
>>>> HTH, Sergey
>>>>
>>>>>
>>>>>
>>>>> --
>>>>> View this message in context:
>>>>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187.html
>>>>>
>>>>>
>>>>> Sent from the cxf-user mailing list archive at Nabble.com.
>>>>
>>>>
>>>> --
>>>> Sergey Beryozkin
>>>>
>>>> Talend Community Coders
>>>> http://coders.talend.com/
>>>>
>>>> Blog: http://sberyozkin.blogspot.com
>>>>
>>>> ________________________________
>>>> If you reply to this email, your message will be added to the
>>>> discussion below:
>>>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719190.html
>>>>
>>>>
>>>> To unsubscribe from REST Method selection for different QueryParam's,
>>>> click
>>>> here<
>>>>
>>>> NAML<http://cxf.547215.n5.nabble.com/template/NamlServlet.jtp?macro=macro_viewer&id=instant_html%21nabble%3Aemail.naml&base=nabble.naml.namespaces.BasicNamespace-nabble.view.web.template.NabbleNamespace-nabble.view.web.template.NodeNamespace&breadcrumbs=notify_subscribers%21nabble%3Aemail.naml-instant_emails%21nabble%3Aemail.naml-send_instant_email%21nabble%3Aemail.naml>
>>>>
>>>>
>>>>
>>>>
>>>>
>>>>
>>>> --
>>>> View this message in context:
>>>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719219.html
>>>>
>>>>
>>>> Sent from the cxf-user mailing list archive at Nabble.com.
>>>
>>>
>>
>>
>> --
>> Sergey Beryozkin
>>
>> Talend Community Coders
>> http://coders.talend.com/
>>
>> Blog: http://sberyozkin.blogspot.com
>>
>> ________________________________
>> If you reply to this email, your message will be added to the
>> discussion below:
>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719454.html
>>
>> To unsubscribe from REST Method selection for different QueryParam's,
>> click
>> here<
>>
>> NAML<http://cxf.547215.n5.nabble.com/template/NamlServlet.jtp?macro=macro_viewer&id=instant_html%21nabble%3Aemail.naml&base=nabble.naml.namespaces.BasicNamespace-nabble.view.web.template.NabbleNamespace-nabble.view.web.template.NodeNamespace&breadcrumbs=notify_subscribers%21nabble%3Aemail.naml-instant_emails%21nabble%3Aemail.naml-send_instant_email%21nabble%3Aemail.naml>
>>
>>
>>
>>
>>
>> --
>> View this message in context:
>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719458.html
>>
>> Sent from the cxf-user mailing list archive at Nabble.com.
>
>


--
Sergey Beryozkin

Talend Community Coders
http://coders.talend.com/

Blog: http://sberyozkin.blogspot.com

________________________________
If you reply to this email, your message will be added to the discussion below:
http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719465.html
To unsubscribe from REST Method selection for different QueryParam's, click here<http://cxf.547215.n5.nabble.com/template/NamlServlet.jtp?macro=unsubscribe_by_code&node=5719187&code=amJlcm5oYXJkdEB0YWxlbmQuY29tfDU3MTkxODd8LTEzMDQ4ODk1MjM=>.
NAML<http://cxf.547215.n5.nabble.com/template/NamlServlet.jtp?macro=macro_viewer&id=instant_html%21nabble%3Aemail.naml&base=nabble.naml.namespaces.BasicNamespace-nabble.view.web.template.NabbleNamespace-nabble.view.web.template.NodeNamespace&breadcrumbs=notify_subscribers%21nabble%3Aemail.naml-instant_emails%21nabble%3Aemail.naml-send_instant_email%21nabble%3Aemail.naml>




--
View this message in context: http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719466.html
Sent from the cxf-user mailing list archive at Nabble.com.

Re: REST Method selection for different QueryParam's

Posted by Sergey Beryozkin <sb...@gmail.com>.
On 29/11/12 17:58, Sergey Beryozkin wrote:
> Hi Jan
>
> OK, thanks for all the info, looks promising...
> It just occurred to me, you do not have to check the annotations,
> OperationResourceInfo has it all cached, get the list of parameters, and
> check parameter types (can be query. matrix, etc), this will be much
> faster too, please give it a try...The parameter will have an index into
> an array of the Method parameter array, Method can be found from
> ori.getMethodToInvoke()

Actually, you don't even need a method. Parameter will have name, 
default value, and parameter type

Sergey

>
> Cheers, Sergey
>
>
>
> On 29/11/12 17:23, janb wrote:
>> Hi Sergey,
>>
>> my Comperator is not simply checking the number of parameters, but
>> rather the matching of a query parameter name!
>>
>> Here are a couple an examples related to the sample code on the
>> mentioned wiki page:
>>
>> Query:
>> http://localhost:8080/paramTest?foo=Hello
>>
>> Rating:
>>
>> +2 : getFoo(@QueryParam("foo") String foo)
>>
>> +1 : getFooBar(@QueryParam("foo") String foo, @QueryParam("bar")
>> String bar)
>>
>> // Some addition samples
>>
>> 0 : getFooBar()
>>
>> -1 : getFooBar(@QueryParam("bar") String bar)
>>
>>
>>
>> So the first method would be selected.
>>
>>
>> Query:
>> http://localhost:8080/paramTest?bar=Hello
>>
>> Rating:
>>
>> -1 : getFoo(@QueryParam("foo") String foo)
>>
>> +1 : getFooBar(@QueryParam("foo") String foo, @QueryParam("bar")
>> String bar)
>>
>> // Some addition samples
>>
>> 0 : getFooBar()
>>
>> +2 : getFooBar(@QueryParam("bar") String bar)
>>
>>
>> As you can see form the number, the last one is the perfect match,
>> hence it would be selected. If this method would not exist the second
>> would still be the second best match, and so forth...
>>
>> Query:
>> http://localhost:8080/paramTest?something=Hello
>>
>> Rating:
>>
>> -1 : getFoo(@QueryParam("foo") String foo)
>>
>> -2 : getFooBar(@QueryParam("foo") String foo, @QueryParam("bar")
>> String bar)
>>
>> // Some addition samples
>>
>> 0 : getFooBar()
>>
>> -1 : getFooBar(@QueryParam("bar") String bar)
>>
>>
>> Parameter something is not in any of the methods, therefore
>> getFooBar()would be the best choice.
>>
>> Query:
>> http://localhost:8080/paramTest
>>
>> Rating:
>>
>> -1 : getFoo(@QueryParam("foo") String foo)
>>
>> -2 : getFooBar(@QueryParam("foo") String foo, @QueryParam("bar")
>> String bar)
>>
>> // Some addition samples
>>
>> 0 : getFooBar()
>>
>> -1 : getFooBar(@QueryParam("bar") String bar)
>>
>>
>> If no parameters are provided, all method expecting parameters will
>> get a negative rating, hence getFooBar() will be selected, which would
>> be the perfect match.
>>
>> So from my understanding, this selection algorithm always provides the
>> best choice, in cases where several methods match the same path and
>> type. And I think headers could also be handled in the same way. If a
>> header is present in a method signature, but this header is not
>> provided within the request, this method will get -1 in the rating,
>> otherwise if a header is requested and also provided in a request the
>> rating will get +2. At the end the method with the highest score will
>> be selected, since it has most matches and fewest mismatches compared
>> to all other available methods.
>>
>> WDYT?
>>
>> By the way, I updated some parts of the code, since they did not
>> handle Annotations within an Interface. Here is the refactored code to
>> update the wiki page:
>>
>> package org.apache.syncope.core.rest;
>>
>> import java.io.UnsupportedEncodingException;
>> import java.lang.annotation.Annotation;
>> import java.net.URLDecoder;
>> import java.util.HashMap;
>> import java.util.HashSet;
>> import java.util.Map;
>> import java.util.Set;
>>
>> import javax.ws.rs.DefaultValue;
>> import javax.ws.rs.QueryParam;
>>
>> import org.apache.cxf.jaxrs.ext.ResourceComparator;
>> import org.apache.cxf.jaxrs.model.ClassResourceInfo;
>> import org.apache.cxf.jaxrs.model.OperationResourceInfo;
>> import org.apache.cxf.jaxrs.model.OperationResourceInfoComparator;
>> import org.apache.cxf.message.Message;
>>
>> public class QueryResourceInfoComperator extends
>> OperationResourceInfoComparator implements
>> ResourceComparator {
>>
>> public QueryResourceInfoComperator() {
>> super(null, null);
>> }
>>
>> @Override
>> public int compare(ClassResourceInfo cri1, ClassResourceInfo cri2,
>> Message message) {
>> // Leave Class selection to CXF
>> return 0;
>> }
>>
>> @Override
>> public int compare(OperationResourceInfo oper1, OperationResourceInfo
>> oper2, Message message) {
>>
>> // Check if CXF can make a decision
>> int cxfResult = super.compare(oper1, oper2);
>> if (cxfResult != 0)
>> return cxfResult;
>>
>> // Compare QueryParam annotations
>> Set<String> qParams = getParams((String)
>> message.get(Message.QUERY_STRING));
>> Map<String, Boolean> op1Annos = getAnnotations(oper1);
>> Map<String, Boolean> op2Annos = getAnnotations(oper2);
>>
>> int op1Counter = getMatchingRate(op1Annos, qParams);
>> int op2Counter = getMatchingRate(op2Annos, qParams);
>>
>> return op1Counter == op2Counter
>> ? 0
>> : op1Counter< op2Counter
>> ? 1
>> : -1;
>> }
>>
>> /**
>> * This method calculates a number indicating a good or bad match between
>> * queryParams from request and annotated method parameters. A higher
>> number
>> * means a better match.
>> *
>> * @param annotations
>> * Map contains name of QueryParam method parameters as a key and
>> * a Boolean value indicating an existing default value for this
>> * parameter.
>> * @param queryParams
>> * A Set of query parameters provided within the request
>> * @return A positive or negative number, indicating a good match between
>> * query and method
>> */
>> protected int getMatchingRate(Map<String, Boolean> annotations,
>> Set<String> queryParams) {
>> int rate = 0;
>> for (String anno : annotations.keySet()) {
>> if (queryParams.contains(anno)) {
>> // URL query matches one method parameter
>> rate += 2;
>> } else if (!annotations.get(anno).booleanValue()) {
>> // No default value exists for method parameter
>> rate -= 1;
>> }
>> }
>> return rate;
>> }
>>
>> /**
>> * @param opInfo
>> * OperationInfo to check for parameter annotations
>> * @return Key in Map is QueryParam name, and Value indicates if a default
>> * value is present.
>> */
>> protected Map<String, Boolean> getAnnotations(OperationResourceInfo
>> opInfo) {
>> Map<String, Boolean> opAnnos = new HashMap<String, Boolean>();
>> opAnnos.putAll(getAnnotations(opInfo.getAnnotatedMethod().getParameterAnnotations()));
>>
>> opAnnos.putAll(getAnnotations(opInfo.getMethodToInvoke().getParameterAnnotations()));
>>
>> return opAnnos;
>> }
>>
>> /**
>> * @param opParamAnnos
>> * Array containing all annotations for all method parameters
>> * @return Key in Map is QueryParam name, and Value indicates if a default
>> * value is present.
>> */
>> protected Map<String, Boolean> getAnnotations(Annotation[][]
>> opParamAnnos) {
>> Map<String, Boolean> parameterAnnos = new HashMap<String, Boolean>();
>>
>> if (opParamAnnos.length == 0)
>> return parameterAnnos;
>>
>> for (Annotation[] pAnnos : opParamAnnos) {
>> if (pAnnos.length> 0) {
>> QueryParam qParam = null;
>> DefaultValue dValue = null;
>> for (Annotation anno : pAnnos) {
>> if (anno instanceof QueryParam)
>> qParam = (QueryParam) anno;
>> if (anno instanceof DefaultValue)
>> dValue = (DefaultValue) anno;
>> }
>> parameterAnnos.put(qParam.value(), Boolean.valueOf((dValue != null)));
>> }
>> }
>> return parameterAnnos;
>> }
>>
>> /**
>> * @param query
>> * URL Query
>> * @return A Set of all keys, contained within query.
>> */
>> protected Set<String> getParams(String query) {
>> Set<String> params = new HashSet<String>();
>> if (query == null || query.length() == 0)
>> return params;
>>
>> try {
>> for (String param : query.split("&")) {
>> String pair[] = param.split("=");
>> String key = URLDecoder.decode(pair[0], "UTF-8");
>> params.add(key);
>> }
>> } catch (UnsupportedEncodingException e) {
>> e.printStackTrace();
>> }
>> return params;
>> }
>> }
>>
>>
>> Best regards
>> Jan
>>
>> From: Sergey Beryozkin-5 [via CXF]
>> [mailto:ml-node+s547215n5719454h25@n5.nabble.com]
>> Sent: Donnerstag, 29. November 2012 17:51
>> To: Jan Bernhardt
>> Subject: Re: REST Method selection for different QueryParam's
>>
>> Hi Jan
>>
>> I've posted it to the
>> wiki:https://cwiki.apache.org/confluence/display/CXF20DOC/JAX-RS+Basics#JAX-RSBasics-Customselectionbetweenmultipleresources
>>
>>
>> I guess we might indeed can add a more general comparator which would
>> select the methods based on the number of optional parameters (of any
>> type such as query, header, and the combination, etc), be they single or
>> repetitive.
>>
>> I'm just yet sure if that will work well or not, example,
>> what if we have a single query parameter but it has the name which is
>> not expected by a method expecting a single parameter, etc... May be we
>> will tune it in time :-)
>>
>> Thanks, Sergey
>>
>>
>>
>> On 27/11/12 16:40, Sergey Beryozkin wrote:
>>
>>> Hi Jan
>>>
>>> This looks very neat, more comments below
>>> On 27/11/12 15:25, janb wrote:
>>>> Hi Sergey,
>>>>
>>>> Thank you for your reply. If just developed a generic sample that uses
>>>> CXF standard class and operation selection algorithms, but extends it
>>>> with a QueryParam selection algorithm. If all other conditions in CXF
>>>> match and hence CXF cannot decide which method to select best, my
>>>> QueryParam selection algorithm comes into place. This algorithm
>>>> chooses the method with most matches between provided parameters in
>>>> query and matching QueryParam annotations within the method.
>>>> Additional (not matching) QueryParam annotations will decrease the
>>>> matching rate, while method parameters with a default value will be
>>>> ignored (in the rating).
>>>>
>>>> If you think this code is of any value for CXF, please feel free to
>>>> extend the current selection strategy with my QueryParam selection
>>>> algorithm. I guess the best place for my algorithm would be another
>>>> JAXRSUtils method, like
>>>> JAXRSUtils.compareQueryParams(...) to be called at the end of
>>>> OperationResourceInfoComparator.compare(...).
>>>>
>>>>
>>>> public class QueryResourceInfoComperator extends
>>>> OperationResourceInfoComparator implements
>>>> ResourceComparator {
>>>>
>>>> public QueryResourceInfoComperator() {
>>>> super(null, null);
>>>> }
>>>>
>>>> @Override
>>>> public int compare(ClassResourceInfo cri1, ClassResourceInfo cri2,
>>>> Message message) {
>>>> // Leave Class selection to CXF
>>>> return 0;
>>>> }
>>>>
>>>> @Override
>>>> public int compare(OperationResourceInfo oper1, OperationResourceInfo
>>>> oper2, Message message) {
>>>>
>>>> // Check if CXF can make a decision
>>>> int cxfResult = super.compare(oper1, oper2);
>>>> if (cxfResult != 0)
>>>> return cxfResult;
>>>>
>>>> // Compare QueryParam annotations
>>>> Set<String> qParams = getParams((String)
>>>> message.get(Message.QUERY_STRING));
>>>> int op1Counter =
>>>> getMatchingRate(getAnnotations(oper1.getMethodToInvoke().getParameterAnnotations()),
>>>>
>>>>
>>>> qParams);
>>>> int op2Counter =
>>>> getMatchingRate(getAnnotations(oper2.getMethodToInvoke().getParameterAnnotations()),
>>>>
>>>>
>>>> qParams);
>>>>
>>>> return op1Counter == op2Counter
>>>> ? 0
>>>> : op1Counter< op2Counter
>>>> ? 1
>>>> : -1;
>>>> }
>>>>
>>>> /**
>>>> * This method calculates a number indicating a good or bad match
>>>> between
>>>> * queryParams from request and annotated method parameters. A higher
>>>> number
>>>> * means a better match.
>>>> *
>>>> * @param annotations
>>>> * Map contains name of QueryParam method parameters as a key and
>>>> * a Boolean value indicating an existing default value for this
>>>> * parameter.
>>>> * @param queryParams
>>>> * A Set of query parameters provided within the request
>>>> * @return A positive or negative number, indicating a good match
>>>> between
>>>> * query and method
>>>> */
>>>> protected int getMatchingRate(Map<String, Boolean> annotations,
>>>> Set<String> queryParams) {
>>>> int rate = 0;
>>>> for (String anno : annotations.keySet()) {
>>>> if (queryParams.contains(anno)) {
>>>> // URL query matches one method parameter
>>>> rate += 2;
>>>> } else if (!annotations.get(anno).booleanValue()) {
>>>> // No default value exists for method parameter
>>>> rate -= 1;
>>>> }
>>>> }
>>>> return rate;
>>>> }
>>>>
>>>> /**
>>>> * @param opParamAnnos
>>>> * Array containing all annotations for all method parameters
>>>> * @return Key in Map is QueryParam name, and Value indicates if a
>>>> default
>>>> * value is present.
>>>> */
>>>> protected Map<String, Boolean> getAnnotations(Annotation[][]
>>>> opParamAnnos) {
>>>> Map<String, Boolean> parameterAnnos = new HashMap<String, Boolean>();
>>>>
>>>> if (opParamAnnos.length == 0)
>>>> return parameterAnnos;
>>>>
>>>> for (Annotation[] pAnnos : opParamAnnos) {
>>>> QueryParam qParam = null;
>>>> DefaultValue dValue = null;
>>>> for (Annotation anno : pAnnos) {
>>>> if (anno instanceof QueryParam)
>>>> qParam = (QueryParam) anno;
>>>> if (anno instanceof DefaultValue)
>>>> dValue = (DefaultValue) anno;
>>>> }
>>>> parameterAnnos.put(qParam.value(), Boolean.valueOf((dValue != null)));
>>>> }
>>>>
>>>> return parameterAnnos;
>>>> }
>>>>
>>>> /**
>>>> * @param query
>>>> * URL Query
>>>> * @return A Set of all keys, contained within query.
>>>> */
>>>> protected Set<String> getParams(String query) {
>>>> Set<String> params = new HashSet<String>();
>>>> if (query == null || query.length() == 0)
>>>> return params;
>>>>
>>>> try {
>>>> for (String param : query.split("&")) {
>>>> String pair[] = param.split("=");
>>>> String key = URLDecoder.decode(pair[0], "UTF-8");
>>>> params.add(key);
>>>> }
>>>> } catch (UnsupportedEncodingException e) {
>>>> e.printStackTrace();
>>>> }
>>>> return params;
>>>> }
>>>> }
>>>>
>>>
>>> I think it is difficult to generalize given that another user may want a
>>> similar support for the selection based on matrix/header/form
>>> parameters, and the other complexity is that we can have repeating
>>> parameters, example, "a=1&a=2" query, etc.
>>>
>>> However it definitely makes sense to update the docs and show what does
>>> it mean to customize the selection algo, so what I will do is I will
>>> update the page and paste the code - lets see may be we can push some of
>>> the code to CXF eventually
>>>
>>> thanks, Sergey
>>>
>>>> Best regards.
>>>> Jan
>>>>
>>>> From: Sergey Beryozkin-5 [via CXF]
>>>> [mailto:[hidden email]</user/SendEmail.jtp?type=node&node=5719454&i=0>]
>>>> Sent: Dienstag, 27. November 2012 11:31
>>>> To: Jan Bernhardt
>>>> Subject: Re: REST Method selection for different QueryParam's
>>>>
>>>> Hi Jan
>>>> On 27/11/12 10:12, janb wrote:
>>>>
>>>>> Hi @all,
>>>>>
>>>>> I'm wondering about the selection strategy in CXF for different Query
>>>>> Parameter. The documentation [1] does not cover this at all.
>>>>>
>>>>> A simple system-test provided the impression to me, that CXF has no
>>>>> valid
>>>>> selection strategy in place for handling different query parameters.
>>>>> Is this
>>>>> assumption correct? Should I create a Jira ticket for a better
>>>>> support?
>>>>>
>>>>> Here is my sample code. No matter which parameter have been provided
>>>>> within
>>>>> my URL, only and always the first method was selected by CXF.
>>>>>
>>>>> @Path("/paramTest")
>>>>> public class MySimpleService {
>>>>>
>>>>> @GET
>>>>> public String getFoo(@QueryParam("foo") String foo){
>>>>> return "foo:" + foo;
>>>>> }
>>>>>
>>>>> @GET
>>>>> public String getFooBar(@QueryParam("foo") String foo,
>>>>> @QueryParam("bar") String bar){
>>>>> return "foo:" + foo + " bar:" + bar;
>>>>> }
>>>>>
>>>>> @GET
>>>>> public String getTest(@QueryParam("test") String test){
>>>>> return "test:" + test;
>>>>> }
>>>>> }
>>>>>
>>>>> [1]
>>>>> http://cxf.apache.org/docs/jax-rs-basics.html#JAX-RSBasics-Overviewoftheselectionalgorithm.
>>>>>
>>>>>
>>>>>
>>>>
>>>> Only URI path segment, HTTP method and in/out media types are taken
>>>> into
>>>> consideration when selecting the candidates. If you prefer to have
>>>> individual methods having the same HTTP Method/Uri Path/Media Types but
>>>> with specific query parameters then the only way to get it managed
>>>> is to
>>>> use a CXF ResourceComparator where, in this case, you can affect the
>>>> ordering of specific resource methods by checking the current
>>>> Message.QUERY_STRING available on the CXF Message
>>>>
>>>> A simpler alternative is to have a single method and work with
>>>> UriInfo, say
>>>>
>>>> @Context
>>>> private UriInfo ui;
>>>>
>>>> @GET
>>>> public String getFooOrBar() {
>>>> MultivaluedMap<String, String> params = ui.getQueryParameters();
>>>> String foo = params.getFirst("foo");
>>>> String bar = params.getFirst("foo");
>>>> // etc
>>>> }
>>>>
>>>> HTH, Sergey
>>>>
>>>>>
>>>>>
>>>>> --
>>>>> View this message in context:
>>>>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187.html
>>>>>
>>>>>
>>>>> Sent from the cxf-user mailing list archive at Nabble.com.
>>>>
>>>>
>>>> --
>>>> Sergey Beryozkin
>>>>
>>>> Talend Community Coders
>>>> http://coders.talend.com/
>>>>
>>>> Blog: http://sberyozkin.blogspot.com
>>>>
>>>> ________________________________
>>>> If you reply to this email, your message will be added to the
>>>> discussion below:
>>>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719190.html
>>>>
>>>>
>>>> To unsubscribe from REST Method selection for different QueryParam's,
>>>> click
>>>> here<
>>>>
>>>> NAML<http://cxf.547215.n5.nabble.com/template/NamlServlet.jtp?macro=macro_viewer&id=instant_html%21nabble%3Aemail.naml&base=nabble.naml.namespaces.BasicNamespace-nabble.view.web.template.NabbleNamespace-nabble.view.web.template.NodeNamespace&breadcrumbs=notify_subscribers%21nabble%3Aemail.naml-instant_emails%21nabble%3Aemail.naml-send_instant_email%21nabble%3Aemail.naml>
>>>>
>>>>
>>>>
>>>>
>>>>
>>>>
>>>> --
>>>> View this message in context:
>>>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719219.html
>>>>
>>>>
>>>> Sent from the cxf-user mailing list archive at Nabble.com.
>>>
>>>
>>
>>
>> --
>> Sergey Beryozkin
>>
>> Talend Community Coders
>> http://coders.talend.com/
>>
>> Blog: http://sberyozkin.blogspot.com
>>
>> ________________________________
>> If you reply to this email, your message will be added to the
>> discussion below:
>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719454.html
>>
>> To unsubscribe from REST Method selection for different QueryParam's,
>> click
>> here<http://cxf.547215.n5.nabble.com/template/NamlServlet.jtp?macro=unsubscribe_by_code&node=5719187&code=amJlcm5oYXJkdEB0YWxlbmQuY29tfDU3MTkxODd8LTEzMDQ4ODk1MjM=>.
>>
>> NAML<http://cxf.547215.n5.nabble.com/template/NamlServlet.jtp?macro=macro_viewer&id=instant_html%21nabble%3Aemail.naml&base=nabble.naml.namespaces.BasicNamespace-nabble.view.web.template.NabbleNamespace-nabble.view.web.template.NodeNamespace&breadcrumbs=notify_subscribers%21nabble%3Aemail.naml-instant_emails%21nabble%3Aemail.naml-send_instant_email%21nabble%3Aemail.naml>
>>
>>
>>
>>
>>
>> --
>> View this message in context:
>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719458.html
>>
>> Sent from the cxf-user mailing list archive at Nabble.com.
>
>


-- 
Sergey Beryozkin

Talend Community Coders
http://coders.talend.com/

Blog: http://sberyozkin.blogspot.com

Re: REST Method selection for different QueryParam's

Posted by Sergey Beryozkin <sb...@gmail.com>.
Hi Jan

OK, thanks for all the info, looks promising...
It just occurred to me, you do not have to check the annotations, 
OperationResourceInfo has it all cached, get the list of parameters, and 
check parameter types (can be query. matrix, etc), this will be much 
faster too, please give it a try...The parameter will have an index into 
an array of the Method parameter array, Method can be found from 
ori.getMethodToInvoke()

Cheers, Sergey



On 29/11/12 17:23, janb wrote:
> Hi Sergey,
>
> my Comperator is not simply checking the number of parameters, but rather the matching of a query parameter name!
>
> Here are a couple an examples related to the sample code on the mentioned wiki page:
>
> Query:
> http://localhost:8080/paramTest?foo=Hello
>
> Rating:
>
> +2 : getFoo(@QueryParam("foo") String foo)
>
> +1 : getFooBar(@QueryParam("foo") String foo, @QueryParam("bar") String bar)
>
> // Some addition samples
>
>   0 : getFooBar()
>
> -1 : getFooBar(@QueryParam("bar") String bar)
>
>
>
> So the first method would be selected.
>
>
> Query:
> http://localhost:8080/paramTest?bar=Hello
>
> Rating:
>
> -1 : getFoo(@QueryParam("foo") String foo)
>
> +1 : getFooBar(@QueryParam("foo") String foo, @QueryParam("bar") String bar)
>
> // Some addition samples
>
>   0 : getFooBar()
>
> +2 : getFooBar(@QueryParam("bar") String bar)
>
>
> As you can see form the number, the last one is the perfect match, hence it would be selected. If this method would not exist the second would still be the second best match, and so forth...
>
> Query:
> http://localhost:8080/paramTest?something=Hello
>
> Rating:
>
> -1 : getFoo(@QueryParam("foo") String foo)
>
> -2 : getFooBar(@QueryParam("foo") String foo, @QueryParam("bar") String bar)
>
> // Some addition samples
>
>   0 : getFooBar()
>
> -1 : getFooBar(@QueryParam("bar") String bar)
>
>
> Parameter something is not in any of the methods, therefore getFooBar()would be the best choice.
>
> Query:
> http://localhost:8080/paramTest
>
> Rating:
>
> -1 : getFoo(@QueryParam("foo") String foo)
>
> -2 : getFooBar(@QueryParam("foo") String foo, @QueryParam("bar") String bar)
>
> // Some addition samples
>
>   0 : getFooBar()
>
> -1 : getFooBar(@QueryParam("bar") String bar)
>
>
> If no parameters are provided, all method expecting parameters will get a negative rating, hence  getFooBar() will be selected, which would be the perfect match.
>
> So from my understanding, this selection algorithm always provides the best choice, in cases where several methods match the same path and type. And I think headers could also be handled in the same way. If a header is present in a method signature, but this header is not provided within the request, this method will get -1 in the rating, otherwise if a header is requested and also provided in a request the rating will get +2. At the end the method with the highest score will be selected, since it has most matches and fewest mismatches compared to all other available methods.
>
> WDYT?
>
> By the way, I updated some parts of the code, since they did not handle Annotations within an Interface. Here is the refactored code to update the wiki page:
>
> package org.apache.syncope.core.rest;
>
> import java.io.UnsupportedEncodingException;
> import java.lang.annotation.Annotation;
> import java.net.URLDecoder;
> import java.util.HashMap;
> import java.util.HashSet;
> import java.util.Map;
> import java.util.Set;
>
> import javax.ws.rs.DefaultValue;
> import javax.ws.rs.QueryParam;
>
> import org.apache.cxf.jaxrs.ext.ResourceComparator;
> import org.apache.cxf.jaxrs.model.ClassResourceInfo;
> import org.apache.cxf.jaxrs.model.OperationResourceInfo;
> import org.apache.cxf.jaxrs.model.OperationResourceInfoComparator;
> import org.apache.cxf.message.Message;
>
> public class QueryResourceInfoComperator extends OperationResourceInfoComparator implements
>          ResourceComparator {
>
>      public QueryResourceInfoComperator() {
>          super(null, null);
>      }
>
>      @Override
>      public int compare(ClassResourceInfo cri1, ClassResourceInfo cri2, Message message) {
>          // Leave Class selection to CXF
>          return 0;
>      }
>
>      @Override
>      public int compare(OperationResourceInfo oper1, OperationResourceInfo oper2, Message message) {
>
>          // Check if CXF can make a decision
>          int cxfResult = super.compare(oper1, oper2);
>          if (cxfResult != 0)
>              return cxfResult;
>
>          // Compare QueryParam annotations
>          Set<String>  qParams = getParams((String) message.get(Message.QUERY_STRING));
>          Map<String, Boolean>  op1Annos = getAnnotations(oper1);
>          Map<String, Boolean>  op2Annos = getAnnotations(oper2);
>
>          int op1Counter = getMatchingRate(op1Annos, qParams);
>          int op2Counter = getMatchingRate(op2Annos, qParams);
>
>          return op1Counter == op2Counter
>                  ? 0
>                  : op1Counter<  op2Counter
>                          ? 1
>                          : -1;
>      }
>
>      /**
>       * This method calculates a number indicating a good or bad match between
>       * queryParams from request and annotated method parameters. A higher number
>       * means a better match.
>       *
>       * @param annotations
>       *            Map contains name of QueryParam method parameters as a key and
>       *            a Boolean value indicating an existing default value for this
>       *            parameter.
>       * @param queryParams
>       *            A Set of query parameters provided within the request
>       * @return A positive or negative number, indicating a good match between
>       *         query and method
>       */
>      protected int getMatchingRate(Map<String, Boolean>  annotations, Set<String>  queryParams) {
>          int rate = 0;
>          for (String anno : annotations.keySet()) {
>              if (queryParams.contains(anno)) {
>                  // URL query matches one method parameter
>                  rate += 2;
>              } else if (!annotations.get(anno).booleanValue()) {
>                  // No default value exists for method parameter
>                  rate -= 1;
>              }
>          }
>          return rate;
>      }
>
>      /**
>       * @param opInfo
>       *            OperationInfo to check for parameter annotations
>       * @return Key in Map is QueryParam name, and Value indicates if a default
>       *         value is present.
>       */
>      protected Map<String, Boolean>  getAnnotations(OperationResourceInfo opInfo) {
>          Map<String, Boolean>  opAnnos = new HashMap<String, Boolean>();
>          opAnnos.putAll(getAnnotations(opInfo.getAnnotatedMethod().getParameterAnnotations()));
>          opAnnos.putAll(getAnnotations(opInfo.getMethodToInvoke().getParameterAnnotations()));
>          return opAnnos;
>      }
>
>      /**
>       * @param opParamAnnos
>       *            Array containing all annotations for all method parameters
>       * @return Key in Map is QueryParam name, and Value indicates if a default
>       *         value is present.
>       */
>      protected Map<String, Boolean>  getAnnotations(Annotation[][] opParamAnnos) {
>          Map<String, Boolean>  parameterAnnos = new HashMap<String, Boolean>();
>
>          if (opParamAnnos.length == 0)
>              return parameterAnnos;
>
>          for (Annotation[] pAnnos : opParamAnnos) {
>              if (pAnnos.length>  0) {
>                  QueryParam qParam = null;
>                  DefaultValue dValue = null;
>                  for (Annotation anno : pAnnos) {
>                      if (anno instanceof QueryParam)
>                          qParam = (QueryParam) anno;
>                      if (anno instanceof DefaultValue)
>                          dValue = (DefaultValue) anno;
>                  }
>                  parameterAnnos.put(qParam.value(), Boolean.valueOf((dValue != null)));
>              }
>          }
>          return parameterAnnos;
>      }
>
>      /**
>       * @param query
>       *            URL Query
>       * @return A Set of all keys, contained within query.
>       */
>      protected Set<String>  getParams(String query) {
>          Set<String>  params = new HashSet<String>();
>          if (query == null || query.length() == 0)
>              return params;
>
>          try {
>              for (String param : query.split("&")) {
>                  String pair[] = param.split("=");
>                  String key = URLDecoder.decode(pair[0], "UTF-8");
>                  params.add(key);
>              }
>          } catch (UnsupportedEncodingException e) {
>              e.printStackTrace();
>          }
>          return params;
>      }
> }
>
>
> Best regards
> Jan
>
> From: Sergey Beryozkin-5 [via CXF] [mailto:ml-node+s547215n5719454h25@n5.nabble.com]
> Sent: Donnerstag, 29. November 2012 17:51
> To: Jan Bernhardt
> Subject: Re: REST Method selection for different QueryParam's
>
> Hi Jan
>
> I've posted it to the
> wiki:https://cwiki.apache.org/confluence/display/CXF20DOC/JAX-RS+Basics#JAX-RSBasics-Customselectionbetweenmultipleresources
>
> I guess we might indeed can add a more general comparator which would
> select the methods based on the number of optional parameters (of any
> type such as query, header, and the combination, etc), be they single or
> repetitive.
>
> I'm just yet sure if that will work well or not, example,
> what if we have a single query parameter but it has the name which is
> not expected by a method expecting a single parameter, etc... May be we
> will tune it in time :-)
>
> Thanks, Sergey
>
>
>
> On 27/11/12 16:40, Sergey Beryozkin wrote:
>
>> Hi Jan
>>
>> This looks very neat, more comments below
>> On 27/11/12 15:25, janb wrote:
>>> Hi Sergey,
>>>
>>> Thank you for your reply. If just developed a generic sample that uses
>>> CXF standard class and operation selection algorithms, but extends it
>>> with a QueryParam selection algorithm. If all other conditions in CXF
>>> match and hence CXF cannot decide which method to select best, my
>>> QueryParam selection algorithm comes into place. This algorithm
>>> chooses the method with most matches between provided parameters in
>>> query and matching QueryParam annotations within the method.
>>> Additional (not matching) QueryParam annotations will decrease the
>>> matching rate, while method parameters with a default value will be
>>> ignored (in the rating).
>>>
>>> If you think this code is of any value for CXF, please feel free to
>>> extend the current selection strategy with my QueryParam selection
>>> algorithm. I guess the best place for my algorithm would be another
>>> JAXRSUtils method, like
>>> JAXRSUtils.compareQueryParams(...) to be called at the end of
>>> OperationResourceInfoComparator.compare(...).
>>>
>>>
>>> public class QueryResourceInfoComperator extends
>>> OperationResourceInfoComparator implements
>>> ResourceComparator {
>>>
>>> public QueryResourceInfoComperator() {
>>> super(null, null);
>>> }
>>>
>>> @Override
>>> public int compare(ClassResourceInfo cri1, ClassResourceInfo cri2,
>>> Message message) {
>>> // Leave Class selection to CXF
>>> return 0;
>>> }
>>>
>>> @Override
>>> public int compare(OperationResourceInfo oper1, OperationResourceInfo
>>> oper2, Message message) {
>>>
>>> // Check if CXF can make a decision
>>> int cxfResult = super.compare(oper1, oper2);
>>> if (cxfResult != 0)
>>> return cxfResult;
>>>
>>> // Compare QueryParam annotations
>>> Set<String>  qParams = getParams((String)
>>> message.get(Message.QUERY_STRING));
>>> int op1Counter =
>>> getMatchingRate(getAnnotations(oper1.getMethodToInvoke().getParameterAnnotations()),
>>>
>>> qParams);
>>> int op2Counter =
>>> getMatchingRate(getAnnotations(oper2.getMethodToInvoke().getParameterAnnotations()),
>>>
>>> qParams);
>>>
>>> return op1Counter == op2Counter
>>> ? 0
>>> : op1Counter<  op2Counter
>>> ? 1
>>> : -1;
>>> }
>>>
>>> /**
>>> * This method calculates a number indicating a good or bad match between
>>> * queryParams from request and annotated method parameters. A higher
>>> number
>>> * means a better match.
>>> *
>>> * @param annotations
>>> * Map contains name of QueryParam method parameters as a key and
>>> * a Boolean value indicating an existing default value for this
>>> * parameter.
>>> * @param queryParams
>>> * A Set of query parameters provided within the request
>>> * @return A positive or negative number, indicating a good match between
>>> * query and method
>>> */
>>> protected int getMatchingRate(Map<String, Boolean>  annotations,
>>> Set<String>  queryParams) {
>>> int rate = 0;
>>> for (String anno : annotations.keySet()) {
>>> if (queryParams.contains(anno)) {
>>> // URL query matches one method parameter
>>> rate += 2;
>>> } else if (!annotations.get(anno).booleanValue()) {
>>> // No default value exists for method parameter
>>> rate -= 1;
>>> }
>>> }
>>> return rate;
>>> }
>>>
>>> /**
>>> * @param opParamAnnos
>>> * Array containing all annotations for all method parameters
>>> * @return Key in Map is QueryParam name, and Value indicates if a default
>>> * value is present.
>>> */
>>> protected Map<String, Boolean>  getAnnotations(Annotation[][]
>>> opParamAnnos) {
>>> Map<String, Boolean>  parameterAnnos = new HashMap<String, Boolean>();
>>>
>>> if (opParamAnnos.length == 0)
>>> return parameterAnnos;
>>>
>>> for (Annotation[] pAnnos : opParamAnnos) {
>>> QueryParam qParam = null;
>>> DefaultValue dValue = null;
>>> for (Annotation anno : pAnnos) {
>>> if (anno instanceof QueryParam)
>>> qParam = (QueryParam) anno;
>>> if (anno instanceof DefaultValue)
>>> dValue = (DefaultValue) anno;
>>> }
>>> parameterAnnos.put(qParam.value(), Boolean.valueOf((dValue != null)));
>>> }
>>>
>>> return parameterAnnos;
>>> }
>>>
>>> /**
>>> * @param query
>>> * URL Query
>>> * @return A Set of all keys, contained within query.
>>> */
>>> protected Set<String>  getParams(String query) {
>>> Set<String>  params = new HashSet<String>();
>>> if (query == null || query.length() == 0)
>>> return params;
>>>
>>> try {
>>> for (String param : query.split("&")) {
>>> String pair[] = param.split("=");
>>> String key = URLDecoder.decode(pair[0], "UTF-8");
>>> params.add(key);
>>> }
>>> } catch (UnsupportedEncodingException e) {
>>> e.printStackTrace();
>>> }
>>> return params;
>>> }
>>> }
>>>
>>
>> I think it is difficult to generalize given that another user may want a
>> similar support for the selection based on matrix/header/form
>> parameters, and the other complexity is that we can have repeating
>> parameters, example, "a=1&a=2" query, etc.
>>
>> However it definitely makes sense to update the docs and show what does
>> it mean to customize the selection algo, so what I will do is I will
>> update the page and paste the code - lets see may be we can push some of
>> the code to CXF eventually
>>
>> thanks, Sergey
>>
>>> Best regards.
>>> Jan
>>>
>>> From: Sergey Beryozkin-5 [via CXF]
>>> [mailto:[hidden email]</user/SendEmail.jtp?type=node&node=5719454&i=0>]
>>> Sent: Dienstag, 27. November 2012 11:31
>>> To: Jan Bernhardt
>>> Subject: Re: REST Method selection for different QueryParam's
>>>
>>> Hi Jan
>>> On 27/11/12 10:12, janb wrote:
>>>
>>>> Hi @all,
>>>>
>>>> I'm wondering about the selection strategy in CXF for different Query
>>>> Parameter. The documentation [1] does not cover this at all.
>>>>
>>>> A simple system-test provided the impression to me, that CXF has no
>>>> valid
>>>> selection strategy in place for handling different query parameters.
>>>> Is this
>>>> assumption correct? Should I create a Jira ticket for a better support?
>>>>
>>>> Here is my sample code. No matter which parameter have been provided
>>>> within
>>>> my URL, only and always the first method was selected by CXF.
>>>>
>>>> @Path("/paramTest")
>>>> public class MySimpleService {
>>>>
>>>> @GET
>>>> public String getFoo(@QueryParam("foo") String foo){
>>>> return "foo:" + foo;
>>>> }
>>>>
>>>> @GET
>>>> public String getFooBar(@QueryParam("foo") String foo,
>>>> @QueryParam("bar") String bar){
>>>> return "foo:" + foo + " bar:" + bar;
>>>> }
>>>>
>>>> @GET
>>>> public String getTest(@QueryParam("test") String test){
>>>> return "test:" + test;
>>>> }
>>>> }
>>>>
>>>> [1]
>>>> http://cxf.apache.org/docs/jax-rs-basics.html#JAX-RSBasics-Overviewoftheselectionalgorithm.
>>>>
>>>>
>>>
>>> Only URI path segment, HTTP method and in/out media types are taken into
>>> consideration when selecting the candidates. If you prefer to have
>>> individual methods having the same HTTP Method/Uri Path/Media Types but
>>> with specific query parameters then the only way to get it managed is to
>>> use a CXF ResourceComparator where, in this case, you can affect the
>>> ordering of specific resource methods by checking the current
>>> Message.QUERY_STRING available on the CXF Message
>>>
>>> A simpler alternative is to have a single method and work with
>>> UriInfo, say
>>>
>>> @Context
>>> private UriInfo ui;
>>>
>>> @GET
>>> public String getFooOrBar() {
>>> MultivaluedMap<String, String>  params = ui.getQueryParameters();
>>> String foo = params.getFirst("foo");
>>> String bar = params.getFirst("foo");
>>> // etc
>>> }
>>>
>>> HTH, Sergey
>>>
>>>>
>>>>
>>>> --
>>>> View this message in context:
>>>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187.html
>>>>
>>>> Sent from the cxf-user mailing list archive at Nabble.com.
>>>
>>>
>>> --
>>> Sergey Beryozkin
>>>
>>> Talend Community Coders
>>> http://coders.talend.com/
>>>
>>> Blog: http://sberyozkin.blogspot.com
>>>
>>> ________________________________
>>> If you reply to this email, your message will be added to the
>>> discussion below:
>>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719190.html
>>>
>>> To unsubscribe from REST Method selection for different QueryParam's,
>>> click
>>> here<
>>>
>>> NAML<http://cxf.547215.n5.nabble.com/template/NamlServlet.jtp?macro=macro_viewer&id=instant_html%21nabble%3Aemail.naml&base=nabble.naml.namespaces.BasicNamespace-nabble.view.web.template.NabbleNamespace-nabble.view.web.template.NodeNamespace&breadcrumbs=notify_subscribers%21nabble%3Aemail.naml-instant_emails%21nabble%3Aemail.naml-send_instant_email%21nabble%3Aemail.naml>
>>>
>>>
>>>
>>>
>>>
>>> --
>>> View this message in context:
>>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719219.html
>>>
>>> Sent from the cxf-user mailing list archive at Nabble.com.
>>
>>
>
>
> --
> Sergey Beryozkin
>
> Talend Community Coders
> http://coders.talend.com/
>
> Blog: http://sberyozkin.blogspot.com
>
> ________________________________
> If you reply to this email, your message will be added to the discussion below:
> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719454.html
> To unsubscribe from REST Method selection for different QueryParam's, click here<http://cxf.547215.n5.nabble.com/template/NamlServlet.jtp?macro=unsubscribe_by_code&node=5719187&code=amJlcm5oYXJkdEB0YWxlbmQuY29tfDU3MTkxODd8LTEzMDQ4ODk1MjM=>.
> NAML<http://cxf.547215.n5.nabble.com/template/NamlServlet.jtp?macro=macro_viewer&id=instant_html%21nabble%3Aemail.naml&base=nabble.naml.namespaces.BasicNamespace-nabble.view.web.template.NabbleNamespace-nabble.view.web.template.NodeNamespace&breadcrumbs=notify_subscribers%21nabble%3Aemail.naml-instant_emails%21nabble%3Aemail.naml-send_instant_email%21nabble%3Aemail.naml>
>
>
>
>
> --
> View this message in context: http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719458.html
> Sent from the cxf-user mailing list archive at Nabble.com.


-- 
Sergey Beryozkin

Talend Community Coders
http://coders.talend.com/

Blog: http://sberyozkin.blogspot.com

RE: REST Method selection for different QueryParam's

Posted by janb <jb...@talend.com>.
Hi Sergey,

my Comperator is not simply checking the number of parameters, but rather the matching of a query parameter name!

Here are a couple an examples related to the sample code on the mentioned wiki page:

Query:
http://localhost:8080/paramTest?foo=Hello

Rating:

+2 : getFoo(@QueryParam("foo") String foo)

+1 : getFooBar(@QueryParam("foo") String foo, @QueryParam("bar") String bar)

// Some addition samples

 0 : getFooBar()

-1 : getFooBar(@QueryParam("bar") String bar)



So the first method would be selected.


Query:
http://localhost:8080/paramTest?bar=Hello

Rating:

-1 : getFoo(@QueryParam("foo") String foo)

+1 : getFooBar(@QueryParam("foo") String foo, @QueryParam("bar") String bar)

// Some addition samples

 0 : getFooBar()

+2 : getFooBar(@QueryParam("bar") String bar)


As you can see form the number, the last one is the perfect match, hence it would be selected. If this method would not exist the second would still be the second best match, and so forth...

Query:
http://localhost:8080/paramTest?something=Hello

Rating:

-1 : getFoo(@QueryParam("foo") String foo)

-2 : getFooBar(@QueryParam("foo") String foo, @QueryParam("bar") String bar)

// Some addition samples

 0 : getFooBar()

-1 : getFooBar(@QueryParam("bar") String bar)


Parameter something is not in any of the methods, therefore getFooBar()would be the best choice.

Query:
http://localhost:8080/paramTest

Rating:

-1 : getFoo(@QueryParam("foo") String foo)

-2 : getFooBar(@QueryParam("foo") String foo, @QueryParam("bar") String bar)

// Some addition samples

 0 : getFooBar()

-1 : getFooBar(@QueryParam("bar") String bar)


If no parameters are provided, all method expecting parameters will get a negative rating, hence  getFooBar() will be selected, which would be the perfect match.

So from my understanding, this selection algorithm always provides the best choice, in cases where several methods match the same path and type. And I think headers could also be handled in the same way. If a header is present in a method signature, but this header is not provided within the request, this method will get -1 in the rating, otherwise if a header is requested and also provided in a request the rating will get +2. At the end the method with the highest score will be selected, since it has most matches and fewest mismatches compared to all other available methods.

WDYT?

By the way, I updated some parts of the code, since they did not handle Annotations within an Interface. Here is the refactored code to update the wiki page:

package org.apache.syncope.core.rest;

import java.io.UnsupportedEncodingException;
import java.lang.annotation.Annotation;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.ws.rs.DefaultValue;
import javax.ws.rs.QueryParam;

import org.apache.cxf.jaxrs.ext.ResourceComparator;
import org.apache.cxf.jaxrs.model.ClassResourceInfo;
import org.apache.cxf.jaxrs.model.OperationResourceInfo;
import org.apache.cxf.jaxrs.model.OperationResourceInfoComparator;
import org.apache.cxf.message.Message;

public class QueryResourceInfoComperator extends OperationResourceInfoComparator implements
        ResourceComparator {

    public QueryResourceInfoComperator() {
        super(null, null);
    }

    @Override
    public int compare(ClassResourceInfo cri1, ClassResourceInfo cri2, Message message) {
        // Leave Class selection to CXF
        return 0;
    }

    @Override
    public int compare(OperationResourceInfo oper1, OperationResourceInfo oper2, Message message) {

        // Check if CXF can make a decision
        int cxfResult = super.compare(oper1, oper2);
        if (cxfResult != 0)
            return cxfResult;

        // Compare QueryParam annotations
        Set<String> qParams = getParams((String) message.get(Message.QUERY_STRING));
        Map<String, Boolean> op1Annos = getAnnotations(oper1);
        Map<String, Boolean> op2Annos = getAnnotations(oper2);

        int op1Counter = getMatchingRate(op1Annos, qParams);
        int op2Counter = getMatchingRate(op2Annos, qParams);

        return op1Counter == op2Counter
                ? 0
                : op1Counter < op2Counter
                        ? 1
                        : -1;
    }

    /**
     * This method calculates a number indicating a good or bad match between
     * queryParams from request and annotated method parameters. A higher number
     * means a better match.
     *
     * @param annotations
     *            Map contains name of QueryParam method parameters as a key and
     *            a Boolean value indicating an existing default value for this
     *            parameter.
     * @param queryParams
     *            A Set of query parameters provided within the request
     * @return A positive or negative number, indicating a good match between
     *         query and method
     */
    protected int getMatchingRate(Map<String, Boolean> annotations, Set<String> queryParams) {
        int rate = 0;
        for (String anno : annotations.keySet()) {
            if (queryParams.contains(anno)) {
                // URL query matches one method parameter
                rate += 2;
            } else if (!annotations.get(anno).booleanValue()) {
                // No default value exists for method parameter
                rate -= 1;
            }
        }
        return rate;
    }

    /**
     * @param opInfo
     *            OperationInfo to check for parameter annotations
     * @return Key in Map is QueryParam name, and Value indicates if a default
     *         value is present.
     */
    protected Map<String, Boolean> getAnnotations(OperationResourceInfo opInfo) {
        Map<String, Boolean> opAnnos = new HashMap<String, Boolean>();
        opAnnos.putAll(getAnnotations(opInfo.getAnnotatedMethod().getParameterAnnotations()));
        opAnnos.putAll(getAnnotations(opInfo.getMethodToInvoke().getParameterAnnotations()));
        return opAnnos;
    }

    /**
     * @param opParamAnnos
     *            Array containing all annotations for all method parameters
     * @return Key in Map is QueryParam name, and Value indicates if a default
     *         value is present.
     */
    protected Map<String, Boolean> getAnnotations(Annotation[][] opParamAnnos) {
        Map<String, Boolean> parameterAnnos = new HashMap<String, Boolean>();

        if (opParamAnnos.length == 0)
            return parameterAnnos;

        for (Annotation[] pAnnos : opParamAnnos) {
            if (pAnnos.length > 0) {
                QueryParam qParam = null;
                DefaultValue dValue = null;
                for (Annotation anno : pAnnos) {
                    if (anno instanceof QueryParam)
                        qParam = (QueryParam) anno;
                    if (anno instanceof DefaultValue)
                        dValue = (DefaultValue) anno;
                }
                parameterAnnos.put(qParam.value(), Boolean.valueOf((dValue != null)));
            }
        }
        return parameterAnnos;
    }

    /**
     * @param query
     *            URL Query
     * @return A Set of all keys, contained within query.
     */
    protected Set<String> getParams(String query) {
        Set<String> params = new HashSet<String>();
        if (query == null || query.length() == 0)
            return params;

        try {
            for (String param : query.split("&")) {
                String pair[] = param.split("=");
                String key = URLDecoder.decode(pair[0], "UTF-8");
                params.add(key);
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return params;
    }
}


Best regards
Jan

From: Sergey Beryozkin-5 [via CXF] [mailto:ml-node+s547215n5719454h25@n5.nabble.com]
Sent: Donnerstag, 29. November 2012 17:51
To: Jan Bernhardt
Subject: Re: REST Method selection for different QueryParam's

Hi Jan

I've posted it to the
wiki:https://cwiki.apache.org/confluence/display/CXF20DOC/JAX-RS+Basics#JAX-RSBasics-Customselectionbetweenmultipleresources

I guess we might indeed can add a more general comparator which would
select the methods based on the number of optional parameters (of any
type such as query, header, and the combination, etc), be they single or
repetitive.

I'm just yet sure if that will work well or not, example,
what if we have a single query parameter but it has the name which is
not expected by a method expecting a single parameter, etc... May be we
will tune it in time :-)

Thanks, Sergey



On 27/11/12 16:40, Sergey Beryozkin wrote:

> Hi Jan
>
> This looks very neat, more comments below
> On 27/11/12 15:25, janb wrote:
>> Hi Sergey,
>>
>> Thank you for your reply. If just developed a generic sample that uses
>> CXF standard class and operation selection algorithms, but extends it
>> with a QueryParam selection algorithm. If all other conditions in CXF
>> match and hence CXF cannot decide which method to select best, my
>> QueryParam selection algorithm comes into place. This algorithm
>> chooses the method with most matches between provided parameters in
>> query and matching QueryParam annotations within the method.
>> Additional (not matching) QueryParam annotations will decrease the
>> matching rate, while method parameters with a default value will be
>> ignored (in the rating).
>>
>> If you think this code is of any value for CXF, please feel free to
>> extend the current selection strategy with my QueryParam selection
>> algorithm. I guess the best place for my algorithm would be another
>> JAXRSUtils method, like
>> JAXRSUtils.compareQueryParams(...) to be called at the end of
>> OperationResourceInfoComparator.compare(...).
>>
>>
>> public class QueryResourceInfoComperator extends
>> OperationResourceInfoComparator implements
>> ResourceComparator {
>>
>> public QueryResourceInfoComperator() {
>> super(null, null);
>> }
>>
>> @Override
>> public int compare(ClassResourceInfo cri1, ClassResourceInfo cri2,
>> Message message) {
>> // Leave Class selection to CXF
>> return 0;
>> }
>>
>> @Override
>> public int compare(OperationResourceInfo oper1, OperationResourceInfo
>> oper2, Message message) {
>>
>> // Check if CXF can make a decision
>> int cxfResult = super.compare(oper1, oper2);
>> if (cxfResult != 0)
>> return cxfResult;
>>
>> // Compare QueryParam annotations
>> Set<String> qParams = getParams((String)
>> message.get(Message.QUERY_STRING));
>> int op1Counter =
>> getMatchingRate(getAnnotations(oper1.getMethodToInvoke().getParameterAnnotations()),
>>
>> qParams);
>> int op2Counter =
>> getMatchingRate(getAnnotations(oper2.getMethodToInvoke().getParameterAnnotations()),
>>
>> qParams);
>>
>> return op1Counter == op2Counter
>> ? 0
>> : op1Counter< op2Counter
>> ? 1
>> : -1;
>> }
>>
>> /**
>> * This method calculates a number indicating a good or bad match between
>> * queryParams from request and annotated method parameters. A higher
>> number
>> * means a better match.
>> *
>> * @param annotations
>> * Map contains name of QueryParam method parameters as a key and
>> * a Boolean value indicating an existing default value for this
>> * parameter.
>> * @param queryParams
>> * A Set of query parameters provided within the request
>> * @return A positive or negative number, indicating a good match between
>> * query and method
>> */
>> protected int getMatchingRate(Map<String, Boolean> annotations,
>> Set<String> queryParams) {
>> int rate = 0;
>> for (String anno : annotations.keySet()) {
>> if (queryParams.contains(anno)) {
>> // URL query matches one method parameter
>> rate += 2;
>> } else if (!annotations.get(anno).booleanValue()) {
>> // No default value exists for method parameter
>> rate -= 1;
>> }
>> }
>> return rate;
>> }
>>
>> /**
>> * @param opParamAnnos
>> * Array containing all annotations for all method parameters
>> * @return Key in Map is QueryParam name, and Value indicates if a default
>> * value is present.
>> */
>> protected Map<String, Boolean> getAnnotations(Annotation[][]
>> opParamAnnos) {
>> Map<String, Boolean> parameterAnnos = new HashMap<String, Boolean>();
>>
>> if (opParamAnnos.length == 0)
>> return parameterAnnos;
>>
>> for (Annotation[] pAnnos : opParamAnnos) {
>> QueryParam qParam = null;
>> DefaultValue dValue = null;
>> for (Annotation anno : pAnnos) {
>> if (anno instanceof QueryParam)
>> qParam = (QueryParam) anno;
>> if (anno instanceof DefaultValue)
>> dValue = (DefaultValue) anno;
>> }
>> parameterAnnos.put(qParam.value(), Boolean.valueOf((dValue != null)));
>> }
>>
>> return parameterAnnos;
>> }
>>
>> /**
>> * @param query
>> * URL Query
>> * @return A Set of all keys, contained within query.
>> */
>> protected Set<String> getParams(String query) {
>> Set<String> params = new HashSet<String>();
>> if (query == null || query.length() == 0)
>> return params;
>>
>> try {
>> for (String param : query.split("&")) {
>> String pair[] = param.split("=");
>> String key = URLDecoder.decode(pair[0], "UTF-8");
>> params.add(key);
>> }
>> } catch (UnsupportedEncodingException e) {
>> e.printStackTrace();
>> }
>> return params;
>> }
>> }
>>
>
> I think it is difficult to generalize given that another user may want a
> similar support for the selection based on matrix/header/form
> parameters, and the other complexity is that we can have repeating
> parameters, example, "a=1&a=2" query, etc.
>
> However it definitely makes sense to update the docs and show what does
> it mean to customize the selection algo, so what I will do is I will
> update the page and paste the code - lets see may be we can push some of
> the code to CXF eventually
>
> thanks, Sergey
>
>> Best regards.
>> Jan
>>
>> From: Sergey Beryozkin-5 [via CXF]
>> [mailto:[hidden email]</user/SendEmail.jtp?type=node&node=5719454&i=0>]
>> Sent: Dienstag, 27. November 2012 11:31
>> To: Jan Bernhardt
>> Subject: Re: REST Method selection for different QueryParam's
>>
>> Hi Jan
>> On 27/11/12 10:12, janb wrote:
>>
>>> Hi @all,
>>>
>>> I'm wondering about the selection strategy in CXF for different Query
>>> Parameter. The documentation [1] does not cover this at all.
>>>
>>> A simple system-test provided the impression to me, that CXF has no
>>> valid
>>> selection strategy in place for handling different query parameters.
>>> Is this
>>> assumption correct? Should I create a Jira ticket for a better support?
>>>
>>> Here is my sample code. No matter which parameter have been provided
>>> within
>>> my URL, only and always the first method was selected by CXF.
>>>
>>> @Path("/paramTest")
>>> public class MySimpleService {
>>>
>>> @GET
>>> public String getFoo(@QueryParam("foo") String foo){
>>> return "foo:" + foo;
>>> }
>>>
>>> @GET
>>> public String getFooBar(@QueryParam("foo") String foo,
>>> @QueryParam("bar") String bar){
>>> return "foo:" + foo + " bar:" + bar;
>>> }
>>>
>>> @GET
>>> public String getTest(@QueryParam("test") String test){
>>> return "test:" + test;
>>> }
>>> }
>>>
>>> [1]
>>> http://cxf.apache.org/docs/jax-rs-basics.html#JAX-RSBasics-Overviewoftheselectionalgorithm.
>>>
>>>
>>
>> Only URI path segment, HTTP method and in/out media types are taken into
>> consideration when selecting the candidates. If you prefer to have
>> individual methods having the same HTTP Method/Uri Path/Media Types but
>> with specific query parameters then the only way to get it managed is to
>> use a CXF ResourceComparator where, in this case, you can affect the
>> ordering of specific resource methods by checking the current
>> Message.QUERY_STRING available on the CXF Message
>>
>> A simpler alternative is to have a single method and work with
>> UriInfo, say
>>
>> @Context
>> private UriInfo ui;
>>
>> @GET
>> public String getFooOrBar() {
>> MultivaluedMap<String, String> params = ui.getQueryParameters();
>> String foo = params.getFirst("foo");
>> String bar = params.getFirst("foo");
>> // etc
>> }
>>
>> HTH, Sergey
>>
>>>
>>>
>>> --
>>> View this message in context:
>>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187.html
>>>
>>> Sent from the cxf-user mailing list archive at Nabble.com.
>>
>>
>> --
>> Sergey Beryozkin
>>
>> Talend Community Coders
>> http://coders.talend.com/
>>
>> Blog: http://sberyozkin.blogspot.com
>>
>> ________________________________
>> If you reply to this email, your message will be added to the
>> discussion below:
>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719190.html
>>
>> To unsubscribe from REST Method selection for different QueryParam's,
>> click
>> here<
>>
>> NAML<http://cxf.547215.n5.nabble.com/template/NamlServlet.jtp?macro=macro_viewer&id=instant_html%21nabble%3Aemail.naml&base=nabble.naml.namespaces.BasicNamespace-nabble.view.web.template.NabbleNamespace-nabble.view.web.template.NodeNamespace&breadcrumbs=notify_subscribers%21nabble%3Aemail.naml-instant_emails%21nabble%3Aemail.naml-send_instant_email%21nabble%3Aemail.naml>
>>
>>
>>
>>
>>
>> --
>> View this message in context:
>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719219.html
>>
>> Sent from the cxf-user mailing list archive at Nabble.com.
>
>


--
Sergey Beryozkin

Talend Community Coders
http://coders.talend.com/

Blog: http://sberyozkin.blogspot.com

________________________________
If you reply to this email, your message will be added to the discussion below:
http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719454.html
To unsubscribe from REST Method selection for different QueryParam's, click here<http://cxf.547215.n5.nabble.com/template/NamlServlet.jtp?macro=unsubscribe_by_code&node=5719187&code=amJlcm5oYXJkdEB0YWxlbmQuY29tfDU3MTkxODd8LTEzMDQ4ODk1MjM=>.
NAML<http://cxf.547215.n5.nabble.com/template/NamlServlet.jtp?macro=macro_viewer&id=instant_html%21nabble%3Aemail.naml&base=nabble.naml.namespaces.BasicNamespace-nabble.view.web.template.NabbleNamespace-nabble.view.web.template.NodeNamespace&breadcrumbs=notify_subscribers%21nabble%3Aemail.naml-instant_emails%21nabble%3Aemail.naml-send_instant_email%21nabble%3Aemail.naml>




--
View this message in context: http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719458.html
Sent from the cxf-user mailing list archive at Nabble.com.

Re: REST Method selection for different QueryParam's

Posted by Sergey Beryozkin <sb...@gmail.com>.
Hi Jan

I've posted it to the 
wiki:https://cwiki.apache.org/confluence/display/CXF20DOC/JAX-RS+Basics#JAX-RSBasics-Customselectionbetweenmultipleresources

I guess we might indeed can add a more general comparator which would 
select the methods based on the number of optional parameters (of any 
type such as query, header, and the combination, etc), be they single or 
repetitive.

I'm just yet sure if that will work well or not, example,
what if we have a single query parameter but it has the name which is 
not expected by a method expecting a single parameter, etc... May be we 
will tune it in time :-)

Thanks, Sergey



On 27/11/12 16:40, Sergey Beryozkin wrote:
> Hi Jan
>
> This looks very neat, more comments below
> On 27/11/12 15:25, janb wrote:
>> Hi Sergey,
>>
>> Thank you for your reply. If just developed a generic sample that uses
>> CXF standard class and operation selection algorithms, but extends it
>> with a QueryParam selection algorithm. If all other conditions in CXF
>> match and hence CXF cannot decide which method to select best, my
>> QueryParam selection algorithm comes into place. This algorithm
>> chooses the method with most matches between provided parameters in
>> query and matching QueryParam annotations within the method.
>> Additional (not matching) QueryParam annotations will decrease the
>> matching rate, while method parameters with a default value will be
>> ignored (in the rating).
>>
>> If you think this code is of any value for CXF, please feel free to
>> extend the current selection strategy with my QueryParam selection
>> algorithm. I guess the best place for my algorithm would be another
>> JAXRSUtils method, like
>> JAXRSUtils.compareQueryParams(...) to be called at the end of
>> OperationResourceInfoComparator.compare(...).
>>
>>
>> public class QueryResourceInfoComperator extends
>> OperationResourceInfoComparator implements
>> ResourceComparator {
>>
>> public QueryResourceInfoComperator() {
>> super(null, null);
>> }
>>
>> @Override
>> public int compare(ClassResourceInfo cri1, ClassResourceInfo cri2,
>> Message message) {
>> // Leave Class selection to CXF
>> return 0;
>> }
>>
>> @Override
>> public int compare(OperationResourceInfo oper1, OperationResourceInfo
>> oper2, Message message) {
>>
>> // Check if CXF can make a decision
>> int cxfResult = super.compare(oper1, oper2);
>> if (cxfResult != 0)
>> return cxfResult;
>>
>> // Compare QueryParam annotations
>> Set<String> qParams = getParams((String)
>> message.get(Message.QUERY_STRING));
>> int op1Counter =
>> getMatchingRate(getAnnotations(oper1.getMethodToInvoke().getParameterAnnotations()),
>>
>> qParams);
>> int op2Counter =
>> getMatchingRate(getAnnotations(oper2.getMethodToInvoke().getParameterAnnotations()),
>>
>> qParams);
>>
>> return op1Counter == op2Counter
>> ? 0
>> : op1Counter< op2Counter
>> ? 1
>> : -1;
>> }
>>
>> /**
>> * This method calculates a number indicating a good or bad match between
>> * queryParams from request and annotated method parameters. A higher
>> number
>> * means a better match.
>> *
>> * @param annotations
>> * Map contains name of QueryParam method parameters as a key and
>> * a Boolean value indicating an existing default value for this
>> * parameter.
>> * @param queryParams
>> * A Set of query parameters provided within the request
>> * @return A positive or negative number, indicating a good match between
>> * query and method
>> */
>> protected int getMatchingRate(Map<String, Boolean> annotations,
>> Set<String> queryParams) {
>> int rate = 0;
>> for (String anno : annotations.keySet()) {
>> if (queryParams.contains(anno)) {
>> // URL query matches one method parameter
>> rate += 2;
>> } else if (!annotations.get(anno).booleanValue()) {
>> // No default value exists for method parameter
>> rate -= 1;
>> }
>> }
>> return rate;
>> }
>>
>> /**
>> * @param opParamAnnos
>> * Array containing all annotations for all method parameters
>> * @return Key in Map is QueryParam name, and Value indicates if a default
>> * value is present.
>> */
>> protected Map<String, Boolean> getAnnotations(Annotation[][]
>> opParamAnnos) {
>> Map<String, Boolean> parameterAnnos = new HashMap<String, Boolean>();
>>
>> if (opParamAnnos.length == 0)
>> return parameterAnnos;
>>
>> for (Annotation[] pAnnos : opParamAnnos) {
>> QueryParam qParam = null;
>> DefaultValue dValue = null;
>> for (Annotation anno : pAnnos) {
>> if (anno instanceof QueryParam)
>> qParam = (QueryParam) anno;
>> if (anno instanceof DefaultValue)
>> dValue = (DefaultValue) anno;
>> }
>> parameterAnnos.put(qParam.value(), Boolean.valueOf((dValue != null)));
>> }
>>
>> return parameterAnnos;
>> }
>>
>> /**
>> * @param query
>> * URL Query
>> * @return A Set of all keys, contained within query.
>> */
>> protected Set<String> getParams(String query) {
>> Set<String> params = new HashSet<String>();
>> if (query == null || query.length() == 0)
>> return params;
>>
>> try {
>> for (String param : query.split("&")) {
>> String pair[] = param.split("=");
>> String key = URLDecoder.decode(pair[0], "UTF-8");
>> params.add(key);
>> }
>> } catch (UnsupportedEncodingException e) {
>> e.printStackTrace();
>> }
>> return params;
>> }
>> }
>>
>
> I think it is difficult to generalize given that another user may want a
> similar support for the selection based on matrix/header/form
> parameters, and the other complexity is that we can have repeating
> parameters, example, "a=1&a=2" query, etc.
>
> However it definitely makes sense to update the docs and show what does
> it mean to customize the selection algo, so what I will do is I will
> update the page and paste the code - lets see may be we can push some of
> the code to CXF eventually
>
> thanks, Sergey
>
>> Best regards.
>> Jan
>>
>> From: Sergey Beryozkin-5 [via CXF]
>> [mailto:ml-node+s547215n5719190h41@n5.nabble.com]
>> Sent: Dienstag, 27. November 2012 11:31
>> To: Jan Bernhardt
>> Subject: Re: REST Method selection for different QueryParam's
>>
>> Hi Jan
>> On 27/11/12 10:12, janb wrote:
>>
>>> Hi @all,
>>>
>>> I'm wondering about the selection strategy in CXF for different Query
>>> Parameter. The documentation [1] does not cover this at all.
>>>
>>> A simple system-test provided the impression to me, that CXF has no
>>> valid
>>> selection strategy in place for handling different query parameters.
>>> Is this
>>> assumption correct? Should I create a Jira ticket for a better support?
>>>
>>> Here is my sample code. No matter which parameter have been provided
>>> within
>>> my URL, only and always the first method was selected by CXF.
>>>
>>> @Path("/paramTest")
>>> public class MySimpleService {
>>>
>>> @GET
>>> public String getFoo(@QueryParam("foo") String foo){
>>> return "foo:" + foo;
>>> }
>>>
>>> @GET
>>> public String getFooBar(@QueryParam("foo") String foo,
>>> @QueryParam("bar") String bar){
>>> return "foo:" + foo + " bar:" + bar;
>>> }
>>>
>>> @GET
>>> public String getTest(@QueryParam("test") String test){
>>> return "test:" + test;
>>> }
>>> }
>>>
>>> [1]
>>> http://cxf.apache.org/docs/jax-rs-basics.html#JAX-RSBasics-Overviewoftheselectionalgorithm.
>>>
>>>
>>
>> Only URI path segment, HTTP method and in/out media types are taken into
>> consideration when selecting the candidates. If you prefer to have
>> individual methods having the same HTTP Method/Uri Path/Media Types but
>> with specific query parameters then the only way to get it managed is to
>> use a CXF ResourceComparator where, in this case, you can affect the
>> ordering of specific resource methods by checking the current
>> Message.QUERY_STRING available on the CXF Message
>>
>> A simpler alternative is to have a single method and work with
>> UriInfo, say
>>
>> @Context
>> private UriInfo ui;
>>
>> @GET
>> public String getFooOrBar() {
>> MultivaluedMap<String, String> params = ui.getQueryParameters();
>> String foo = params.getFirst("foo");
>> String bar = params.getFirst("foo");
>> // etc
>> }
>>
>> HTH, Sergey
>>
>>>
>>>
>>> --
>>> View this message in context:
>>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187.html
>>>
>>> Sent from the cxf-user mailing list archive at Nabble.com.
>>
>>
>> --
>> Sergey Beryozkin
>>
>> Talend Community Coders
>> http://coders.talend.com/
>>
>> Blog: http://sberyozkin.blogspot.com
>>
>> ________________________________
>> If you reply to this email, your message will be added to the
>> discussion below:
>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719190.html
>>
>> To unsubscribe from REST Method selection for different QueryParam's,
>> click
>> here<http://cxf.547215.n5.nabble.com/template/NamlServlet.jtp?macro=unsubscribe_by_code&node=5719187&code=amJlcm5oYXJkdEB0YWxlbmQuY29tfDU3MTkxODd8LTEzMDQ4ODk1MjM=>.
>>
>> NAML<http://cxf.547215.n5.nabble.com/template/NamlServlet.jtp?macro=macro_viewer&id=instant_html%21nabble%3Aemail.naml&base=nabble.naml.namespaces.BasicNamespace-nabble.view.web.template.NabbleNamespace-nabble.view.web.template.NodeNamespace&breadcrumbs=notify_subscribers%21nabble%3Aemail.naml-instant_emails%21nabble%3Aemail.naml-send_instant_email%21nabble%3Aemail.naml>
>>
>>
>>
>>
>>
>> --
>> View this message in context:
>> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719219.html
>>
>> Sent from the cxf-user mailing list archive at Nabble.com.
>
>


-- 
Sergey Beryozkin

Talend Community Coders
http://coders.talend.com/

Blog: http://sberyozkin.blogspot.com

Re: REST Method selection for different QueryParam's

Posted by Sergey Beryozkin <sb...@gmail.com>.
Hi Jan

This looks very neat, more comments below
On 27/11/12 15:25, janb wrote:
> Hi Sergey,
>
> Thank you for your reply. If just developed a generic sample that uses CXF standard class and operation selection algorithms, but extends it with a QueryParam selection algorithm. If all other conditions in CXF match and hence CXF cannot decide which method to select best, my QueryParam selection algorithm comes into place. This algorithm chooses the method with most matches between provided parameters in query and matching QueryParam annotations within the method. Additional (not matching) QueryParam annotations will decrease the matching rate, while method parameters with a default value will be ignored (in the rating).
>
> If you think this code is of any value for CXF, please feel free to extend the current selection strategy with my QueryParam selection algorithm. I guess the best place for my algorithm would be another JAXRSUtils method, like
> JAXRSUtils.compareQueryParams(...) to be called at the end of OperationResourceInfoComparator.compare(...).
>
>
> public class QueryResourceInfoComperator extends OperationResourceInfoComparator implements
>          ResourceComparator {
>
>      public QueryResourceInfoComperator() {
>          super(null, null);
>      }
>
>      @Override
>      public int compare(ClassResourceInfo cri1, ClassResourceInfo cri2, Message message) {
>          // Leave Class selection to CXF
>          return 0;
>      }
>
>      @Override
>      public int compare(OperationResourceInfo oper1, OperationResourceInfo oper2, Message message) {
>
>          // Check if CXF can make a decision
>          int cxfResult = super.compare(oper1, oper2);
>          if (cxfResult != 0)
>              return cxfResult;
>
>          // Compare QueryParam annotations
>          Set<String>  qParams = getParams((String) message.get(Message.QUERY_STRING));
>          int op1Counter = getMatchingRate(getAnnotations(oper1.getMethodToInvoke().getParameterAnnotations()),
>                  qParams);
>          int op2Counter = getMatchingRate(getAnnotations(oper2.getMethodToInvoke().getParameterAnnotations()),
>                  qParams);
>
>          return op1Counter == op2Counter
>                  ? 0
>                  : op1Counter<  op2Counter
>                          ? 1
>                          : -1;
>      }
>
>      /**
>       * This method calculates a number indicating a good or bad match between
>       * queryParams from request and annotated method parameters. A higher number
>       * means a better match.
>       *
>       * @param annotations
>       *            Map contains name of QueryParam method parameters as a key and
>       *            a Boolean value indicating an existing default value for this
>       *            parameter.
>       * @param queryParams
>       *            A Set of query parameters provided within the request
>       * @return A positive or negative number, indicating a good match between
>       *         query and method
>       */
>      protected int getMatchingRate(Map<String, Boolean>  annotations, Set<String>  queryParams) {
>          int rate = 0;
>          for (String anno : annotations.keySet()) {
>              if (queryParams.contains(anno)) {
>                  // URL query matches one method parameter
>                  rate += 2;
>              } else if (!annotations.get(anno).booleanValue()) {
>                  // No default value exists for method parameter
>                  rate -= 1;
>              }
>          }
>          return rate;
>      }
>
>      /**
>       * @param opParamAnnos
>       *            Array containing all annotations for all method parameters
>       * @return Key in Map is QueryParam name, and Value indicates if a default
>       *         value is present.
>       */
>      protected Map<String, Boolean>  getAnnotations(Annotation[][] opParamAnnos) {
>          Map<String, Boolean>  parameterAnnos = new HashMap<String, Boolean>();
>
>          if (opParamAnnos.length == 0)
>              return parameterAnnos;
>
>          for (Annotation[] pAnnos : opParamAnnos) {
>              QueryParam qParam = null;
>              DefaultValue dValue = null;
>              for (Annotation anno : pAnnos) {
>                  if (anno instanceof QueryParam)
>                      qParam = (QueryParam) anno;
>                  if (anno instanceof DefaultValue)
>                      dValue = (DefaultValue) anno;
>              }
>              parameterAnnos.put(qParam.value(), Boolean.valueOf((dValue != null)));
>          }
>
>          return parameterAnnos;
>      }
>
>      /**
>       * @param query
>       *            URL Query
>       * @return A Set of all keys, contained within query.
>       */
>      protected Set<String>  getParams(String query) {
>          Set<String>  params = new HashSet<String>();
>          if (query == null || query.length() == 0)
>              return params;
>
>          try {
>              for (String param : query.split("&")) {
>                  String pair[] = param.split("=");
>                  String key = URLDecoder.decode(pair[0], "UTF-8");
>                  params.add(key);
>              }
>          } catch (UnsupportedEncodingException e) {
>              e.printStackTrace();
>          }
>          return params;
>      }
> }
>

I think it is difficult to generalize given that another user may want a 
similar support for the selection based on matrix/header/form 
parameters, and the other complexity is that we can have repeating 
parameters, example, "a=1&a=2" query, etc.

However it definitely makes sense to update the docs and show what does 
it mean to customize the selection algo, so what I will do is I will 
update the page and paste the code - lets see may be we can push some of 
the code to CXF eventually

thanks, Sergey

> Best regards.
> Jan
>
> From: Sergey Beryozkin-5 [via CXF] [mailto:ml-node+s547215n5719190h41@n5.nabble.com]
> Sent: Dienstag, 27. November 2012 11:31
> To: Jan Bernhardt
> Subject: Re: REST Method selection for different QueryParam's
>
> Hi Jan
> On 27/11/12 10:12, janb wrote:
>
>> Hi @all,
>>
>> I'm wondering about the selection strategy in CXF for different Query
>> Parameter. The documentation [1] does not cover this at all.
>>
>> A simple system-test provided the impression to me, that CXF has no valid
>> selection strategy in place for handling different query parameters. Is this
>> assumption correct? Should I create a Jira ticket for a better support?
>>
>> Here is my sample code. No matter which parameter have been provided within
>> my URL, only and always the first method was selected by CXF.
>>
>> @Path("/paramTest")
>> public class MySimpleService {
>>
>>       @GET
>>       public String getFoo(@QueryParam("foo") String foo){
>>           return "foo:" + foo;
>>       }
>>
>>       @GET
>>       public String getFooBar(@QueryParam("foo") String foo,
>> @QueryParam("bar") String bar){
>>           return "foo:" + foo + " bar:" + bar;
>>       }
>>
>>       @GET
>>       public String getTest(@QueryParam("test") String test){
>>           return "test:" + test;
>>       }
>> }
>>
>> [1]
>> http://cxf.apache.org/docs/jax-rs-basics.html#JAX-RSBasics-Overviewoftheselectionalgorithm.
>>
>
> Only URI path segment, HTTP method and in/out media types are taken into
> consideration when selecting the candidates. If you prefer to have
> individual methods having the same HTTP Method/Uri Path/Media Types but
> with specific query parameters then the only way to get it managed is to
> use a CXF ResourceComparator where, in this case, you can affect the
> ordering of specific resource methods by checking the current
> Message.QUERY_STRING available on the CXF Message
>
> A simpler alternative is to have a single method and work with UriInfo, say
>
> @Context
> private UriInfo ui;
>
> @GET
> public String getFooOrBar() {
>       MultivaluedMap<String, String>  params = ui.getQueryParameters();
>       String foo = params.getFirst("foo");
>       String bar = params.getFirst("foo");
>       // etc
> }
>
> HTH, Sergey
>
>>
>>
>> --
>> View this message in context: http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187.html
>> Sent from the cxf-user mailing list archive at Nabble.com.
>
>
> --
> Sergey Beryozkin
>
> Talend Community Coders
> http://coders.talend.com/
>
> Blog: http://sberyozkin.blogspot.com
>
> ________________________________
> If you reply to this email, your message will be added to the discussion below:
> http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719190.html
> To unsubscribe from REST Method selection for different QueryParam's, click here<http://cxf.547215.n5.nabble.com/template/NamlServlet.jtp?macro=unsubscribe_by_code&node=5719187&code=amJlcm5oYXJkdEB0YWxlbmQuY29tfDU3MTkxODd8LTEzMDQ4ODk1MjM=>.
> NAML<http://cxf.547215.n5.nabble.com/template/NamlServlet.jtp?macro=macro_viewer&id=instant_html%21nabble%3Aemail.naml&base=nabble.naml.namespaces.BasicNamespace-nabble.view.web.template.NabbleNamespace-nabble.view.web.template.NodeNamespace&breadcrumbs=notify_subscribers%21nabble%3Aemail.naml-instant_emails%21nabble%3Aemail.naml-send_instant_email%21nabble%3Aemail.naml>
>
>
>
>
> --
> View this message in context: http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719219.html
> Sent from the cxf-user mailing list archive at Nabble.com.


-- 
Sergey Beryozkin

Talend Community Coders
http://coders.talend.com/

Blog: http://sberyozkin.blogspot.com

RE: REST Method selection for different QueryParam's

Posted by janb <jb...@talend.com>.
Hi Sergey,

Thank you for your reply. If just developed a generic sample that uses CXF standard class and operation selection algorithms, but extends it with a QueryParam selection algorithm. If all other conditions in CXF match and hence CXF cannot decide which method to select best, my QueryParam selection algorithm comes into place. This algorithm chooses the method with most matches between provided parameters in query and matching QueryParam annotations within the method. Additional (not matching) QueryParam annotations will decrease the matching rate, while method parameters with a default value will be ignored (in the rating).

If you think this code is of any value for CXF, please feel free to extend the current selection strategy with my QueryParam selection algorithm. I guess the best place for my algorithm would be another JAXRSUtils method, like
JAXRSUtils.compareQueryParams(...) to be called at the end of OperationResourceInfoComparator.compare(...).


public class QueryResourceInfoComperator extends OperationResourceInfoComparator implements
        ResourceComparator {

    public QueryResourceInfoComperator() {
        super(null, null);
    }

    @Override
    public int compare(ClassResourceInfo cri1, ClassResourceInfo cri2, Message message) {
        // Leave Class selection to CXF
        return 0;
    }

    @Override
    public int compare(OperationResourceInfo oper1, OperationResourceInfo oper2, Message message) {

        // Check if CXF can make a decision
        int cxfResult = super.compare(oper1, oper2);
        if (cxfResult != 0)
            return cxfResult;

        // Compare QueryParam annotations
        Set<String> qParams = getParams((String) message.get(Message.QUERY_STRING));
        int op1Counter = getMatchingRate(getAnnotations(oper1.getMethodToInvoke().getParameterAnnotations()),
                qParams);
        int op2Counter = getMatchingRate(getAnnotations(oper2.getMethodToInvoke().getParameterAnnotations()),
                qParams);

        return op1Counter == op2Counter
                ? 0
                : op1Counter < op2Counter
                        ? 1
                        : -1;
    }

    /**
     * This method calculates a number indicating a good or bad match between
     * queryParams from request and annotated method parameters. A higher number
     * means a better match.
     *
     * @param annotations
     *            Map contains name of QueryParam method parameters as a key and
     *            a Boolean value indicating an existing default value for this
     *            parameter.
     * @param queryParams
     *            A Set of query parameters provided within the request
     * @return A positive or negative number, indicating a good match between
     *         query and method
     */
    protected int getMatchingRate(Map<String, Boolean> annotations, Set<String> queryParams) {
        int rate = 0;
        for (String anno : annotations.keySet()) {
            if (queryParams.contains(anno)) {
                // URL query matches one method parameter
                rate += 2;
            } else if (!annotations.get(anno).booleanValue()) {
                // No default value exists for method parameter
                rate -= 1;
            }
        }
        return rate;
    }

    /**
     * @param opParamAnnos
     *            Array containing all annotations for all method parameters
     * @return Key in Map is QueryParam name, and Value indicates if a default
     *         value is present.
     */
    protected Map<String, Boolean> getAnnotations(Annotation[][] opParamAnnos) {
        Map<String, Boolean> parameterAnnos = new HashMap<String, Boolean>();

        if (opParamAnnos.length == 0)
            return parameterAnnos;

        for (Annotation[] pAnnos : opParamAnnos) {
            QueryParam qParam = null;
            DefaultValue dValue = null;
            for (Annotation anno : pAnnos) {
                if (anno instanceof QueryParam)
                    qParam = (QueryParam) anno;
                if (anno instanceof DefaultValue)
                    dValue = (DefaultValue) anno;
            }
            parameterAnnos.put(qParam.value(), Boolean.valueOf((dValue != null)));
        }

        return parameterAnnos;
    }

    /**
     * @param query
     *            URL Query
     * @return A Set of all keys, contained within query.
     */
    protected Set<String> getParams(String query) {
        Set<String> params = new HashSet<String>();
        if (query == null || query.length() == 0)
            return params;

        try {
            for (String param : query.split("&")) {
                String pair[] = param.split("=");
                String key = URLDecoder.decode(pair[0], "UTF-8");
                params.add(key);
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return params;
    }
}

Best regards.
Jan

From: Sergey Beryozkin-5 [via CXF] [mailto:ml-node+s547215n5719190h41@n5.nabble.com]
Sent: Dienstag, 27. November 2012 11:31
To: Jan Bernhardt
Subject: Re: REST Method selection for different QueryParam's

Hi Jan
On 27/11/12 10:12, janb wrote:

> Hi @all,
>
> I'm wondering about the selection strategy in CXF for different Query
> Parameter. The documentation [1] does not cover this at all.
>
> A simple system-test provided the impression to me, that CXF has no valid
> selection strategy in place for handling different query parameters. Is this
> assumption correct? Should I create a Jira ticket for a better support?
>
> Here is my sample code. No matter which parameter have been provided within
> my URL, only and always the first method was selected by CXF.
>
> @Path("/paramTest")
> public class MySimpleService {
>
>      @GET
>      public String getFoo(@QueryParam("foo") String foo){
>          return "foo:" + foo;
>      }
>
>      @GET
>      public String getFooBar(@QueryParam("foo") String foo,
> @QueryParam("bar") String bar){
>          return "foo:" + foo + " bar:" + bar;
>      }
>
>      @GET
>      public String getTest(@QueryParam("test") String test){
>          return "test:" + test;
>      }
> }
>
> [1]
> http://cxf.apache.org/docs/jax-rs-basics.html#JAX-RSBasics-Overviewoftheselectionalgorithm.
>

Only URI path segment, HTTP method and in/out media types are taken into
consideration when selecting the candidates. If you prefer to have
individual methods having the same HTTP Method/Uri Path/Media Types but
with specific query parameters then the only way to get it managed is to
use a CXF ResourceComparator where, in this case, you can affect the
ordering of specific resource methods by checking the current
Message.QUERY_STRING available on the CXF Message

A simpler alternative is to have a single method and work with UriInfo, say

@Context
private UriInfo ui;

@GET
public String getFooOrBar() {
     MultivaluedMap<String, String> params = ui.getQueryParameters();
     String foo = params.getFirst("foo");
     String bar = params.getFirst("foo");
     // etc
}

HTH, Sergey

>
>
> --
> View this message in context: http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187.html
> Sent from the cxf-user mailing list archive at Nabble.com.


--
Sergey Beryozkin

Talend Community Coders
http://coders.talend.com/

Blog: http://sberyozkin.blogspot.com

________________________________
If you reply to this email, your message will be added to the discussion below:
http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719190.html
To unsubscribe from REST Method selection for different QueryParam's, click here<http://cxf.547215.n5.nabble.com/template/NamlServlet.jtp?macro=unsubscribe_by_code&node=5719187&code=amJlcm5oYXJkdEB0YWxlbmQuY29tfDU3MTkxODd8LTEzMDQ4ODk1MjM=>.
NAML<http://cxf.547215.n5.nabble.com/template/NamlServlet.jtp?macro=macro_viewer&id=instant_html%21nabble%3Aemail.naml&base=nabble.naml.namespaces.BasicNamespace-nabble.view.web.template.NabbleNamespace-nabble.view.web.template.NodeNamespace&breadcrumbs=notify_subscribers%21nabble%3Aemail.naml-instant_emails%21nabble%3Aemail.naml-send_instant_email%21nabble%3Aemail.naml>




--
View this message in context: http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187p5719219.html
Sent from the cxf-user mailing list archive at Nabble.com.

Re: REST Method selection for different QueryParam's

Posted by Sergey Beryozkin <sb...@gmail.com>.
Hi Jan
On 27/11/12 10:12, janb wrote:
> Hi @all,
>
> I'm wondering about the selection strategy in CXF for different Query
> Parameter. The documentation [1] does not cover this at all.
>
> A simple system-test provided the impression to me, that CXF has no valid
> selection strategy in place for handling different query parameters. Is this
> assumption correct? Should I create a Jira ticket for a better support?
>
> Here is my sample code. No matter which parameter have been provided within
> my URL, only and always the first method was selected by CXF.
>
> @Path("/paramTest")
> public class MySimpleService {
>
>      @GET
>      public String getFoo(@QueryParam("foo") String foo){
>          return "foo:" + foo;
>      }
>
>      @GET
>      public String getFooBar(@QueryParam("foo") String foo,
> @QueryParam("bar") String bar){
>          return "foo:" + foo + " bar:" + bar;
>      }
>
>      @GET
>      public String getTest(@QueryParam("test") String test){
>          return "test:" + test;
>      }
> }
>
> [1]
> http://cxf.apache.org/docs/jax-rs-basics.html#JAX-RSBasics-Overviewoftheselectionalgorithm.
>

Only URI path segment, HTTP method and in/out media types are taken into 
consideration when selecting the candidates. If you prefer to have 
individual methods having the same HTTP Method/Uri Path/Media Types but 
with specific query parameters then the only way to get it managed is to 
use a CXF ResourceComparator where, in this case, you can affect the 
ordering of specific resource methods by checking the current 
Message.QUERY_STRING available on the CXF Message

A simpler alternative is to have a single method and work with UriInfo, say

@Context
private UriInfo ui;

@GET
public String getFooOrBar() {
     MultivaluedMap<String, String> params = ui.getQueryParameters();
     String foo = params.getFirst("foo");
     String bar = params.getFirst("foo");
     // etc
}

HTH, Sergey

>
>
> --
> View this message in context: http://cxf.547215.n5.nabble.com/REST-Method-selection-for-different-QueryParam-s-tp5719187.html
> Sent from the cxf-user mailing list archive at Nabble.com.


-- 
Sergey Beryozkin

Talend Community Coders
http://coders.talend.com/

Blog: http://sberyozkin.blogspot.com