I need to get at the baseUri, I'm familliar with this
#Context
UriInfo uriInfo;
my problem is I need the baseUri in a piece of code I can't Inject into, (a sub object of an entity). I could pass uriInfo or the builder down but that doesn't seem ideal, as most of the objects have no business knowing about it. The end goal is HATEOAS URI's in my JSON responses, but I don't need it to be a JAX-RS specific solution, I figure if I can just get at the Base URI either in my jackson converters, or entities, I can create the URIBuilder from there.
The UriInfo is managed by the JAX-RS implementation and AFAIK, you cannot actually inject it in other places of your code (it's no a CDI bean). If your goal is to implement HATEOAS, you could probably write your own MessageBodyWriter which will be responsible for unmarshalling your entity objects graph into a JSON response and in which you can inject this UriInfo.
HTH.
UPDATE: Minutes after I wrote all this I found Chapter 12. Declarative Hyperlinking in the Jersey 2.13 spec. It's still "under development and experimental" but looks like it solves this problem much more elegantly. All the code in the original post can be replaced by this...
public class Pie {
private String name;
Pie(String name) {
this.name = name;
}
#JsonIgnore
public String getName() {
return name;
}
#InjectLink(value="pies/{name}", style=Style.ABSOLUTE)
public URI url;
}
If you need to send arbitrary data to a Jackson serializer then the solution below is still valid.
ORIGINAL I had the same issue. Specifically I wanted my resources to contain just their id (the thing at the end of their URI) and find a way to generate the complete URI using UriInfo only when Jackson generates the JSON. I wanted to avoid solutions where I had to either create new cover objects on my resources for each request, or insert the UriInfo into each resource before it was serialized.
It turns out that as of Jackson 2.3 there is a very nice way to do this. Jackson supports a class called ObjectWriterInjector. Using its static set method you can provide your own instance of an ObjectWriterModifier once per request. That instance can be created and set in your Jersey resource method where UriInfo is available. Before Jackson writes out JSON it calls your ObjectWriterModifier giving you a chance to tweak the ObjectWriter it will use. In this case, you can use the withAttribute method to attach UriInfo to the ObjectWriter.
So what good is an attribute on the ObjectWriter? It turns out that those attributes are available through the SerializerProvider parameter in the serialize method of a JsonSerializer implementation. In my case I only wanted to modify one field so I used #JsonSerialize on the getter. In the serialize method I then call the SerializerProvider parameter's getAttribute method to retrieve the UriInfo and then use it to construct the complete URI.
This methodology should work in any case where you want to push information down to a Jackson serializer.
Simple Jersey resource...
#Path("/pies")
public class Pies {
static List<Pie> pies;
static
{
pies = new ArrayList<Pie>();
pies.add(new Pie("apple"));
pies.add(new Pie("pumpkin"));
}
#GET
#Produces(MediaType.APPLICATION_JSON)
public List<Pie> getPies(#Context UriInfo uriInfo) {
// This is the good stuff!!!!
ObjectWriterInjector.set(new ObjectWriterPlusUriInfo(uriInfo));
// Notice I don't have to anything to the objects in the Pies list
return pies;
}
}
The class that tweaks the ObjectWriter...
class ObjectWriterPlusUriInfo extends ObjectWriterModifier {
private UriInfo uriInfo;
ObjectWriterPlusUriInfo(UriInfo uriInfo) {
this.uriInfo = uriInfo;
}
#Override
public ObjectWriter modify(EndpointConfigBase<?> endpoint,
MultivaluedMap<String, Object> responseHeaders, Object value,
ObjectWriter ow, JsonGenerator gen) throws IOException {
// Adding the UriInfo attribute
return ow.withAttribute("JerseyUriInfo", uriInfo);
}
}
The Pie class with Jackson annotations...
public class Pie {
private String name;
Pie(String name) {
this.name = name;
}
#JsonProperty(value="url")
#JsonSerialize(using=JacksonIdToUrlSerializer.class)
public String getName() {
return name;
}
}
Serializer...
public class JacksonIdToUrlSerializer extends JsonSerializer<String>{
#Override
public void serialize(String value, JsonGenerator jgen,
SerializerProvider provider) throws IOException,
JsonProcessingException {
UriInfo uriInfo = (UriInfo)provider.getAttribute("JerseyUriInfo");
jgen.writeString(uriInfo.getRequestUriBuilder().path(value).build().toString());
}
}
A GET of...
http://localhost:8080/service/api/pies...
Produces...
[
{
url: "http://localhost:8080/service/api/pies/apple"
},
{
url: "http://localhost:8080/service/api/pies/pumpkin"
}
]
A GET of...
http://127.0.0.1:8080/service/api/pies...
Produces...
[
{
url: "http://127.0.0.1:8080/service/api/pies/apple"
},
{
url: "http://127.0.0.1:8080/service/api/pies/pumpkin"
}
]
Note that the ObjectWriterInjector uses a Thread Local variable internally so using that more generic method to pass data down on a per request basis seems equally valid.
Related
I've a filter through which POST REST api goes with and i want to extract the below part of my payload in the filter.
{
"foo": "bar",
"hello": "world"
}
Filter code :-
public class PostContextFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext requestContext)
throws IOException {
String transactionId = requestContext.getHeaderString("id");
// Here how to get the key value corresponding to the foo.
String fooKeyVal = requestContext. ??
}
}
I don't see any easy method to get the payload to the api using the ContainerRequestContext object.
So my question is how do i get the key value corresponding to the foo key in my payload.
Whereas filters are primarily intended to manipulate request and response parameters like HTTP headers, URIs and/or HTTP methods, interceptors are intended to manipulate entities, via manipulating entity input/output streams.
A ReaderInterceptor allows you to manipulate inbound entity streams, that is, the streams coming from the "wire". Using Jackson to parse the inbound entity stream, your interceptor could be like:
#Provider
public class CustomReaderInterceptor implements ReaderInterceptor {
// Create a Jackson ObjectMapper instance (it can be injected instead)
private ObjectMapper mapper = new ObjectMapper();
#Override
public Object aroundReadFrom(ReaderInterceptorContext context)
throws IOException, WebApplicationException {
// Parse the request entity into the Jackson tree model
JsonNode tree = mapper.readTree(context.getInputStream());
// Extract the values you need from the tree
// Proceed to the next interceptor in the chain
return context.proceed();
}
}
This answer and this answer may also be related to your question.
public ResponseEntity<TastyDashResponse> order(#PathVariable("restaurantId") String restaurantId,
#RequestBody RestaurantOrderBook request,
#RequestBody ExpItems exp) {}
I have two Objects RestaurantOrderBook and ExpItems. Incoming request should have either of them and the other becomes optional.
How to achieve this within same method. When this code I get 404 bad request. please help with it.
You cannot use two #RequestBody as it can bind to a single object .
solution create one object that will capture all the relevent data like this this :
public class Data {
private String restaurantId;
private RestaurantOrderBook request;
private ExpItems exp;
getters/setters
}
public ResponseEntity<TastyDashResponse> order(#RequestBody Data data) {}
Let's say I have:
#GET
public UserList fetch(#PathParam("user") String userId) {
// Do stuff here
}
Now, let's say I have my own type for userId, let's call it UserId. Is it possible to parse that String to UserId when it is passed into the fetch method, i.e.:
#GET
public UserList fetch(#PathParam("user") UserId userId) {
// Do stuff here
}
I realize I can parse the String once I am inside the method, but it would be more convenient that my method gets the type I want.
Well you've attempted to make a GET call with a request body is what I find not very helpful. Do read Paul's answer here -
you can send a body with GET, and no, it is never useful to do so
What would be good to practice is, to make a PUT or a POST call (PUT vs POST in REST) as follows -
#POST
#Path("/some-path/{some-query-param}")
public Response getDocuments(#ApiParam("user") UserId userId,
#PathParam("some-query-param") String queryParam) {
UserId userIdInstance = userId; // you can use the request body further
Note - The ApiParam annotation used is imported from the com.wordnik.swagger.annotations package. You can similarily use FormParam,QueryParam according to your source of input.
Dropwizard is using Jersey for HTTP<->Java POJO marshalling. You could use the various annotations from Jersey #*Param (#FormParam, #QueryParam, etc.) for some of the parameters.
If you need to use map/marshall to/from Java POJOs take a look at the test cases in Dropwizard:
#Path("/valid/")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public class ValidatingResource {
#POST
#Path("foo")
#Valid
public ValidRepresentation blah(#NotNull #Valid ValidRepresentation representation, #QueryParam("somethingelse") String xer) {
return new ValidRepresentation();
}
This defines an API endpoint responding to HTTP POST method which expects ValidRepresentation object and "somethingelse" as HTTP method query parameter. The endpoint WILL respond ONLY when supplied with JSON parameters and will return only JSON objects (#Produces and #Consumes on the class level). The #NotNull requires that object to be mandatory for the call to succeed and #Valid instructs Dropwizard to call Hibernate validator to validate the object before calling the endpoint.
The ValidRepresentation class is here:
package io.dropwizard.jersey.validation;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.hibernate.validator.constraints.NotEmpty;
public class ValidRepresentation {
#NotEmpty
private String name;
#JsonProperty
public String getName() {
return name;
}
#JsonProperty
public void setName(String name) {
this.name = name;
}
}
The POJO is using Jackson annotations to define how JSON representation of this object should look like. #NotEmtpy is annotation from Hibernate validator.
Dropwizard, Jersey and Jackson take care of the details. So for the basic stuff this is all that you need.
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
Using this post as a reference I put together a bare bones Jersey controller method for POST calls that looks like this:
#POST
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public Response createVisit(Visit newVisit) {
LOGGER.info("Creating visit");
this.visits.add(newVisit);
return Response.ok(newVisit)
.build();
}
Here are the fields on my Visit object (constructors and getter/setters omitted because I don't think they're relevant here - I can add them in if they'd be helpful):
public class Visit {
private VisitId id;
private AthleteId athleteId;
private CustomerId customerId;
private StoreId storeId;
private Instant createdUtc;
private Instant lastModifiedUtc;
}
When I pass in valid Visit object fields in JSON format in the request body, I see the Visit object successfully populated in the response as expected. However, if I add fields that aren't part of the Visit object to the request body they seem to be ignored.
I've seen a number of posts trying to figure out how to disable the FAIL_ON_UNKNOWN_PROPERTIES property, but I seem to be having the opposite issue. My understanding is that FAIL_ON_UNKNOWN_PROPERTIES defaults to true, but in that case I would expect to get an error response code (500?) when the JSON object in the request body doesn't match with the object I'm passing in to my POST method. Any ideas on what I'm missing here?
Use a ContextResolver, as mentioned in the documentation
#Provider
public class MyObjectMapperProvider implements ContextResolver<ObjectMapper> {
private final ObjectMapper mapper;
public MyObjectMapperProvider() {
mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
#Override
public ObjectMapper getContext(Class<?> type) {
return mapper;
}
}
}
Then you need to make sure the resolver is registered. If you are using some scanning mechanism to auto-register your resources and providers, this class should be picked up with the #Provider annotation