I am new to REST API currently i am working on a project where I have 2 resources:
Project
Client
Now for this did I need to create 2 resource class as given below or a single resource class.
#Path("/v1/projects")
public interface ProjectResource {
#POST
public Respone add(Project project)
#DELETE
public Respone delete(Project project)
#PUT
public Respone update(Project project)
}
#Path("/v1/projects/{projectId}/client")
public interface ClientResource {
#POST
public Respone add(Client client)
#DELETE
public Respone delete(Client client)
#PUT
public Respone update(Client client)
}
Or a single resource class with all methods
#Path("/v1/projects")
public interface ProjectResource {
#POST
public Respone add(Project project)
#DELETE
public Respone delete(Project project)
#PUT
public Respone update(Project project)
#Path("/{projectId}/client")
#POST
public Respone add(Client client)
#Path("/{projectId}/client")
#DELETE
public Respone delete(Client client)
#Path("/{projectId}/client")
#PUT
public Respone update(Client client)
}
It's up to you, but taking SRP into consideration it's better to split the implementation into two classes. Remember that classes should be atomic and focus only on delivering single piece of functionality.
Related
I noticed an anomaly in the way Spring Data Rest repositories are behaving. I have two types of entities. in my application - readonly entities (for reference data like statecodes, country codes, zip codes etc.). I don't want to let the end user change these. So I implemented the following ReadOnly repository.
#NoRepositoryBean
public interface ReadOnlyRepository<T, ID extends Serializable> extends Repository<T, ID> {
T findOne(ID id);
Iterable<T> findAll();
}
#Repository
public interface StateRepository extends ReadOnlyRepository<State, Long> {
}
Now, all other entities have CrudRepositories associated with them because they are editable entities like addresses which reference the states and zip codes.
Here's an example.
#Repository
public interface CustomerRepository extends CrudRepository<Address, Long> {
}
I have a controller for both readonly and editable entities, with a pass-through call to the repositories.
#RestController
#RequestMapping(value = "/addresses", produces = MediaType.APPLICATION_JSON_VALUE)
public class AddressController {
#Autowired
private AddressRepository addressRepository;
#RequestMapping(method = RequestMethod.GET)
#ResponseStatus(HttpStatus.OK)
public Iterable<Address> getAllAddresses() {
return addressRepository.findAll();
}
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
#ResponseStatus(HttpStatus.OK)
public Address getAddress(#PathVariable("id") Long id) {
return addressRepository.findOne(id);
}
}
I have an identical Controller corresponding to the State entity.
Funnily enough, the request to StateController gives me a HATEOAS json response, while the request to Address gives me a non HATEOAS json response. What gives?
My bad. My application server did not redeploy certain repositories. This is a non-issue.
So for those running into these issues, you are likely using hot-code replace feature of your IDE. Consider restarting your app and it should be a non-issue.
We would usually define POST and PUT verbs as different service APIs.
#POST
#Path("/getbook")
#Produces({"application/xml","application/json"})
#Consumes({"application/xml","application/json","application/x-www-form-urlencoded"})
public Response getBucket() {
... }
#PUT
#Path("/getbook/{name}")
#Produces({"application/xml","application/json"})
#Consumes({"application/xml","application/json","application/x-www-form-urlencoded"})
public Response getBucket(#PathParam("name") String name) {
... }
Would there be a way to combine these verbs into a single method - and then drive different logic based on the type of the verb ?
Hypothetically
#POST
#PUT
#Path("/getbook/{name}")
#Produces({"application/xml","application/json"})
#Consumes({"application/xml","application/json","application/x-www-form-urlencoded"})
public Response getBucket(#PathParam("name") String name) {
if(verb=POST){
... }
else{
}
}
You may try like this using MessageContext. You need the context injected into the service method like below for updateCustomer method and then you can check for the method type as you like (here I am checking for PUT):
#Path("/customer")
public class CustomerService {
#Context
private org.apache.cxf.jaxrs.ext.MessageContext mc;
#PUT
public Response updateCustomer(#Context MessageContext context, Customer c) {
HttpServletRequest request = context.getHttpServletRequest();
boolean isPut = "PUT".equals(request.getMethod());
}
}
There is a RestEasy method, which handles #GET requests. How is it possible to open a jsp/html page from that method?
#GET
#Path("/")
public void getMainPage(){
//...
}
HtmlEasy is a great tool to render jsp files through RestEasy.
#Path("/")
public class Welcome {
#GET #Path("/welcome/{name}")
public View sayHi(#PathParm("name") String name) {
return new View("/welcome.jsp", name);
}
}
See documents for all options.
Using org.jboss.resteasy.resteasy-html version 3.0.6.Final you can directly access the HttpServletRequest and inject your own attributes before directing output to a RESTEasy View.
#GET
#Path("{eventid}")
#Produces("text/html")
public View getEvent(#Context HttpServletResponse response,
#Context HttpServletRequest request,
#PathParam("eventid") Long eventid){
EventDao eventdao = DaoFactory.getEventDao();
Event event = eventdao.find(eventid);
request.setAttribute("event", event);
return new View("eventView.jsp");
}
This emulates some behavior of the Htmleasy plugin without having to rewire your web.xml.
I have a restfull implementation using Jersey and Tomcat7. I have 3 resources called RegionService, ClientService and NoteService defined in my campher.rest package.
When I try to add another resource called TestResource, and Tomcat starts, it gives me the following error below. I don't understand how /{notes} conflicts with /{test}??
Please help, my hair will thank you.
Aug 22, 2012 2:23:39 AM com.sun.jersey.api.core.ScanningResourceConfig logClasses
INFO: Root resource classes found:
class campher.rest.NoteService
class campher.rest.ClientService
class campher.rest.TestResource
class campher.rest.RegionService
Aug 22, 2012 2:23:39 AM com.sun.jersey.api.core.ScanningResourceConfig init
INFO: No provider classes found.
Aug 22, 2012 2:23:40 AM com.sun.jersey.server.impl.application.WebApplicationImpl _initiate
INFO: Initiating Jersey application, version 'Jersey: 1.12 02/15/2012 04:51 PM'
Aug 22, 2012 2:23:40 AM com.sun.jersey.spi.inject.Errors processErrorMessages
SEVERE: The following errors and warnings have been detected with resource and/or provider classes:
SEVERE: Conflicting URI templates. The URI template /{test} for root resource class campher.rest.TestResource and the URI template /{notes} transform to the same regular expression /([^/]+?)(/.*)?
Aug 22, 2012 2:23:40 AM org.apache.catalina.core.ApplicationContext log
SEVERE: StandardWrapper.Throwable
Here are the skeleton implementations of those 4 services.
package campher.rest;
#Path("regions")
public class RegionService {
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response regions() {}
#POST
#Consumes(MediaType.APPLICATION_JSON)
public Region addRegion(Region region){}
#PUT
#Consumes(MediaType.APPLICATION_JSON)
public Region updateRegion(Region region){}
#GET #Path("{id}")
#Produces(MediaType.APPLICATION_JSON)
public Response getRegion(#PathParam("id") long id) {}
#DELETE #Path("{id}")
#Produces(MediaType.APPLICATION_JSON)
public Response deleteRegion(#PathParam("id") long id) {}
#GET #Path("{id}/clients")
#Produces(MediaType.APPLICATION_JSON)
public Response getClients(#PathParam("id") long id) {}
}
package campher.rest;
#Path("clients")
public class ClientService {
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response clients() {}
#GET #Path("{id}")
#Produces(MediaType.APPLICATION_JSON)
public Response getClient(#PathParam("id") long id) {}
#GET #Path("{id}/notes")
#Produces(MediaType.APPLICATION_JSON)
public Response getNotes(#PathParam("id") long id) {}
#GET #Path("{id}/alerts")
#Produces(MediaType.APPLICATION_JSON)
public Response getAlerts(#PathParam("id") long id) {}
#POST
#Consumes(MediaType.APPLICATION_JSON)
public Client addClient(Client client){}
#PUT
#Consumes(MediaType.APPLICATION_JSON)
public Client updateClient(Client client){}
#DELETE #Path("{id}")
#Consumes(MediaType.APPLICATION_JSON)
public Response deleteClient(#PathParam("id") long id){}
}
package campher.rest;
#Path("{notes}")
public class NoteService {
#GET #Path("{id}")
#Produces(MediaType.APPLICATION_JSON)
public Response getNote(#PathParam("id") long id) {}
#POST
#Consumes(MediaType.APPLICATION_JSON)
public Note addNote(Note note){}
#PUT
#Consumes(MediaType.APPLICATION_JSON)
public Note updateNote(Note note){}
#DELETE #Path("{id}")
#Produces(MediaType.APPLICATION_JSON)
public Response deleteNote(#PathParam("id") long id) {}
}
package campher.rest;
import javax.ws.rs.Path;
#Path("{test}")
public class TestResource {
}
#Path("test")
will match <web-root>/test
#Path("{test}")
will match <web-root>/foo and <web-root>/bar. The word test here is merely the path-param map key to associate foo and bar values.
Notice the presence and absence of {} around the names. They completely change the meaning of the expression. Their presence indicates that you want to extract that out and put it in an instance variable annotated with #PathParam("name-between-brackets").
Your #Path("{test}") and #Path("{notes}") both are essentially asking Jersey to look for root URLs of the form http://<host:port>/<webapp>/{capture-text} and copy the capture-text into test and notes path variables respectively. This is ambiguous.
I have worked on Web services using Jaxb earlier. I geneated Java from xsd, and then I used to post the xml request to the specified URL using HTTP post. Recently I heard about this Restful web services, on reading I felt that what I had been doing earlier is the restful web service only. But, I am not sure about it if its the same thing.
Can anyone explain please.
It sounds like you have been creating the same types of RESTful services. You may be referring to is JAX-RS with is a standard that defines an easier way of creating RESTful services where JAXB is the standard binding layer for the application/xml media type. Below is an example service:
package org.example;
import java.util.List;
import javax.ejb.*;
import javax.persistence.*;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
#Stateless
#LocalBean
#Path("/customers")
public class CustomerService {
#PersistenceContext(unitName="CustomerService",
type=PersistenceContextType.TRANSACTION)
EntityManager entityManager;
#POST
#Consumes(MediaType.APPLICATION_XML)
public void create(Customer customer) {
entityManager.persist(customer);
}
#GET
#Produces(MediaType.APPLICATION_XML)
#Path("{id}")
public Customer read(#PathParam("id") long id) {
return entityManager.find(Customer.class, id);
}
#PUT
#Consumes(MediaType.APPLICATION_XML)
public void update(Customer customer) {
entityManager.merge(customer);
}
#DELETE
#Path("{id}")
public void delete(#PathParam("id") long id) {
Customer customer = read(id);
if(null != customer) {
entityManager.remove(customer);
}
}
}
For More Information
http://blog.bdoughan.com/2010/08/creating-restful-web-service-part-45.html
When it comes to say 'RESTful', it's just an convention of HTTP methods and url patterns.
CRUD METHOD URL RESPONSE DESCRIPTION
----------------------------------------------------------------
CREATE POST http://www.doma.in/people 202 Creates a new person with given entity body
READ GET http://www.doma.in/people 200
READ GET http://www.doma.in/people/1 200 404 Reads a single person
UPDATE PUT http://www.doma.in/people/2 204 Updates a single person with given entity body
DELETE DELETE http://www.doma.in/people/1 204 Deletes a person mapped to given id(1)
You can even implement those kind of contracts with Sevlets. Actually I had done with Sevlets before the era of JAX-RS.
And your life will be much more easier when you use JAX-RS.
Here comes a slightly modified version of Mr. Blaise Doughan's.
Nothing's wrong with Mr. Blaise Doughan's code.
I just want to add more for above url patterns.
One of great things that JAX-RS can offer is that you can serve XMLs and JSONs as clients want if you have those fine JAXB classes. See #Producess and #Consumess for those two formats in same method.
When client want to receive as XML with Accept: application/xml, they just get the XML.
When client want to receive as JSON with Accept: application/json, they just get the JSON.
#Path("/customers");
public class CustomersResource {
/**
* Reads all person units.
*/
#POST
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Response read() {
final List<Customer> listed = customerBean.list();
final Customers wrapped = Customers.newInstance(listed);
return Response.ok(wrapped).build();
}
#POST
#Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Response createCustomer(final Customer customer) {
entityManager.persist(customer);
return Response.created("/" + customer.getId()).build();
}
#GET
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
#Path("/{id: \\d+}")
public Response read(#PathParam("id") final long id) {
final Customer customer = entityManager.find(Customer.class, id);
if (customer == null) {
return Response.status(Status.NOT_FOUND).build();
}
return Response.ok(customer).build();
}
#PUT
#Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public void updateCustomer(final Customer customer) {
entityManager.merge(customer);
}
#DELETE
#Path("/{id: \\d+}")
public void deleteCustomer(#PathParam("id") final long id) {
final Customer customer = entityManager.find(Customer.class, id);
if (customer != null) {
entityManager.remove(customer);
}
return Response.status(Status.NO_CONTENT).build();
}
}
Say you want to serve some images?
#GET
#Path("/{id: \\d+}")
#Produces({"image/png", "image/jpeg"})
public Response readImage(
#HeaderParam("Accept") String accept,
#PathParam("id") final long id,
#QueryParam("width") #DefaultValue("160") final int width,
#QueryParam("height") #DefaultValue("160") final int height) {
// get the image
// resize the image
// make a BufferedImage for accept(MIME type)
// rewrite it to an byte[]
return Response.ok(bytes).build();
// you can event send as a streaming outout
return Response.ok(new StreamingOutput(){...}).build();
}