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