Jax.rs: What is the zero-length representation of an object? - java

I have this issue:
We have a JAX.RS api like this:
#GET
#Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
#Path("get")
public User get(#QueryParam(value = "id") String identifier) {
return toUser(getUserEntry(identifier));
}
The toUser() method can return null, which in practice means that the client will see a 204 - No Content response.
Now, on the client side my code looks like this:
getWebTarget("user")
.path("get")
.queryParam("id", identifier)
.request(getMediaType())
.get()
.readEntity(SsoUser.class);
I was expecting the readEntity() to throw some kind of exception, but it actually returns null and does not complain.
Looking at the documentation, I see this:
for a zero-length response entities returns a corresponding Java object that represents zero-length data. In case no zero-length representation is defined for the Java type, a ProcessingException wrapping the underlying NoContentException is thrown.
So it appears that my User class does define a "zero-length representation". But I can't find anywhere in the documentation what this representation means.
I can understand how Java might infer that the zero-length representation is null, but I don't know where to define that.
Any insight on this?

I may be wrong about this, but I don't think Jax-RS is capable of handling null values for You. What You could do is explicitly handling that case, e.g.
#GET
#Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
#Path("get")
public Response get(#QueryParam(value = "id") String identifier) {
User maybeNull = toUser(getUserEntry(identifier));
Response response = null;
if(null == maybeNull) {
response = Response.noContent().build();
} else {
response = Response.ok(maybeNull).build();
}
return response;
}

Related

How to send json with GET params via Spring Boot / Tomcat?

so currently I'm working on a project where we have product objects which in turn contain "Origin" objects (containing region: String and country: String).
What I'm trying to do is a RestController which takes in an optional Origin object and does something with it (e.g. logs it).
This is what I have right now:
#GetMapping("search")
public Page<Wine> getProductByStuff(
#RequestParam(required = false) Origin origin,
/* other attributes */) {
log.info(origin); // it has a proper toString method.
}
There are two problem with this approach. First of all, when I send a request like:
http://[...]/search?origin={"region":"blah","country":"UK"}
or even the html converted string like:
http://[...]/search?origin={%22region%22:%22blah%22%44%22country%22:%22UK%22}
... it says
Invalid character found in the request target [/api/products/search?origin={%22region%22:%22blah%22%44%22country%22:%22DE%22}]. The valid characters are defined in RFC 7230 and RFC 3986.
Afaik the only valid characters Tomcat has that I need are {}. All others I've replaced with the html encoded chars and it still doesn't work.
What I did to prevent this:
#Component
public class TomcatWebServerCustomizer
implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
#Override
public void customize(TomcatServletWebServerFactory factory) {
TomcatConnectorCustomizer a = null;
factory.addConnectorCustomizers(connector -> {
connector.setAttribute("relaxedPathChars", "<>[\\]^`{|},\"");
connector.setAttribute("relaxedQueryChars", "<>[\\]^`{|},\"");
});
}
}
(See this, which is, by the way, deprecated (at least connector.setAttribute).)
This produced:
MethodArgumentConversionNotSupportedException: Failed to convert value of type 'java.lang.String' to required type '[censored].backend.model.Origin'.
My questions are:
(How) is it possible to configure Tomcat/Spring so that they can actually accept json in the url params?
How would I format it in e.g. Postman so that it would work? Currently I'm just converting special characters by hand in the params tab of Postman.
Here is what you need to do if you want to send it as json query param.
#RestController
public class OriginController {
#GetMapping("/search")
public void getOrigin(#RequestParam(value = "origin", required = false)
Origin origin) {
System.out.println(origin);
}
}
Register a converter
#Component
public class StringToOriginConverter implements
Converter<String, Origin> {
ObjectMapper objectMapper = new ObjectMapper();
#Override
public Origin convert(String source) {
try {
return objectMapper.readValue(source, Origin.class);
} catch (JsonProcessingException e) {
//You could throw some exception here instead for custom error
return null;
}
}
}
Sending from postman
Note
My answer is not debating whether you should use POST or GET as it is not what you have asked. It is just providing one option if you want to send some payload as query param
As mentioned, don't use JSON as a path parameter.
Directly use path parameters, and convert to Origin object.
#GetMapping("search")
public Page<Wine> getProductByStuff(
#RequestParam(required = false) String region,
#RequestParam(required = false) String country, /* other attributes */) {
Origin origin = new Origin(region, country);
log.info(origin); // it has a proper toString method.
}

Encode Response in REST service

I use class javax.ws.rs.core.Response in my service method:
#GET
#Path("/object/{id}")
#Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
#Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Response findObject(#Context HttpHeaders headers,
#PathParam("id") String objectId) {
Object object = getObject(objectId);
return createResponse(object, headers);
}
private Response createResponse(Object object, HttpHeaders headers) {
Response.ResponseBuilder responseBuilder = Response
.ok()
.entity(object)
.type(MediaType.APPLICATION_JSON_TYPE)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
.build();
}
And instance of Response contents hierarchy of my system which is contained in the instance object. This's object with all its children and their properties. And I want encoding values of properties in object. I need to give in response percent-encoding symbols instead of space in names for example. I need encoding whole my response including all fields. According to the UTF-8 encoding. For example, instead of value of some field 'His name: John' I need have 'His20%name%3A20%John' in my response. And similarly for all fields without exception in the whole hierarchy.
I didn't find anything better than how each field was handled manually using the method URLEncoder.encode(String s, String enc):
fieldValue = URLEncoder.encode(fieldValue, "UTF-8");
But in this case I need to manually go through all the fields that are in all the objects of my hierarchy coming in response. Maybe there is a more standard and correct way to do this?
And some fields are not string and have for example type BigDecimal. They can contain delimiters. How do I process them? Is there any way to encode the entire object response before return?
You can create separate fields/getter for encoded properties. In this way, the original values will be kept intact. You can also rely on reflection to fetch all fields and do it which will be messier than this.
class User {
private String firstName;
private String lastName;
private String encodedName;
public String getEncodedName(){
return URLEncoder.encode(this.firstName, "UTF-8”)+URLEncoder.encode(this.lastName, "UTF-8");
}
}

Deserializing null into a specific object in gson

I have this old implementation of awebservice class, which works off of retrofit1 and rxjava1 and is just a pain to work with, so wrote a new one from scratch.
Every response to an API-request is enveloped with the following object
{
"status":200,
"payload": {}
}
I bypassed the envelope-problem by simply wrapping every response into a generic object and when accessing the payload, I can do that easily by calling the getter
class BaseResponse<T> {
int status;
T payload;
public T getPayload(){
return payload;
}
}
The idea is to turn this BaseResponse<T> into Observable<T> and then call map {it.payload} to work with in some other place
interface API {
#Body("blablabla")
Observable<BaseResponse<SomeResponse>> someCall(#Body SomeRequest request);
}
public Observable<SomeResponse> someCall(SomeRequest request){
return api.someCall(request)
.map( response -> response.payload)
}
Now I've run into a problem. There are some API-requests, where the responsebody is defined as empty. I defined these empty responsebodies as class EmptyResponse {}
The problem is that I get those empty bodies like this
{
"status":200,
"payload":null
}
Now the problem is that payload is null and rxjava2 doesn't want null values.
Is there some way to have GSON deserialize null into an object of type EmptyResponse? Note that this ONLY occurs for calls that are documented to return empty responsebodies, there is no way that a call would return one, if it had a documented body

Hibernate validation with strong typing in Jersey with Jackson

I am implementing a REST API using Jersey. I want to validate all of the inputs to my service (query params, path params, DTOs) and am looking into some options - one that looks like it does the job is Jersey Bean Validation. I also want to have everything in the service strongly typed - for example, instead of using String to represent all of the bits of data, where you'd have a function like this:
public Order getOrder(String customerId);
Instead define types for each bit of data (the benefit of this is to let the compiler catch incorrect data being passed to functions, being able to obfuscate the underlying value in the toString method for logging, knowing that the value is definitely valid if you have an instance and so on), so you end up with functions like this:
public Order getOrder(CustomerId customerId);
And types like this:
public class CustomerId {
private final String value;
public CustomerId(String value) {
this.value = validate(value);
}
public String getValue() {
return value;
}
private String validate(String value) {
// Do some validation here
}
}
The Jersey Bean Validation examples do not use strong types like above. For example:
#Path("/")
class MyResourceClass {
#POST
#Consumes("application/x-www-form-urlencoded")
public void registerUser(
#Pattern(regexp="[a-zA-Z -]{1,50}") #FormParam("name") String name) {
...
}
}
The build in validation is nice in that you get some features for free:
400 bad request exception returned on any validation error
Optionally include the validation error in the response
None of the code in your function gets executed if validation fails
However, there are a few problems:
You have to remember to include the annotations everywhere the data
can be input to your system, so it's hard to apply consistently
You may end up with different definitions of what is valid for a type
You don't get the strong typing benefits mentioned above
Does anyone know of a way to get all of these benefits. I tried defining a type like this:
public class CustomerId {
private final String value;
public CustomerId(String value) {
this.value = validate(value);
}
public String getValue() {
return value;
}
private String validate(String value) {
if (!Pattern.matches("[a-zA-Z -]{1,50}", value)) {
throw new ConstraintViolationException(new HashSet<ConstraintViolation<?>>());
}
return value;
}
}
But it seems the exception doesn't get handled the same way by Jersey, and the response code you get if the validation fails is 404 instead of 400.
Does anyone know of a way to get the best of both worlds?
This is from the spec, in regards to how errors are handle when constructing #XxxParams
... if the [..] is annotated with #MatrixParam, #QueryParam or #PathParam then an implementation MUST generate an instance of NotFoundException (404 status) that wraps the thrown exception and no entity; if the field or property is annotated with #HeaderParam or #CookieParam then an implementation MUST generate an instance of
BadRequestException (400 status) that wraps the thrown exception and no entity.
Though not listed here, #FormParam falls under the 400 bracket.
"Does anyone know of a way to get the best of both worlds?"
We can override this behavior by throwing a WebApplicationException. We could then create an ExceptionMapper for the exception, and then just delegate to the ExceptionMapper that normally handles ConstraintViolationException. I couldn't find any clear detail on this behavior. I mean you would expect that the ExceptionMapper should get called anyway, but it doesn't if it is isn't an instance of WebApplicationException. So you can make your exception extend WebApplicationException.
public static class MyException extends WebApplicationException {
private final ConstraintViolationException cve;
public MyException(ConstraintViolationException cve) {
this.cve = cve;
}
public ConstraintViolationException getConstraintViolationException() {
return cve;
}
}
Then create an ExceptionMapper for it. In the mapper, we simply delegate to the original mapper that handles ConstraintViolationException
public static class MyExceptionMapper implements ExceptionMapper<MyException> {
#Context
private Providers providers;
#Override
public Response toResponse(MyException exception) {
ExceptionMapper<ValidationException> mapper
= providers.getExceptionMapper(ValidationException.class);
return mapper.toResponse(exception.getConstraintViolationException());
}
}
Then you can just throw MyException. If you don't care for an error response body, and all you want is a 400 status, you can forget everything above and simply throw a BadRequestException. Or if you don't care for the response entity that the ConstraintViolationException mapper sends out, you can create your own response in the MyExceptionMapper, or create a Response inside the CustomerId class and pass it the BadRequestException constructor. So you have some options.
A headache from this approach I could see is that you need to create your own ConstraintViolation. That can get old really quick.
The other approach I could see is to use #BeanParam and #Valid
public static class CustomerId {
#FormParam("cust")
#Pattern(regexp="[a-zA-Z -]{1,50}")
private String value;
public void setValue(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
#POST
#Path("form")
#Consumes("application/x-www-form-urlencoded")
public String postForm(#BeanParam #Valid CustomerId custId) {
The problem with this approach is that your bean is now stuck with #FormParam and is not reusable with other #XxxParams.
So you have some trade-offs. Hope this gives you some good information to work with.
UPDATE
Oh and the last option I can think of, is similar to second one above, but you aren't tied to the #XxxParam in the bean
public static class CustomerId {
//#FormParam("cust")
#javax.validation.constraints.Pattern(regexp="[a-zA-Z -]{1,50}")
private String value;
public CustomerId(String value) {
//this.value = validate(value);
this.value = value;
}
...
}
#POST
#Path("form")
#Consumes("application/x-www-form-urlencoded")
public String postForm(#FormParam("cust") #Valid CustomerId custId) {
Think the last option might be the way to go, but you still need to remember to always annotate with #Valid, which sounds like something you were trying to avoid.

POST with Restlet framework for Java

I can make a GET with no problem at all.
When trying it with a POST request, I get this message:
Internal Server Error
The server encountered an unexpected condition which prevented it from fulfilling the request
I'm testing it with Simple REST Client extension for Chrome, but I get the same message in the real application.
This is my post:
#Post
public StringRepresentation pepe(Representation entity) {
StringRepresentation result = this.users();
// Parse the given representation and retrieve data
Form form = new Form(entity);
String action = form.getFirstValue("action");
if(action.equals("add")){
//nothing
}
Db.closeConnection();
return result;
}
And this is my #Get working properly:
#Get
public StringRepresentation pepe() {
String action = getQuery().getValues("action");
StringRepresentation result = null;
result = this.users();
Db.closeConnection();
return result;
}
And the funny thing is: whenever I remove the condition if(action.equals("add")){, (which was empty inside) the POST works correctly.
This would work:
#Post
public StringRepresentation pepe(Representation entity) {
StringRepresentation result = this.users();
// Parse the given representation and retrieve data
Form form = new Form(entity);
String action = form.getFirstValue("action");
Db.closeConnection();
return result;
}
What's going on? Looks so random!
Yes, your variable action will be null if you don't have an entry action within the form payload.
You can notice that Restlet provides a method getFirstValue with a default value parameter:
String action = form.getFirstValue("action", "defaultActionValue");
This could help you not to have NullPointerException.
Otherwise, it seems that you try to implement several actions for a method POST. I think that this blog post could give you some additional hints: https://templth.wordpress.com/2015/03/20/handling-multiple-actions-for-a-post-method/.
Hope it helps you,
Thierry

Categories