RESTEasy - Reuse method parameters when @GET/@POST

10,289

Solution 1

Quoting the book RESTful Java with JAX-RS:

JAX-RS defines five annotations that map to specific HTTP operations:

  • @javax.ws.rs.GET
  • @javax.ws.rs.PUT
  • @javax.ws.rs.POST
  • @javax.ws.rs.DELETE
  • @javax.ws.rs.HEAD

(...)
The @GET annotation instructs the JAX-RS runtime that this Java method will process HTTP GET requests to the URI. You would use one of the other five annotations described earlier to bind to different HTTP operations. One thing to note, though, is that you may only apply one HTTP method annotation per Java method. A deployment error occurs if you apply more than one.

(The text above was written by the creator of RESTEasy.)

And, in short, as RESTEasy complies with JAX-RS, you can't annotate a method with more than one HTTP verb.

If you are not convinced, looking at the @GET annotation, you can see that it is only a meta-annotation to @HttpMethod.

/**
 * Indicates that the annotated method responds to HTTP GET requests
 * @see HttpMethod
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@HttpMethod(HttpMethod.GET)
public @interface GET { 
}

And if you open the @HttpMethod, check the javadoc (It is an error for a method to be annotated with more than one annotation that is annotated with HttpMethod.):

/**
 * Associates the name of a HTTP method with an annotation. A Java method annotated
 * with a runtime annotation that is itself annotated with this annotation will
 * be used to handle HTTP requests of the indicated HTTP method. It is an error
 * for a method to be annotated with more than one annotation that is annotated
 * with {@code HttpMethod}.
 *
 * @see GET
 * @see POST
 * @see PUT
 * @see DELETE
 * @see HEAD
 */
@Target({ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface HttpMethod
{

So, that's it, you can't have them both in the same method.


That said, you could, if you really really must, achieve that through a PreProcessInterceptor that would be is called before a JAX-RS method.

Still, this way is much more complicated (as you'd have to parse the parameters yourself) and much less maintainable (services being delivered at interceptors!?).

Bottom line, to my knowledge, your solution is the optimal.

Check what I'm saying in the test below:

public class QueryAndFormParamTest  {

    @Path("/")
    public static class InterceptedResource {
        @GET
        //@Path("/stuff") // uncomment this and it will not work
        public String otherService(@QueryParam("yadda") String name){
            return "Im never called in this example" + name;
        }
    }

    public static class MyInterceptor implements PreProcessInterceptor, AcceptedByMethod {
        @Override
        public boolean accept(Class declaring, Method method) {
            System.out.println("Accepted by method "+method.getName());
            // you can check if this interceptor should act on this method here
            return true; // it'll act everytime
        }

        @Override
        public ServerResponse preProcess(HttpRequest request, ResourceMethod method)
                throws Failure, WebApplicationException {

            // parsing form parameters
            if (request.getHttpHeaders().getMediaType() != null && request.getHttpHeaders().getMediaType().isCompatible(MediaType.valueOf("application/x-www-form-urlencoded"))) {
                MultivaluedMap<String, String> formParameters = request.getFormParameters();
                if (formParameters != null) {
                    for (String key : formParameters.keySet()) {
                        System.out.println("[FORM] "+key + ": "+formParameters.get(key));
                    }
                }
            }

            // parsing query parameters
            MultivaluedMap<String, String> queryParameters = request.getUri().getQueryParameters();
            if (queryParameters != null)
            for (String key : queryParameters.keySet()) {
                System.out.println("[QUERY] "+key + ": "+queryParameters.get(key));
            }

            String responseText = "do something: " + request.getUri().getQueryParameters().getFirst("test");
            return new ServerResponse(responseText, 200, new Headers<Object>());
        }
    }

    @Test
    public void test() throws Exception {
        Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
        dispatcher.getProviderFactory().getServerPreProcessInterceptorRegistry().register(new MyInterceptor());
        dispatcher.getRegistry().addSingletonResource(new InterceptedResource());

        MockHttpRequest request = MockHttpRequest.get("/?test=someStuff");
        MockHttpResponse response = new MockHttpResponse();

        dispatcher.invoke(request, response);

        System.out.println(response.getContentAsString());
        Assert.assertEquals("do something: someStuff", response.getContentAsString());
    }
}

Solution 2

You cannot have a REST-method, which is annotated with more than one of the @GET , @POST , @PUT , @DELETE annotations, because this is conflict with the HTTP specification.

Also, if myMethod2 just returns the result of myMethod, you can use the only one of them in you application (for instance, myMethod), because basically myMethod2 just reads and retrieves data from the server, but doesn't update anything. This means it's not proper to be annotated with @POST, since it doesn't update anything on the server. If annotated with @POST, it will still work, but it won't meet the HTTP specs.

There's a mapping between the CRUD operations and the HTTP verbs. In the cases when you create a resource on the server, you have to use PUT or POST and in the cases when you want the read some resource from the server, you're supposed to use GET. All the cases are as follows:

Create = PUT with a new URI
         POST to a base URI returning a newly created URI
Read   = GET
Update = PUT with an existing URI
Delete = DELETE

Solution 3

For binding the multiple queryParam into single object we need add @Form as a arguments in response method. It works fine for us.

@GET    
@Path("/")  
@Produces("application/json")
@Consumes("application/json")

public Response search(@Form CatalogSearchRequest reqObject)      
{

System.out.println("Entered into service"+reqObject.getAttribute());


}

POJO CLASS should contain @QueryParam("") for every attributes
for example:

@QueryParam("pageSize")
public Integer pageSize;

@QueryParam("page")
public Integer page;

public Integer getPageSize() {
    return pageSize;
}

public void setPageSize(Integer pageSize) {
    this.pageSize = pageSize;
}

public Integer getPage() 
{
 return page;
}

public void setPage(Integer page)
{
    this.page = page;
}

regards,
Prasanna.

Share:
10,289

Related videos on Youtube

gustavohenke
Author by

gustavohenke

Developer and entrepreneur of the web. I love to invest my time, knowledge and forces to help build a more open, innovative, functional and liberal web. You'll likely find me on javascript related tags, but I obviously love html5 and css3 as well!

Updated on June 04, 2022

Comments

  • gustavohenke
    gustavohenke over 1 year

    I have a method in my RESTEasy service which I'd like to use as both @GET/@POST, and its data may come from both query string and request body.

    @GET
    @POST
    public String myMethod(@QueryParam("param1") @FormParam("param1") String param1,
                           @QueryParam("param2") @FormParam("param1") String param2) {
        // ...do things
    }
    

    However, I haven't found yet a way to do this without doing the following:

    @GET
    public String myMethod(@QueryParam("param1") String param1, @QueryParam("param2") String param2) {
        // ...do things
    }
    
    @POST
    public String myMethod2(@FormParam("param1") String param1, @FormParam("param2") String param2) {
        return this.myMethod(param1, param2);
    }
    

    Does anyone knows how to make the first example work, or another approach with the least code possible?

    • eiden
      eiden over 10 years
      What's bothering you about this limitation? Is it the fact that you need to duplicate the annotations?
  • gustavohenke
    gustavohenke over 10 years
    it's strange, I don't get any deployment error in the method I have with both @GET and @POST annotations; also, I'm able to use it, but if @QueryParam is defined, it's always used instead of @FormParam..
  • acdcjunior
    acdcjunior over 10 years
    I'd have to check, but what may be happening is it is failing gracefully by using only the first annotation (both in the method and the parameter) and ignoring the rest. Still, this is unpredictable behaviour, since the spec is very clear about how more than one should not be used.
  • gustavohenke
    gustavohenke about 10 years
    Thanks for the reply, but this doesn't answer the question; also, it has already been answered anyway :)
  • Zach Lysobey
    Zach Lysobey about 10 years
    While the spirit of this post is good.. "You cannot have a REST-method, ... annotated with more than one... because this is conflict with the HTTP specification." isn't true. You cannot because RESTEasy doesn't allow it. I wish it did... because sometimes it is (or would be) convenient to break with the spirit of HTTP to get a job done.
  • Edoardo
    Edoardo over 7 years
    even though it is true that this does not answer the question directly, it should be noted that it answers the reason that triggers the question in the first place: one would like to have simultaneously @get and @post methods mostly to reuse the entry parameters of the query.