I've used a regular expression in #Path to achieve overloading and at first I thought it was really neat, but overloading methods is usually not good practice. Does the same apply to RESTful web services? Is there a better way to achieve this using JAX-RS?
So I can now call my getProject REST service by /project/ProjectNumber1000 or /project/12345
#Path("/project")
public class ProjectPropertiesResource
{
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path("/{name : [a-zA-Z]+}")
public Response getProjectPropertiesByName(#PathParam("name") String name)
{
...
}
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path("/{id : \\d+}")
public Response getProjectPropertiesById(#PathParam("id") long id)
{
...
}
}
You can do it, however, only one of the overloads should actually return response body with a 200. The other overloads should return a 303 redirect to the URI that returns the body.
This will ensure that caches only have one copy of the resource and if you do PUT or POST on the main URI you will invalidate the one copy. Otherwise, you can start to get inconsistent results due to different versions existing in the cache.
Related
I'm working with Dropwizard, which uses Jersey internally. I have two methods on a controller:
PUT /garbage/[id1,id2,...idN] is intended to take a path parameter that's a list of numeric IDs representing resources to be updated. I'm using a regex-based PathParam here. I've fudged the regex in this example because I don't think it matters, but the point is that a single numeric ID should match the regex.
GET /garbage/[id] fetches data about a single piece of garbage.
Jersey seems to get confused, despite the difference in method. When I query with something like
curl localhost:8080/garbage/1
Jersey gives me a 405 error. If I take the PUT out of the picture (for example, sabotage the path param regex, or remove it entirely), the GET endpoint works fine.
I assume there is some detail in JAX-RS 3.7.2 I'm missing that explains why this should be the case, but I can't figure out what it is.
Here's the code:
#Path("/garbage")
#Produces(MediaType.APPLICATION_JSON)
public class GarbageController {
private static final Logger LOG = LoggerFactory.getLogger(GarbageController.class);
#PUT
#Path("/{params: [\\d,]+}")
#Consumes(MediaType.APPLICATION_JSON)
#Timed
public Response updateGarbage(#PathParam("params") List<PathSegment> params) {
LOG.warn("updateGarbage");
return Response.status(Response.Status.OK).build();
}
#GET
#Path("/{garbageId}")
public Response getGarbageById(#PathParam("garbageId") long garbageId) {
LOG.warn("getGarbage");
return Response.status(Response.Status.OK).build();
}
}
The main purpose of #PathSegment is to handle fragments of the URI which is useful to retrieve Matrix Parameters. For example the method below:
#GET
#Path("/book/{id}")
public String getBook(#PathParam("id") PathSegment id) {...}
Should be able to handle this request:
GET /book;name=EJB 3.0;author=Bill Burke
Because the #PathSegment intercepts the entire URL fragment the GET method seems to be ignored. You can handle the comma-separated IDs on the PUT request with a simple String split:
#PUT
#Path("/{params}")
#Consumes(MediaType.APPLICATION_JSON)
#Timed
public Response updateGarbage(#PathParam("params") String params) {
LOG.warn("updateGarbage ", params.split(","));
return Response.status(Response.Status.OK).build();
}
You can also change the request format to query parameters or implement a Converter/Provider to handle a custom object. All of them should solve the GET not implemented issue.
I believe this is not a case of route priorities between GET and PUT but instead this is related to the #Consumes annotation which cannot be used on a GET request. Either DW is ignoring this endpoint or is converting it into the default POST method, which would explain the 405 response for the GET request.
I figured this out, although I have not traced far enough into Jersey to know why it works. The solution is to rewrite the #GET method to use the same regex syntax as the #PUT. Jersey will handle the type conversion in the method signature, with the note that it will return a 404 if the type conversion fails (ie, GET /garbage/xyz).
#PUT
#Path("/{params: .+}")
#Consumes(MediaType.APPLICATION_JSON)
public Response updateGarbage(#PathParam("params") List<PathSegment> params) {
LOG.warn("updateGarbage");
return Response.status(Response.Status.OK).build();
}
#GET
#Path("/{params: .+}")
public Response getGarbageById(#PathParam("params") long garbageId) {
LOG.warn("getGarbage {}", garbageId);
return Response.status(Response.Status.OK).build();
}
I'm new in jersey rest service and I want to understand in this example the utility of adding #Consumes annotation to a delete method in this case this is the code it's work well (in a video ), is the #Consumes annotation optional here ? Thanks in advance
#path("activities")
public class ActivityResource {
#DELETE
#Path("{activityId}")
#Consumes(MediaType.APPLICATION_JSON)
#Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML})
public Response delete(#PathParam("activityId")String activityId) {
activityRepository.delete(activityId);
return Response.ok().build() ;
}
}
Is the #Consumes annotation optional here ?
Yes, I would even say that it is not needed as you have only one parameter and it is a PathParam which means that it will be extracted from the path.
The annotation #Consumes is used to indicate the JAX-RS implementation how to dynamically parse/deserialize the body of your request in order to have it as parameter in a more convenient type.
For example:
#POST
#Consumes("application/xml")
public void registerUser(User user) {
...
}
In this example, we indicate that the body of the request is of type application/xml, the JAX-RS implementation will then parse the body's content as an XML to finally get an instance of User.
NB: The HTTP method used has no effect on whether or not #Consumes is needed, only the need to parse the body matter.
A DELETE should not be interested in anything that is in the request body. It should only identify the resource to be deleted based on the URI.
Remove the #Consumes, it is wrong here.
Also think about returning a HTTP status 204 No Content instead of 200 OK. After deleting a resource, there is nothing to return. You should also remove the #Produces because of this.
I am developing a REST web server with Java and Jersey. This is my first web application and I want to be sure that I am structuring the application well. I created my first function which is working well:
#Path("/startAuto")
public class Rest {
#GET
public String startAuto() {
try {
ProcessBuilder pb = new ProcessBuilder("/startAuto.sh");
Process p = pb.start();
p.waitFor();
return ("Auto Started");
} catch (Exception e) {
e.printStackTrace();
return ("error");
}
}
}
I want to add a new function like stopAuto.
Which is cleaner: adding the function in this class or create a new class?
Approach 1
The GET method should be used for retrieving information. Don't use GET to change a state of a resource. Prefer POST instead.
So, you'll have something as following:
Start the process:
POST /auto/start HTTP/1.1
Host: example.org
Stop the process:
POST /auto/stop HTTP/1.1
Host: example.org
With this approach, you will have the following in your resource class:
#Path("/auto")
public class Rest {
#POST
#Path("/start")
public String start() {
...
}
#POST
#Path("/stop")
public String stop() {
...
}
}
Approach 2
REST is protocol independent and is a resource-oriented architecture. When implementing REST applications over the HTTP protocol, for example, the resource is identified by the URI and the operation over the resource is expressed by the HTTP method.
With this approach, the new state of the resource will be expressed in the request payload using JSON, for example. To obtain the state of a resource, use GET and to replace the state of a resource, use PUT.
You could have the following:
Start the process:
PUT /auto/status HTTP/1.1
Host: example.org
Content-Type: application/json
{
"value" : "started"
}
Stop the process:
PUT /auto/status HTTP/1.1
Host: example.org
Content-Type: application/json
{
"value" : "stopped"
}
Get the status of the process:
GET /auto/status HTTP/1.1
Host: example.org
Your resource class will be like:
#Path("/auto/status")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public class Rest {
#PUT
public String changeStatus(Status status) {
...
}
#GET
public Status getStatus() {
...
}
}
And that's what the Status class will look like:
public class Status {
private String value;
// Default constructor, getters and setters omitted
}
Response status codes
You certainly will need to inform your client about the result of the operation. To do it, use the HTTP response status codes.
A few status that might be useful:
200: Use this status to indicate that the request has succeeded.
202: Use this status code to indicate the request has been accepted for processing, but the processing has not been completed.
204: Use this status code to indicate the server has successfully fulfilled the request and that there is no additional content to send in the response payload body.
409: Use this indicates that the request could not be completed due to a conflict with the current state of the target resource.
As peeskillet points out in his comment, it depends on how you want to structure your URLS
If you wanted something like
/auto/start and
/auto/stop
I would go for all in one class structured as follows
#Path("/auto")
public class Rest {
#GET
#Path("/start")
public String startAuto() {
}
#GET
#Path("/stop")
public String stopAuto() {
}
}
Good structure is essential to any good project/product, however it is also a question whose answers vary from situation to situation.
However, if in doubt, a good starting point is to group your endpoints as per "responsibilities". If they belong together, then put them in the same class.
Personal Opinion : Boundary-Control-Entity is the simplest starting point that I could find for starting a project. The structure then adapts as per need. Take a look at this article from Adam Bien for more ideas.
Generally it is better to put another function related with the current resource into the same class.
But you should also remember that REST operates with resources but not with functions (nouns but not verbs). So probably it makes sense to make your API more RESTfull:
You target resource this is an auto. So it could be accessible by the URL "/auto"
Functions "start" and "stop" is used to change the state of resource. So it means that you have a "sub-resource" of the auto that could be accessed by URL "/auto/state" with possible values, for instance "started", "stopped". This values is accessible by GET:/auto/state
Now it is possible to change the state with a REST style using PUT/POST request with a status value in the body (also it is possible to use PATCH method in order to partially update auto). Probably, at your case, it makes sense to leave only one method exposed as an endpoint which consumes status value and call logic for starting or stopping the auto in accordance with parameter.
There seems to be many examples about creating RESTful clients by Jersey 1.x, but not Jersey 2.0 or above.
I referred to other questions and the Jersey's web site, but I still cannot create a client for REST due to the differences between Jersey 2.0 and the previous one.
So I'd like to ask some advice.
So far, my coding is like this.
ClientConfig config = new ClientConfig();
Client client = ClientBuilder.newClient(config);
WebTarget target = client.target("http://localhost:8080/CustomerBack2211/webresources/entities.customer");
Invocation.Builder invocationBuilder = target.request(MediaType.TEXT_XML_TYPE);
Response response = invocationBuilder.get();
System.out.println(response.getStatus());
System.out.println(response.readEntity(String.class));
This produces 406 error.
However, when I tried to test RESTful service by Glassfish server, the test works properly, and the server side class has its #GET methods having #Produces({"application/xml", "application/json"}).
So I don't see why the coding above produces 406 error on a Java application.
(i.e. the client side has #GET methods in the following way)
#GET
#Path("{id}")
#Produces({"application/xml", "application/json"})
public Customer find(#PathParam("id") Integer id) {
return super.find(id);
}
#GET
#Override
#Produces({ "application/xml"})
public List<Customer> findAll() {
return super.findAll();
}
Does any of you see what I'm doing wrong, or could you please suggest an example of a RESTful client?
Any advice will be helpful...thanks in advance!
In addition, I'd appreciate if you would offer information about how to invoke methods like GET, PUT and DELETE with appropriate parameters.
I just needed to put an ID number (i.e. integer values) when I was testing the server side class on Glassfish RESTful test. However, it seems that I need to set "Class" and/or "Entity" values as arguments, but I cannot see any information associated with them on the Jersey website.
For the first block of code:
406 means Not Acceptable.
Look at your request() method target.request(MediaType.TEXT_XML_TYPE). From the Javadoc of request() if states
Invocation.Builder request(MediaType... acceptedResponseTypes)
Start building a request to the targeted web resource and define the accepted response media types.
Invoking this method is identical to:
webTarget.request().accept(types);
So basically, in your request, you are saying that you will only Accept: text/plain. Now look at your resource methods. Look at the #Produces. None of them "produce" text/plain. It's all json or xml. That's why you get the exception. Change the accept to application/xml (or MediaType.APPLICATION_XML) on the client side, and you should no longer get this error.
For the second question: I'm assuming you mean why does it work when you test it from the browser.
If you send a request from the browser by simply typing in the url, it will send out the request with many Accept types. If you have firebug (for FireFox) or the developer tools (for Chrome), if you send out a request, you will see a header similar to
Accept text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
You can see application/xml in there. Even if application/xml wasn't there, the wild card */* is there, so basically almost all media types are acceptable as a return type when working in the browser.
For your last question:
Look at the API for SyncInvoker, which Invocation.Builder extends from. You will see different overrloaded put and post methods, most of which, as you mentioned accept an Entity.
There are a few different ways to build an Entity, all of which use one of the static methods. Here are some
Entity.entity( body, mediaType )
Entity.json( body )
Entity.xml( body )
And many more (see the Entity link above). But all of these static method return an Entity. So we could do something like
// resource method
#POST
#Consumes(MediaType.APPLICATION_XML)
public Response getResponse(Customer customer) { ... }
// some model class
#XmlRootElement
public class Customer { ... }
// client request
Customer customer = new Customer();
Response response = target.request().post(Entity.xml(customer));
Internally, the Customer will get converted to XML. If you used Entity.json is would get converted to JSON, BUT you need to make sure you have a JSON provider dependency. Jersey will not come with one by default. See more at Support for Common Media Type Representations
Also note, with your method find, when you try and make a request to the method, the request should end with an integer value, as that's the type specified for the {id} path parameter.
I am using Jersey Restful webservices. I have below web method to get the results.
#Path("/persons")
public class PersonWS {
private final static Logger logger = LoggerFactory.getLogger(PersonWS.class);
#Autowired
private PersonService personService;
#GET
#Path("/{id}")
#Produces({MediaType.APPLICATION_XML})
public Person fetchPerson(#PathParam("id") Integer id) {
return personService.fetchPerson(id);
}
#DELETE
#Path("/{id}")
public void deletePerson(#PathParam("id") Integer id) {
return personService.deletePerson(id);
}
}
In above Jersey RESTful webservice, i have two web methods one for get and one more for delete with same number of parameters. In above case will there be any ambiguity? If not what should be the URIs for both of the methods? Thanks!
Thanks!
Jersey decides which method to call based on the HTTP method specified in the request. If you use multiple methods with the same HTTP method like GET, then the choice is made by more Annotations like Consumes or Produces etc.
BTW: If you use the URI /persons/{id} for all endpoints, then you can annotate your class with #Path("/persons/{id}") instead of annotating every method with this sub-URI.
There is no ambiguity as the HTTP Method is different (GET vs DELETE).
The same url would also be used to update the object, using the HTTP method PUT
No ambiguity since the HTTP methods used are different i.e GET and DELETE
And the urls will be same as the param required is "id" for both
IN Jersey client program use GET http method for fetching person info, Use DELETE http method for deleting the person.