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.
