I have a messages resource:
#Path("/messages")
#Consumes("application/json")
#Produces("application/json")
public class MessageResource {
...
#Path("/{msgId}/comments")
public CommentsResource getCommentsResource() {
return new CommentsResource();
}
Now i want to add a new comment for a particular messageId,
So here is the comments resource:
#Path("/")
#Produces("application/json")
#Consumes("application/json")
public class CommentsResource {
#POST
public Comment addNewComment(Comment newComment, #PathParam("msgId") long messageId) {
commentService.addComment(messageId, newComment);
return newComment;
}
and here is commentService.addComment:
public Comment addComment(long msgId, Comment newComment) {
Map<Long, Comment> allCommentsOfAMessage = messages.get(msgId).getComments();
newComment.setId(allCommentsOfAMessage.size() + 1);
allCommentsOfAMessage.put(newComment.getId(), newComment);
return newComment;
}
But problem is that the addNewComment() method of the CommentsResource class never been called with some URL like: http://localhost:8080/messages/2/comments
Including a new comment in Json format by POST method.
(This URL should add a new comment to message with Id=2)
The Jersey documentation says that you can do this in a way such as the root resource is /messages/comments/{msgId}. It probably cannot propagate the pathparam if you catch it in the root resource and not in the sub-resource.
Maybe you should just have them in the same class or have comments as a root resource like POST /comments/{msgId}.
Related
I have these 2 resources
#Path("/orders")
public class OrderResource {
#GET
#Path("/{id}")
#Produces(MediaType.APPLICATION_JSON)
public Response getOrder(#PathParam("id") String orderid)
throws JSONException {
Order order = db.getOrder(orderid);
return Response.status(Status.OK).entity(order).build();
}
#GET
#Path("/{orderid}/products")
public ProductResource getProducts() {
return new ProductResource();
}
}
#Path("/")
public class ProductResource {
#GET
#Path("/{productid}")
#Produces(MediaType.APPLICATION_JSON)
public Response getProduct(#PathParam("orderid") String orderid, #PathParam("productid") String productid) throws JSONException {
Product product = db.getProduct(productid);
return Response.status(Status.OK).entity(product).build();
}
}
I get a successful output when I do this:
http://localhost:8080/testApp/api/orders/O101
I can see the collection of the products linked to the order in the output so I copied the id and tried this
http://localhost:8080/testApp/api/orders/O101/products/P101
But I always get a 404 error. Why? How can I solve this?
This is my config in the web.xml
<servlet-mapping>
<servlet-name>TestApp</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
EDIT
Thank you so much for your answers. Woke up this morning tired to test it with no success.
I tried your suggestions, but still get 404.
#Path("/orders")
public class OrderResource {
#GET
#Path("/{id}")
#Produces(MediaType.APPLICATION_JSON)
public Response getOrder(#PathParam("id") String orderid)
throws JSONException {
Order order = db.getOrder(orderid);
return Response.status(Status.OK).entity(order).build();
}
#GET
#Path("/{orderid}/products") //Here I added after products /{productID} which gives me an empty JSON. Never reach the method from the subresource.
public ProductResource getProducts() {
return new ProductResource();
}
}
public class ProductResource {
#Path("/{productid}") //Here I tried to remove the slash also.
#Produces(MediaType.APPLICATION_JSON)
public Response getProduct(#PathParam("orderid") String orderid, #PathParam("productid") String productid) throws JSONException {
Product product = db.getProduct(productid);
return Response.status(Status.OK).entity(product).build();
}
}
The problem is the #GET on the getProducts. A sub-resource locator is defined as a method with a #Path and which has no #METHOD. If you think about it, it makes perfect sense, as the there can be more than say just a #GET in the sub-resource class. So remove the #GET, and it should work. Leaving it would cause the method to not be a sub-resource locator, and it would behave like a normal resource method.
Aside from that, what others have suggested about the #Path("/") is not the cause of the problem, but it is a problem. What this does is cause Jersey to also register the ProductsResource as a root resource. So would be able to access /api/1234, since it is mapped to /. You probably don't want this. So you should remove the #Path("/") from the ProductsResource.
Sub-resources shouldn't be annotated with #Path on class level and they need to be registered with the JAX-RS runtinme.
Just remove the #Path annotation.
In your case, the problem seems to be the annotation #Path in your sub-resource. When defining a sub-resource, it should not be annotated at the class level with #Path. Also in your ProductResource, try removing the '/' from #Path("/{productid}") as it should be referenced from the context of the parent(OrderResource) and should not exists as an individual instance.
Thanks
Scenario-1 : During my work I encountered below scenario, On which : getText1, getText2,getText3,getText4,getText5,getText6 are without #Path annotations,
But when I call the API (http://localhost:8080/.../testqa/ )it always returns following result :
{
"name" : "Sumit1 Arora",
"age" : 21,
"address" : "Lakshay1 Arora"
}
SimpleQAImpl
#Service("qaservice")
#Path("/testqa")
public class SimpleQAImpl {
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path("/simpleqa")
public Person getText() {
return new Person("Sumit Arora",21,"Lakshay Arora");
}
#GET
#Produces(MediaType.APPLICATION_JSON)
public Person getText1() {
return new Person("Sumit1 Arora",21,"Lakshay1 Arora");
}
#GET
#Produces(MediaType.APPLICATION_JSON)
public Person getText3() {
return new Person("Sumit3 Arora",21,"Lakshay3 Arora");
}
#GET
#Produces(MediaType.APPLICATION_JSON)
public Person getText4() {
return new Person("Sumit4 Arora",21,"Lakshay4 Arora");
}
#GET
#Produces(MediaType.APPLICATION_JSON)
public Person getText5() {
return new Person("Sumit5 Arora",21,"Lakshay5 Arora");
}
#GET
#Produces(MediaType.APPLICATION_JSON)
public Person getText6() {
return new Person("Sumit6 Arora",21,"Lakshay6 Arora");
}
}
May you please tell me how Apache CXF works, if #Path not given like the case above or on other scenarios as well?
Is there any reference to understand such stuff?
Scenario-2 : On this scenario, No #Path variable defined on top of API Call, how all of these API would be called from URI ?
#Service
#Path("/customer")
public class CustomerResource {
private final Logger logger = LoggerFactory.getLogger(CustomerResource.class);
#Autowired
private CustomerService customerService;
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Response create(Customer customer) {
if(customerService.createCustomer(customer).isPresent()) {
return Response.ok().build();
} else
return Response.status(Response.Status.BAD_REQUEST).entity(new Error(1,"test")).build();
}
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response getAll() {
logger.debug("Received request to fetch all the customers.");
List<Customer> customers = customerService.fetchAll();
GenericEntity<List<Customer>> customerEntities = new GenericEntity<List<Customer>>(customers) {};
return Response.ok(customerEntities).build();
}
#PUT
#Consumes(MediaType.APPLICATION_JSON)
public Response update(Customer customer) {
return Response.status(Response.Status.NO_CONTENT).build();
}
}
The documentation for how CXF selects which method is executed is here: CXF resource selection overview. The docs talks about which method it prefers by looking at which has more path parameters or more a more specific path but each method in your first scenario has the same path so the first one is chosen. To differentiate between them you could use a path parameter.
The Second scenario requires you to change the HTTP method used with the URL so:
POST /customer
GET /customer
PUT /customer
would each invoke the different methods.
I want to authorize calls made to my rest api differently depending on which method is being called. But the RequestHandler looks like this:
public interface RequestHandler {
Response handleRequest(Message m,
ClassResourceInfo resourceClass);
}
I can't figure out how to get the Method that will be called from that resourceClass. Is this possible?
The ResponseHandler seems to have a parameter that can do this named OperationResourceInfo:
public interface ResponseHandler {
Response handleResponse(Message m,
OperationResourceInfo ori,
Response response);
}
But by that time, I will have already deleted something I had no permission to delete (as an example).
How do I figure out what method will be called in a request filter? FWIW, the reason I want the Method is because I want to search for a custom built annotation I will put on each method. If there is a better way to approach this, I'm open to the idea.
For completeness, here's the documentation on the topic: http://cxf.apache.org/docs/jax-rs-filters.html
You can use Interceptors, rather than RequestHandler filters as the request handlers are deprecated and replaced in JAXRS 2.0 with ContainerRequestFilter and ContainerResponseFilter
For Example
Say I've RestService shown below
#Service
#Path("/Course")
public class KPRestService {
private final Logger LOG = LoggerFactory.getLogger(KPRestService.class);
#POST
#Path("/create")
#Consumes(MediaType.APPLICATION_JSON)
public Response create(CourseType course){
LOG.info("You have selected {}", course.getCName());
return Response.ok().build();
}
#POST
#Path("/get")
#Produces(MediaType.APPLICATION_JSON)
public CourseType get(#FormParam("cDate")Date date){
final CourseType course = new CourseType();
if(date.after(new Date())){
course.setCName("E&C");
course.setCDuration(4);
}else{
course.setCName("Mech");
course.setCDuration(3);
}
return course;
}
}
I prevent calling the get method using interceptor as shown below.
#Component
public class KPFilter extends AbstractPhaseInterceptor<Message> {
private final static Logger LOG = LoggerFactory.getLogger(KPFilter.class);
public KPFilter() {
super(Phase.PRE_LOGICAL);
}
public void handleMessage(Message message) throws Fault {
final Exchange exchange = message.getExchange();
exchange.put(Message.REST_MESSAGE, Boolean.TRUE);
OperationResourceInfo resourceInfo = exchange.get(OperationResourceInfo.class);
LOG.info("Method name is {}", resourceInfo.getMethodToInvoke().getName());
if (resourceInfo != null && resourceInfo.getMethodToInvoke().getName().equals("get")) {
Response response = Response.status(Response.Status.FORBIDDEN).entity("You are not authorised")
.type(MediaType.TEXT_XML).build();
exchange.put(Response.class, response);
}
}
}
Is it possible to retrieve http headers inside JAXRS resource method without explicitly specifying these headers as method parameters?
For example I have a following interface:
#Path("/posts")
public interface PostsResource {
#GET
public List<Post> getAllPosts();
}
and the following class that implements this interface:
public class PostsResourceImpl implements PostsResource {
#Autowired
private PostsService postsService;
public List<Post> getAllPosts() {
return postsService.getAllPosts();
}
}
I don't want to change my method signature to:
public List<Post> getAllPosts(#HeaderParam("X-MyCustomHeader") String myCustomHeader);
This header will be added by interceptor on the client side so the client code doesn't know what to put here and this should not be explicit method parameter.
You can inject an object of type HttpHeaders within your resource as class variable to have access to request headers, as described below:
#Path("/test")
public class TestService {
#Context
private HttpHeaders headers;
#GET
#Path("/{pathParameter}")
public Response testMethod() {
(...)
List<String> customHeaderValues = headers.getRequestHeader("X-MyCustomHeader");
System.out.println(">> X-MyCustomHeader = " + customHeaderValues);
(...)
String response = (...)
return Response.status(200).entity(response).build();
}
}
Hope it answers your question.
Thierry
I have the following code:
#Path("/users/{id}")
public class UserResource {
#Autowired
private UserDao userDao;
#GET
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public User getUser(#PathParam("id") int id) {
User user = userDao.getUserById(id);
if (user == null) {
throw new NotFoundException();
}
return user;
}
If I request for a user that doesn't exists, like /users/1234, with "Accept: application/json", this code returns an HTTP 404 response like one would expect, but returns Content-Type sets to text/html and a body message of html. Annotation #Produces is ignored.
Is it a problem of code or a problem of configuration?
Your #Produces annotation is ignored because uncaught exceptions are processed by the jax-rs runtime using a predefined (default) ExceptionMapper If you want to customize the returned message in case of a specific exception you can create your own ExceptionMapper to handle it. In your case you need one to handle the NotFoundException exception and query the "accept" header for the requested type of the response:
#Provider
public class NotFoundExceptionHandler implements ExceptionMapper<NotFoundException>{
#Context
private HttpHeaders headers;
public Response toResponse(NotFoundException ex){
return Response.status(404).entity(yourMessage).type( getAcceptType()).build();
}
private String getAcceptType(){
List<MediaType> accepts = headers.getAcceptableMediaTypes();
if (accepts!=null && accepts.size() > 0) {
//choose one
}else {
//return a default one like Application/json
}
}
}
You can use the Response return. Example below:
#GET
#Path("{id}")
#Produces(MediaType.APPLICATION_JSON)
public Response get(#PathParam("id") Long id) {
ExampleEntity exampleEntity = getExampleEntityById(id);
if (exampleEntity != null) {
return Response.ok(exampleEntity).build();
}
return Response.status(Status.NOT_FOUND).build();
}
that 404 is returned by your server as it is expected that you will pass things in following form
/users/{id}
but you are passing it as
/users/user/{id}
which resource is not existing at all
try accessing resource as /users/1234
EDIT:
create a class like
class RestResponse<T>{
private String status;
private String message;
private List<T> objectList;
//gettrs and setters
}
now in case you want response for User you can create it as following
RestResponse<User> resp = new RestResponse<User>();
resp.setStatus("400");
resp.setMessage("User does not exist");
and signature of your rest method would be like following
public RestResponse<User> getUser(#PathParam("id") int id)
while in case successful response you can set things like
RestResponse<User> resp = new RestResponse<User>();
List<User> userList = new ArrayList<User>();
userList.add(user);//the user object you want to return
resp.setStatus("200");
resp.setMessage("User exist");
resp.setObjectList(userList);