I have the following resource:
#Path("")
public class ReviewsResource {
#Context
private UriInfo context;
#EJB
private ReviewsReadBeanRemote reviewReadServices;
#EJB
private ReviewsBeanRemote reviewServices;
public ReviewsResource() {
}
#GET
#AuthenticateUser
#Produces({MediaType.APPLICATION_JSON})
#Path("cadets/{id}/reviews")
public Response getApprovedReviewsByCadet(#PathParam("id") Long cadetId)
throws EnviosYaException {
return Response.ok(reviewReadServices.getReviewsByCadet(cadetId, State.APPROVED)).build();
}
#GET
#AuthenticateAdministrator
#Produces({MediaType.APPLICATION_JSON})
#Path("reviews")
public Response getReviews(#QueryParam("state") String state,
#QueryParam("start") Date start, #QueryParam("end") Date end)
throws EnviosYaException {
State stateValue = null;
if (state != null && !state.isEmpty()) {
stateValue = State.valueOf(state);
}
return Response.ok(reviewReadServices.getReviews(stateValue, start, end)).build();
}
#GET
#AuthenticateUser
#Produces({MediaType.APPLICATION_JSON})
#Path("clients/{id}/shipments")
public Response getShipmentsPendingReviewByClient(#PathParam("id") Long clientId)
throws EnviosYaException {
return Response.ok(reviewReadServices.getShipmentsPendingReviewsByClient(clientId)).build();
}
}
I can use the getReviews just fine like this:
https://localhost:8181/Gateway-war/reviews
But when I try to hit the others like this:
https://localhost:8181/Gateway-war/cadets/1/reviews
I get 404 Not found.
Is there something wrong with the path? Could it be because of the name of my resource?
I do have another resource that starts like this:
#Path("cadets")
public class CadetsResource {
Could the problem be it tries to look there?
Yes - the other path is interfering. In general I put all of my services in a "namespace" per class. So the ReviewsResource might instead look like:
#Path("/reviews")
public class ReviewsResource {
Then everything within that class has a unique part of the URL. You're already following that pattern in the CadetsResource class - I would recommend using it everywhere.
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
Right now, I have some endpoints in a resource. These endpoints access some data and return it:
#Path("/v1/event")
#Produces({MediaType.APPLICATION_JSON})
public class EventResource {
private final DataStore dataStore;
// constructor stuff
#Timed
#GET
#Path("/all/total")
public String getAll(#Bind({Bind.Params.QUERY}) Params params) throws Exception {
return dataStore.getEventTotals(params);
}
}
We completely revamped how our data is stored so now I have a resource that accesses this new data store:
#Path("/v2/event")
#Produces({MediaType.APPLICATION_JSON})
public class NewEventResource {
private final NewDataStore newDataStore;
// constructor stuff
#Timed
#GET
#Path("/all/total")
public MyDataPojo getAll(#Bind({Bind.Params.QUERY}) Params params) throws Exception {
return newDataStore.getEventTotals(params);
}
}
What I would like to do now is somehow have the v1 endpoint use both these resources. Some object would decide which getAll method to use based on some parameters in the Params object that is passed in.
The reason is we have some customers that have data in the old data store, and other customers have data in the new data store. We also have a bunch of other projects that are using our endpoints. It's not feasible or realistic to go change all the other projects to use the v2 endpoint instead of the v1 endpoint.
A couple thoughts. I could do something like this:
#Path("/v1/event")
#Produces({MediaType.APPLICATION_JSON})
public class EventResource {
private final DataStore dataStore;
private final NewDataStore newDataStore;
// constructor stuff
#Timed
#GET
#Path("/all/total")
public String getAllEndpoint(#Bind({Bind.Params.QUERY}) Params params) throws Exception {
if (customerInNewDataStore(params.getCustomer())) {
return getEventTotalsNew(params);
} else {
return getEventTotalsOld(params);
}
}
private MyDataPojo getEventTotalsNew(Params params) throws Exception {
return newDataStore.getEventTotals(params);
}
private String getEventTotalsOld(Params params) throws Exception {
return dataStore.getEventTotals(params);
}
}
The problem with this is that getEvenTotalsNew and getEventTotalsOld return different types. How would I be able to merge this? Also, doing this would be sort of a pain to do for every endpoint as there are quite a few endpoints in our codebase.
I've been reading about filters and intercepters in Jersey: https://jersey.java.net/documentation/latest/filters-and-interceptors.html.
Would a ContainerRequestFilter be able to accomplish what I want to do? Would I be able to access my Params params object in the filter?
Any other better ways to do this? I'm open to all ideas.
Thanks!
I think I might have what you are looking for. You can use a pre-matching filter to modify the request.
This is based on your example stating that you have a v1 and v2 API, both of which are equal (apart from the versioning). I am using a custom header for routing.
Consider these two resources:
#Path("v1")
#Produces(MediaType.APPLICATION_JSON)
public class TestResource1 {
#GET
#Path("test")
public String get() {
return "Hello v1";
}
}
#Path("v2")
#Produces(MediaType.APPLICATION_JSON)
public class TestResource2 {
#GET
#Path("test")
public MyResultObj get() {
MyResultObj o = new MyResultObj();
o.name = "pandaa";
o.message = "Hello V2";
return o;
}
public static class MyResultObj {
#JsonProperty
String name;
#JsonProperty
String message;
}
}
These are both equal with the exception for the version type in the context and the return type. Note, the return type does not matter in this case. What ends up in the response is a json string regardless. In your case, you could also do something like:
Response.ok(myResultObject).build();
At that point all of your return types would just be Response.
Anyhow, Version 1 prints something, and version 2 returns an object.
The pre-matching (important, otherwise you can not change the URI) filter will look like this:
#PreMatching
public class RoutingFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
String headerString = requestContext.getHeaderString("IS_V2");
boolean v2 = headerString != null && headerString.equals("yes");
URI absolutePath = requestContext.getUriInfo().getAbsolutePath();
if(v2) {
URI v2Redirect = URI.create(absolutePath.toString().replace("v1", "v2"));
requestContext.setRequestUri(v2Redirect);
}
}
}
It simply evaluates the header and replaces the version in the URI. There is probably a better way to do this, but then again this is just an example of how to approach this.
Finally, the test:
artur#pandaadb:~/dev/vpn$ curl "localhost:9085/api/v1/test" --header "IS_V2: yes"
{"name":"pandaa","message":"Hello V2"}
artur#pandaadb:~/dev/vpn$ curl "localhost:9085/api/v1/test" --header "IS_V2: no"
Hello v1
Note how both are doing a request for V1. The first request though gets rerouted internally to v2.
You can write a more generic version (since you might need to be backwards compatible e.g. v1 -> v2 and v2 -> v1) so that it doesn't matter if people call v1 or v2.
Finally - I am not at all sure if this is a good solution :) Personally I would probably write a delegate as seen in your example.
I hope that helps!
Edit: finally - you should be able to use your params object. However this may result in you consuming the requests's input stream. I believe this can only be done once, so you may need to set a new stream after reading it as well.
Artur
I have a problem with my REST Controller. If I sent a request with a RequestBody (json) some attributes doesn't arrive the controller, although they was sent and defined at model.
I could find out that it look like an old version of files will be used from the local java web server. As I changed the System.out.println Value at Constructor still the old value was outputed.
public RestController_ApiKey_2_0() {
System.out.println("RestController_ApiKey_2_0 init");
}
I tried the following things bootless:
deleted java web server and did a new installation
cleaned the project and started server again
clean install of project
Does anyone have an idea?
Please provide more code, how do you declare a controller, and what params it can take. Also show a sample request.
Here is an example of a simple controller:
A model
public class CustomRequestBody {
private String fieldA;
private String fieldB;
public String getFieldA() {
return fieldA;
}
public void setFieldA(final String fieldA) {
this.fieldA = fieldA;
}
public String getFieldB() {
return fieldB;
}
public void setFieldB(final String fieldB) {
this.fieldB = fieldB;
}
}
Controller:
#Controller
public class MyController {
#RequestMapping(value = "/some-path", method = RequestMethod.POST)
#ResponseStatus(HttpStatus.ACCEPTED)
public ResponseEntity handleSomePath(#RequestBody final CustomRequestBody body, final HttpServletRequest request) {
// Do the job.
}
And request will be:
HTTP POST http://some.server.com/some-path
{
"fieldA":"first value",
"fieldB":"second value"
}
Read more at Spring documentation here
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);
}
}
}
This is what I'm trying to do:
#Path("/finder")
public class Finder {
#Path("/{name}")
public Proxy find(#PathParam("name") String name) {
Object found = /* some object found by name */
return new Proxy(found);
}
}
public class Proxy {
private Object obj;
public Proxy(Object found) {
this.obj = found;
}
#GET
#Path("/")
public String info() {
return /* some meta-information about the object */
}
#Path("/")
public Object passthru() {
return this.obj;
}
}
I'm trying to enable:
GET /finder/alpha -> Proxy.info()
GET /finder/alpha/something -> obj.something()
Am I going the right way? Meanwhile, Jersey says:
WARNING: A sub-resource method, public final java.lang.String com.XXX.Proxy.info(),
with URI template, "/", is treated as a resource method
Everything is fine with the code above except that I don't need #Path("/") annotation at info().