2

I need to implement a webservice that uses the first query parameter to identify the operation, i.e. the client call would be something like: http://localhost:8080/ws/operation?info or http://localhost:8080/ws/operation?create&name=something.

It seems that I cannot distinguish the methods using the @Path annotation as the distinguishing characteristic is in the query parameters. And the following example is throwing exceptions as well:

package com.example.ws;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;

@Path("/operation")
public class Operation {

    @GET 
    public String info(@QueryParam("info") String info) {
        return "info";
    }

    @GET 
    public String create(@QueryParam("create") String create) {
        return "create";
    }
}

Is there a way to specify methods to be used depending on query parameters? Or do I really have to define one method and check within that one whether certain query parameters are set?

Isabi
  • 151
  • 4
  • 12
  • "I need to implement a webservice that uses the first query parameter to identify the operation" - JAXRS allows you to build REST resources but it appears that you're trying to use it to [implement URI tunneling](http://www.infoq.com/articles/rest-anti-patterns). Have you considered a more RESTful approach, like using POST for create requests and GET for info requests, rather than tunneling it all through GET? If you can't change your API to be more RESTful IHMO JAXRS/Jersey probably isn't the best tool for the job... you'll be fighting it all the way through. – John R May 21 '14 at 02:42
  • Thanks, John. I'd be happy to choose another way, and also to get some hints on what method you'd prefer. However, unfortunately I can't change the way the resources are called by the client as I try to implement an interface specification that encodes the operation in this way. – Isabi May 21 '14 at 05:48

3 Answers3

4

I see this is already answered, but I had a similar problem and achieved a solution using sub-resources.

package com.example.ws;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;

@Path("/")
public class Operation {

    @Path("/")
    public Object sub(@QueryParam("operation") String operation)
    {
       if ("info".equals(operation))
          return new InfoOp();
       if ("create".equals(operation))
          return new CreateOp();
       //handle via error code
    }

    public class InfoOp {
        @GET 
        public String info() {
            return "info";
        }
    }

    public class CreateOp {
        @GET 
        public String create(@QueryParam("name") String name) {
            return "create: " + name;
        }
    }
}
Paul Bilnoski
  • 556
  • 4
  • 13
2

I think method selection is based on the non parameter part of the URI. Can't you design this in a way the client invoke http://localhost:8080/ws/operation/info and http://localhost:8080/ws/operation/create?name=something instead?

That would be easy to achieve:

@Path("/operation")
public class Operation {

    @GET 
    @Path("info")
    public String info() {
        return "info";
    }

    @GET 
    @Path("create")
    public String create(@QueryParam("name") String name) {
        return "create";
    }
}
Claudio
  • 1,848
  • 12
  • 26
  • Unfortunately, I need to create a service that meets the requirements of a specific interface which calls the operations in this way. Maybe I could have an intermediary in between that will map these requests to something more RESTful and closer to common practice. – Isabi May 21 '14 at 05:50
2

I think Claudio is correct - you could use Jersey, but you'd be on your own to handle the query parameters since it only matches on the path.

You could inject a UriInfo and pull the query parameters out of that:

@Path("/operation") 
public class Operation {

    @Context
    protected UriInfo info;

    @GET 
    public String operation() {
        if (info.getQueryParameters().containsKey("create"))
            // do stuff
        else if (info.getQueryParameters().containsKey("info"))
            // other stuff
    } 

}

You could switch from Jersey to another framework. I believe that Spring can route to multiple methods based on query parameters.

As you mentioned, perhaps you could write something that is a bit more standard and then remap the requests to that. For example, you could use a Servlet filter or a front end server like Apache httpd or nginx to modify the request.

Thinking in terms of resources what is it that these operations are acting on? Customer accounts, movies, stock trades, etc. For arguments sake, let's say it's "Foo". You might go with something like this:

@Path("/foo") 
public class FooResource {

    @Context
    protected UriInfo info;

    @GET
    @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    public Foo getById(@QueryParam("id") int id) {
         // get Foo by id
         Foo = ....

         // return an instance of Foo and let Jersey convert it to XML
         // or JSON depending on the "Accept" header that the client sent
         return foo;
    } 

    @POST
    @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    public Response create(Foo instance)
    {
         // let Jersey deserialize the request body from JSON or XML.
         // save the instance however you want
         int id = fooService.save(instance);

         // return a 204 "created" with a "Location" header
         URI location = info.getAbsolutePathBuilder().path("{id}").build(id);
     return Response.created(location).build();
    }

}

It sounds like your URI structure is mandated by someone else so this might not be an option for you. If you do proceed with the current URI structure, there's one major pitfall that you should be aware of.

According to the HTTP 1.1 spec, GET requests should be idempotent. It appears that your current design is creating new server side instances with GET requests. There's potential for breakage... intermediate proxies or web browsers could cache responses to your GET requests and prevent new instances from being created.

Community
  • 1
  • 1
John R
  • 2,066
  • 1
  • 11
  • 17
  • Thanks again, John. It really seems that this is all there is as an answer to my question. I'll see how I can get around this, but I first and foremost have to accept the calls from the given client and try to handle it the best I can. – Isabi May 23 '14 at 15:39