Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
jerseyrobot opened this issue Oct 28, 2013 · 13 comments
Open

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

jerseyrobot opened this issue Oct 28, 2013 · 13 comments

Comments

@jerseyrobot
Copy link
Contributor

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
Copy link
Contributor Author

@glassfishrobot Commented
Reported by cowwoc

@jerseyrobot
Copy link
Contributor Author

@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
Copy link
Contributor Author

@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
Copy link
Contributor Author

@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
Copy link
Contributor Author

@glassfishrobot Commented
@mpotociar said:
Reclassified as improvement.

@jerseyrobot
Copy link
Contributor Author

@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
Copy link
Contributor Author

@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
Copy link
Contributor Author

@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
Copy link
Contributor Author

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

@jerseyrobot
Copy link
Contributor Author

@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
Copy link
Contributor Author

@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
Copy link
Contributor Author

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

@jerseyrobot
Copy link
Contributor Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant