risingTide
risingTide

Reputation: 1896

URI Recognition in Java Using JAX-RS @Path Annotations at Class and Method Level

I've created a situation in a Resource class where I'm receiving 404s for all 3 endpoints and was hoping someone could help explain why. Here is my resource class:

@Path("/incidents")
public class RegistrationResource {

    @GET
    @Path("/{incidentId}/registration")
    public Response getRegisteredIncidents(@PathParam("incidentId") String incidentId) {
           ...
    }

    @GET
    @Path("/registration/download")
    public Response downloadRegistrationIncidentsReport() {
           ...
    }

    @GET
    @Path("/registration/email")
    public Response emailRegistrationIncidentsReport() {
           ...
    }
}

I'm also using the JAX-RS Application class to register my resources and set a base "context" path across all of the endpoints:

@ApplicationPath("/context") 
public class AdminApplication extends Application { 

    @Override
    public Set<Class<?>> getClasses() { 

        Set<Class<?>> resources = new HashSet<Class<?>>();

        resources.add(RegistrationResource.class);

        ...

        return resources;
    }
}

Now if I split these methods into more than one resource class I can get each service to work fine, but my question is why won't the design above work? The full endpoints I'm trying to call here in order of the methods above are:

/context/incidents/55/registration
/context/incidents/registration/download
/context/incidents/registration/email

Why are the mappings above not being found? With this design I'm getting a 404 for each of the endpoints.

org.apache.wink.server.internal.RequestProcessor logException The following error occurred during the invocation of the handlers chain: WebApplicationException (404 - Not Found) with message 'null' while processing GET request sent to http://localhost:9081/someapp/context/incidents/55/registration

As a further point of clarification I'm using Wink 1.1 as my JAX-RS implementation since I'm bound to the packaged version that comes with WebSphere 8.5.5.5 at this time.

Thanks for your time!

-----------UPDATE #1-----------

In light of the comments below I have modified some of the endpoints. However I am still receiving random failures on certain endpoints with either 404 or, interestingly, 405 errors. It's important to note that if I redeploy sometimes different endpoints will fail, or perhaps all will work. Then if I redeploy again I will get similar, but different results of certain endpoints failing or all working. The bottom line is that it is very inconsistent.

Because of that it seems as if more endpoints are conflicting than in my original post so I am pasting all of my endpoints here for the sake of completeness.

/* Public Service Set */

@ApplicationPath("/public") 
public class PublicApplication extends Application { 

    @Override
    public Set<Class<?>> getClasses() { 

        Set<Class<?>> resources = new HashSet<Class<?>>();

        resources.add(IncidentResource.class);
        resources.add(IncidentDetailsResource.class);
        resources.add(TicketResource.class);
        resources.add(RegistrationResource.class);

        return resources;
    }
}

@Path("/incidents")
public class IncidentResource { 

    @GET
    @Produces(MediaType.APPLICATION_JSON) 
    public Response getIncidents() throws Exception {
    //...
    } 

@Path("/")
public class IncidentDetailsResource {

    @GET
    @Path("/v1/incidents/{incidentId}/details")
    @Produces(MediaType.APPLICATION_JSON) 
    public Response getIncidentDetails(@PathParam("incidentId") String incidentId) throws Exception {
    //...
    }

    @GET
    @Path("/v2/incidents/{incidentId}/details")
    @Produces(MediaType.APPLICATION_JSON) 
    public Response getIncidentDetailsAsJson(@PathParam("incidentId") String incidentId) throws Exception {
    //...
    }

}

@Path("/incidents/{incidentId}/tickets") 
public class TicketResource {

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Response getTickets(@PathParam("incidentId") String incidentId) throws Exception {
    //...
    }
}


@Path("/incidents")
public class RegistrationResource {

    @POST
    @Path("/{incidentId}/registration")
    @Consumes(MediaType.APPLICATION_JSON)
    public Response register(@PathParam("incidentId") String incidentId, BaseRequestMessage<IncidentRegistrationRequest> baseRequestMessage) throws Exception {
    //...
    }
}

/* Admin Service Set */

@ApplicationPath("/admin") 
public class AdminApplication extends Application { 

    @Override
    public Set<Class<?>> getClasses() { 

        Set<Class<?>> resources = new HashSet<Class<?>>();

        resources.add(AdminIncidentResource.class);
        resources.add(AdminIncidentDetailsResource.class);
        resources.add(AdminRegistrationResource.class);
        resources.add(AdminRegistrationReportResource.class);
        resources.add(AdminTicketResource.class);

        return resources;
    }
}

@Path("/incidents")
public class AdminIncidentResource { 

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public Response addIncident(BaseRequestMessage<IncidentRequest> baseRequestMessage) throws Exception {
    //...
    }

    @PUT 
    @Path("/{incidentId}")
    @Produces(MediaType.APPLICATION_JSON)
    public Response updateIncident(@PathParam("incidentId") String incidentId, BaseRequestMessage<IncidentRequest> baseRequestMessage) throws Exception {
    //...
    }

    @DELETE 
    @Path("/{incidentId}")
    @Produces(MediaType.APPLICATION_JSON)
    public Response deleteIncident(@PathParam("incidentId") String incidentId) throws Exception {
    //...
    }
}

@Path("/")
public class AdminIncidentDetailsResource {

    @PUT
    @Path("/v1/incidents/{incidentId}/details")
    @Consumes(MediaType.APPLICATION_JSON)
    public Response updateIncidentDetails(@PathParam("incidentId") String incidentId, BaseRequestMessage<IncidentDetailRequest> baseRequestMessage) throws Exception {
    //...    
    }

    @PUT
    @Path("/v2/incidents/{incidentId}/details")
    @Consumes(MediaType.APPLICATION_JSON)
    public Response updateIncidentDetailsAsJson(@PathParam("incidentId") String incidentId, BaseRequestMessage<JsonNode> baseRequestMessage) throws Exception {
    //...
    }
}

@Path("/incidents/{incidentId}/tickets")
public class AdminTicketResource {

    @POST
    @Consumes(MediaType.APPLICATION_JSON) 
    public Response addTicket(@PathParam("incidentId") String incidentId, BaseRequestMessage<TicketRequest> baseRequestMessage) throws Exception {
    //...   
    }

    @PUT
    @Consumes(MediaType.APPLICATION_JSON) 
    public Response updateTicket(@PathParam("incidentId") String incidentId, BaseRequestMessage<TicketRequest> baseRequestMessage) throws Exception {
    //...    
    }

    @DELETE
    @Path("/{detailsUrn}")
    @Consumes(MediaType.APPLICATION_JSON) 
    public Response removeTicket(@PathParam("incidentId") String incidentId, @PathParam("detailsUrn") String detailsUrn) throws Exception {
    //...    
    }

}

@Path("/incidents/registration/report")
public class AdminRegistrationReportResource {

    @GET
    @Path("/download")
    @Produces("application/vnd.ms-excel")
    public Response downloadRegistrationReport() throws Exception { 
    //...    
    }

    @GET
    @Path("/email")
    @Produces(MediaType.APPLICATION_JSON)
    public Response emailRegistrationReport() throws Exception { 
    //...    
    }
}

@Path("/incidents/{incidentId}/registration")
public class AdminRegistrationResource {

    @GET
    @Produces(MediaType.APPLICATION_JSON) 
    public Response getRegisteredUsers(@PathParam("incidentId") String incidentId) throws Exception { 
    //...    
    }

    @PUT
    @Consumes(MediaType.APPLICATION_JSON)
    public Response updateRegisteredUser(@PathParam("incidentId") String incidentId, BaseRequestMessage<IncidentRegistrationRequest> baseRequestMessage) throws Exception {
    //...    
    }
}

For sake of expediency...the code above ends up generating these uris:

That's a lot of code; I wanted to avoid that originally but it seems it is necessary. Also, for what it is worth, things seemed to be working until I added the AdminRegistrationResource class. Perhaps something with one of the endpoints in that class began causing a conflict?

Again...thanks for your time!

-----------UPDATE #2-----------

I'd thought I'd post one of my recent specific errors here in the hopes that it may help in solving the issue. When I call this endpoint:

GET /public/incidents

I'm receiving this error message:

00000203 ResourceRegis I org.apache.wink.server.internal.registry.ResourceRegistry filterDispatchMethods The system cannot find any method in the com.somewhere.unimportant.rest.resource.external.RegistrationResource class that supports GET. Verify that a method exists.

00000203 RequestProces I org.apache.wink.server.internal.RequestProcessor logException The following error occurred during the invocation of the handlers chain: WebApplicationException (405) with message 'null' while processing GET request sent to https://test.somewhere.com:88888/app777/public/events

Now what's even more interesting about this is that I receive this error locally, but I've also pushed my code up to a test environment where there are two load-balanced servers. In the test environment I receive this error on one of the servers consistently but the other one works consistently. So a 50% failure rate split on one successful server and one unsuccessful server.

Strange...why is it looking in that class for the endpoint I'm hitting?

-----------UPDATE #3-----------

Ok, I've taken out the use of the @ApplicationPath annotation in my PublicApplication class completely (as suggested from the comments below) and replaced it with a mere "/" but still receive a 404 error in the following scenario (note that for this test I have completely removed the "admin" services):

/* Public (and only) Service Set */

@ApplicationPath("/") 
public class PublicApplication extends Application { 

    @Override
    public Set<Class<?>> getClasses() { 

        Set<Class<?>> resources = new HashSet<Class<?>>();

        resources.add(IncidentResource.class);
        resources.add(IncidentDetailsResource.class);
        resources.add(TicketResource.class);
        resources.add(RegistrationResource.class);

        return resources;
    }
}

@Path("/public")
public class IncidentResource { 

    @GET
    @Path("/incidents")
    @Produces(MediaType.APPLICATION_JSON) 
    public Response getIncidents() throws Exception {
    //...
    } 

@Path("/public")
public class IncidentDetailsResource {

    @GET
    @Path("/v1/incidents/{incidentId}/details")
    @Produces(MediaType.APPLICATION_JSON) 
    public Response getIncidentDetails(@PathParam("incidentId") String incidentId) throws Exception {
    //...
    }

    @GET
    @Path("/v2/incidents/{incidentId}/details")
    @Produces(MediaType.APPLICATION_JSON) 
    public Response getIncidentDetailsAsJson(@PathParam("incidentId") String incidentId) throws Exception {
    //...
    }

}

@Path("/public/incidents/{incidentId}/tickets") 
public class TicketResource {

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Response getTickets(@PathParam("incidentId") String incidentId) throws Exception {
    //...
    }
}

@Path("/public/incidents/{incidentId}/registration")
public class RegistrationResource {

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public Response register(@PathParam("incidentId") String incidentId, BaseRequestMessage<IncidentRegistrationRequest> baseRequestMessage) throws Exception {
    //...
    }
}

In this scenario I am getting this error:

org.apache.wink.server.internal.RequestProcessor logException The following error occurred during the invocation of the handlers chain: WebApplicationException (404 - Not Found) with message 'null' while processing GET request sent to http://test.somewhere.com:88888/app777/public/events

when trying to call:

GET /public/incidents

It may also be relevant that the other 4 endpoints above work just fine. And, more importantly, if I change the IncidentResource class to look like the following (moving the @Path annotation at the method level completely to the class level) then all 5 endpoints work:

@Path("/public/incidents")
public class IncidentResource { 

    @GET
    @Produces(MediaType.APPLICATION_JSON) 
    public Response getIncidents() throws Exception {
    //...
    } 

I appreciate everyone who has stayed with me this far!

Upvotes: 0

Views: 1841

Answers (2)

s7vr
s7vr

Reputation: 75934

Remove @ApplicationPath("/context") or alternatively don't use the /context in the request uri or just use @ApplicationPath("/") it wont break even when you upgrade.

All the above different ways should work, but for your version I would recommend @ApplicationPath("/") and update your request uris by removing /context and everything should work the way you originally intended.

Apache Wink doesn't have support for ApplicationPath just yet.

https://issues.apache.org/jira/browse/WINK-398

Upvotes: 2

Mike Thomsen
Mike Thomsen

Reputation: 37506

This might work for you:

@Path("/incidents/{incidentId}/registration")
public class RegistrationResource1 {

    @GET
    public Response getRegisteredIncidents(@PathParam("incidentId") String incidentId) {
           ...
    }
}

@Path("/incidents/registration/download")
public class RegistrationResource {

    @GET
    public Response downloadRegistrationIncidentsReport() {
           ...
    }
}

However, I would not recommend splitting up the controller like that. Instead, just make a point of removing as much of the code inside each function to separate business logic classes.

Upvotes: 0

Related Questions