eclipse-ee4j / jersey

Eclipse Jersey Project - Read our Wiki:
https://github.com/eclipse-ee4j/jersey/wiki
Other
692 stars 355 forks source link

No way to look up a resource by its URI #2444

Open jerseyrobot opened 11 years ago

jerseyrobot commented 11 years ago

In Jersey 1.0 we could look up a resource by URI using ResourceContext.matchResource(URI). There doesn't seem to be an equivalent functionality in Jersey 2.0.

Use-case: The request entity body references the URI of a resource. I need to convert this URI into a resource class, then drill down into its database id in order to honor the request.

Affected Versions

[2.4]

jerseyrobot commented 6 years ago
jerseyrobot commented 11 years ago

@glassfishrobot Commented Reported by cowwoc

jerseyrobot commented 11 years ago

@glassfishrobot Commented cowwoc said: FYI, I tried asking this question on the mailing list and http://stackoverflow.com/q/17284419/14731 but got no answer.

jerseyrobot commented 11 years ago

@glassfishrobot Commented cowwoc said: It looks like I want to be able to create UriInfo from an arbitrary URI but the only way to get a UriInfo is using ContainerRequestContext.getUriInfo(). I tried ContainerRequestContext.setRequestUri(uri) but it failed with:

java.lang.IllegalStateException: Method could be called only in pre-matching request filter.
    at org.glassfish.jersey.server.ContainerRequest.setRequestUri(ContainerRequest.java:329) ~[jersey-server-2.4.jar:na]

I can't use a request filter because I need to process this information inside resource methods (once I understand what kind of operation was invoked), besides which it sounds like this operation would be overkill for what I'm trying to accomplish.

jerseyrobot commented 11 years ago

@glassfishrobot Commented cowwoc said: Here is an example of how I would use this functionality. Imagine an API with Rooms, Participants, and Connections. Participants are users who joined a Room. Users send messages to each other through Connections (two participants per connection).

POST /rooms/31298
{
  "from": "/rooms/31298/participants/31",
  "to": "/rooms/31298/participants/32"
}

creates a connection between participants 31 and 32. How is the server supposed to honor this request? In Jersey 1.0 I would resolve each URI back to a resource. Each resource contains the following methods:

long getId(); URI getUri();

So, once I've got the resource, I invoke getId() to get the database identifier and create the connection. When someone invokes GET /connections/321213 I convert the database id to a resource and from there invoke getUri(). I take the resulting URI and add it to the response body.

jerseyrobot commented 11 years ago

@glassfishrobot Commented @mpotociar said: Reclassified as improvement.

jerseyrobot commented 11 years ago

@glassfishrobot Commented @mpotociar said: I'm not sure I fully follow. How do you convert the database Id to a resource?

Would these 2 methods help in your use case? https://jax-rs-spec.java.net/nonav/2.0/apidocs/javax/ws/rs/core/UriBuilder.html#fromResource(java.lang.Class https://jax-rs-spec.java.net/nonav/2.0/apidocs/javax/ws/rs/core/UriInfo.html#resolve(java.net.URI

jerseyrobot commented 11 years ago

@glassfishrobot Commented cowwoc said: Hi Marek,

How do you convert the database id to a resource?

In the above example, I'd have something like this:

class RoomResource
{
  private final long id;
  private final UriBuilder uriBuilder;

  public RoomResource(RoomsResource parentResource, UriBuilder uriBuilder, long roomId)
  {
    this.parentResource = parentResource;

    // Each parent passes a child resource its URI
    this.uriBuilder = uriBuilder;
    this.id = roomId;
  }

public long getId()
{
  return id;
}

public URI getUri()
{
  return uri;
}

@POST
public Response RoomResource.createConnection(TwoParticipants participants)
{
  long fromParticipant = uriToId(participants.getFrom());
  long toParticipant = uriToId(participants.getTo());
  Connection connection = Connection.insert(fromParticipant, toParticipant);
  ConnectionResource connectionResource = getConnectionById(connection.getId());
  return Response.created(connectionResource.getUri()).build();
}

@Path("{connectionId}/")
public ConnectionResource getConnectionById(long id)
{
  return new ConnectionResource(this, uriBuilder.path(id + "/"), id);
}

Notice how I use both a conversion from URI to id and id to URI. As mentioned in https://java.net/projects/jersey/lists/users/archive/2013-11/message/62 I now realize that Jersey has no concept of what I call a HTTP Resource (meaning the concept of a resource independent of the response format). I'm looking for a mapping from URI to a resource, independent of the response format, and Jersey doesn't seem to have such a concept per-se. If it had such a concept, the question of how this feature should intersect with filters probably wouldn't be relevant because they wouldn't affect the URI to Resource mapping. Jersey 1.x ResourceContext.matchResource(URI) seemed to behave this way (although maybe not intentionally).

At this point, it's not clear how I can implement this without repeating the URI to Resource mapping twice (once for Jersey using @Path and sub-resource locators, once for my code that implements URI to Resource mapping).

jerseyrobot commented 10 years ago

@glassfishrobot Commented cowwoc said: It doesn't seem to be possible to implement this without your help.

I tried using ResourceModel to implement this outside of Jersey but:

  1. We need to be able to invoke subresource locators in order to construct a resource
  2. But in order to figure out what arguments need to be passed into a subresource locator, we need to invoke Resource.getResourceLocator().getInvocable().getHandlingMethod().getValueProviders(serviceLocator)
  3. But PathParamValueFactoryProvider.provide() invokes getContainerRequest().getUriInfo().getPathParameters(decode).
  4. This means that the servicelocator we passed into step 2 must return a ContainerRequest for the URI we are looking up, not for the ongoing HTTP request.
  5. The problem is that we cannot create their own ContainerRequest. Invoking the constructor is simple enough, but the UriInfo field is initialized by ReferencesInitializer which is package protected.

It seems that all roads lead to Rome. Jersey needs to provide a mechanism to construct a UriInfo or ContainerRequest to an arbitrary URI.

If you know of another way, please point me in the right direction. I believe this is the only remaining feature preventing me from migrating to Jersey 2.

jerseyrobot commented 10 years ago

@glassfishrobot Commented cowwoc said: Another approach would be to fix #2757.

jerseyrobot commented 10 years ago

@glassfishrobot Commented levwais said: Hey Guys, This will be super useful for me. I want to create a new endpoint that will get, as a request, a list of URIs (other API endpoints) and will run them in a batch. The missing piece is to find a matching endpoint by a given URI which is something Jersey 1 had via ResourceContext.matchResource(URI) and now, in Jersey 2.x it is impossible.

Thanks, Lev Waisberg Jive Software

jerseyrobot commented 10 years ago

@glassfishrobot Commented cowwoc said: Lev,

Take a look at http://stackoverflow.com/a/23620747/14731

It's ugly code, but it works. We can only hope that this will get folded into Jersey proper (so we can use this functionality in a cleaner fashion).

jerseyrobot commented 7 years ago

@glassfishrobot Commented This issue was imported from java.net JIRA JERSEY-2172