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:[email protected]]
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.

Reply via email to