Jersey, JSR 303 validation - custom "path" and "invalidValue" in ValidationError - java

I am using Jersey (JAX-RS) and I'm trying to implement a validation. I have a problem with a response returned by my application when a validation error occurs. Now the response looks like this:
[{
"message": "Custom message",
"messageTemplate": "{custom.message.template}",
"path": "SomeJerseyResource.resourceMethod.arg0.names[0]",
"invalidValue":"[value1, value2]"
}]
where "SomeJerseyResourceClass.resourceMethod" is a JAX-RS resource:
public class SomeJerseyResource {
#POST
#Path("/path")
public Response resourceMethod(#Valid RequestModel request) {
/** method body **/
}
}
and validation constraint is assigned to a getter in RequestModel:
public class RequestModel {
private List<String> names = new ArrayList<>();
#MyConstraint
public List<String> getNames() {
return tags;
}
}
I have a custom ConstraintValidator, where I validate each element of that List.
Problem
I don't want to include resource and method name in "path" field of the response. Instead of
SomeJerseyResource.resourceMethod.arg0.names[0] I want arg0.names[0] only. Client doesn't know about server classes and methods, and he wouldn't be able to properly assign errors to fields when he receives response like that.
I want to customize "invalidValue" field of a response. More specifically, to have only invalid element value, not the whole list in that field.
I didn't find any easy way to do that. Do you have any ideas?

You can just write an ExceptionMapper<ConstraintViolationException> to return the Response of your liking. Jersey uses an ExceptionMapper<ViolationException>. ConstraintViolationException extends from ViolationException, so you're mapper is more specific, and would take precedence in the choosing of the mapper. Jersey's mapper, returns the response as a ValidationError, that's why the body is how it is. But you can make it whatever you want.
If you just want the invalidValue list, then just iterate through the ConstraintViolations from ContraintViolationException.getConstraintViolations(), and get the invalidValue from the ConstraintViolation.

Related

How to pass a list of integers as json data to a Spring Boot rest API without a custom intermediate object?

How to pass a list of integers as json data to a Spring Boot rest API without creating a custom intermediate object? I know you can create a DTO sort of object to map the request and I've done that successfully but is there a way to receive it as a collection/array of IDs without having to create a class just for this purpose? I tried to do it without any luck. Also tried to map it to a collection of the Entity the IDs refer to also without any luck. A way to do this, or alternatives that are more idiomatic to Spring Boot would be welcomed.
Sample of request body that's being sent to the endpoint:
{
"products": [
"1",
"2"
]
}
It seems answer of fps should be suitable, but here one caveat
Even if your class is annotated as #RestController, you need to use #RequestBody when using Map as input (with POJO this annotation can be avoided)
Bellow code works fine:
#PostMapping("/products")
#ResponseStatus(value = HttpStatus.OK)
public Object createProducts(#RequestBody Map<String, List<String>> input) {
return input;
}
More agile way is using Jackson classes.
It allows to use not only Strings, but all "native" json structures.
You can check JsonNode.isNumber(), JsonNode.isTextual(), JsonNode.isArray(), JsonNode.isObject() and so on.
#PostMapping("/products")
#ResponseStatus(value = HttpStatus.OK)
public Object createProducts(#RequestBody Map<String, JsonNode> input) {
return input;
}

Custom annotation for JAX-RS method and parameter passing to JAX-RS method from interceptor

I will introduce a little bit wider context to clarify my use case...
I would like to implement custom annotation #FilterResponse to annotate JAX-RS method in order to filter fields in JSON response. For latter purpose, I'm using Jacksons's #JsonFilter mechanism. Idea is to enable usage of dot notation to specify wanted fields in whole object graph, starting from returned entity as a root.
For example, if I have method that returns Product entity and I want to return only name field from Product and name field from associated entity CharacteristicValues within a Product, I will do something like this...
#GET
#Produces("application/json")
#Path("/{pid}")
#FilterResponse(include = {"name", "characteristicValues.name"}, responseEntityClass = Product.class)
public Response get(#PathParam("pid") Integer pid) {
Product result = productService.findSimpleProduct(pid);
return Response.ok().entity((result)).build();
}
Under the hood, in interceptor implementation for #FilterResponse annotation, I perform all necessary transformations and configuration of ObjectMapper.
Further, inside interceptor I generate some objects that can be useful in the rest of request processing (in Services, in DAOs, etc), so I would like to pass that objects created in interceptor (for example, my FieldTree object created from metadata specified in #FilterResponse annotation) to intercepted JAX-RS method... And that is my problem right now.
UPDATE: For example, passed FieldTree could be used to reconstruct JPA EntityGraph in order to fetch only preferred fields from database.
Here is pseudo-cod of interceptor to illustrate idea...
#FilterResponse
#Interceptor
public class FilterResponseInterceptor {
#AroundInvoke
public Object intercept(InvocationContext context) throws Exception {
List<String> fieldsToRetain = getIncludeListFromFilterResponseAnottationIn(context);
FeildTree fieldTree = createFieldTreeFrom(fieldsToRetain);
passFieldTreeToMethodInContext(fieldTree, context);
Object response = context.proceed();
return filteredResponseWithFields(response, fieldsToRetain);
}}
When I try to declare paramter which I want to pass to intercepted JAX-RS method from interceptor, as shown below...
#GET
#Produces("application/json")
#Path("/{pid}")
#FilterResponse(include = {"name", "characteristicValues.name"}, responseEntityClass = Product.class)
public Response get(#PathParam("pid") Integer pid, FieldTree requestedFieldTree) {
Product result = productService.findSimpleProduct(pid);
return Response.ok().entity((result)).build();
}
...I'm getting following error...
RESTEASY002010: Failed to execute: javax.ws.rs.NotSupportedException: RESTEASY003200: Could not find message body reader for type: class app.fieldsfiltering.FieldTree of content type: */*
Is there any way to make RESTEasy to ignore some paramters in JAX-RS method, or some other way to perform parameter passing to intercepted JAX-RS method?
Sorry for my lengthily question.
UPDATE2: Some clarifications of my idea... I didn't mention that I would like to avoid "trivial" DTOs that are purely selection of some fields from domain entity without change of the structure. So... EntityGraph for example will be created in DAO or Service layer... But, i feel that "metadata" necessary for EntityGraph creation (e.g. preferred field graph or FieldTree as I called it) should be placed on JAX-RS method. Why? In that case, I would pass FieldTree to Service logic... then, I would create appropriate EntityGraph and eagerly fetch domain entity with all fields defined in FieldTree.. then, I would return domain entity to JAX-RS method and retain only fields defined in FieldTree in JSON response in order to avoid LazyInitialization exception that will be thrown if Jackson try to parse some lazy field in entity object.

Marshall object returned by #RequestBody

I have XML being fed into a REST controller and I use the #RequestBody annotation to transform that into a Java Object. I need to save the whole XML to a database column in addition to some specific elements from the Java Object. I am marshalling the Java object explicitly to accomplish this and that seems to be duplicate work. Is there a way to get the raw XML in addition to the parsed object while using the #RequestBody annotation?
Yes, there is. Let's say this is your controller method:
public Response yourControllerMethod(#RequestBody YourDTO dto) {
Simply change your #RequestBody parameter type to String and you'll get the raw body of the request:
public Response yourControllerMethod(#RequestBody String rawPayload) {
Or you can even get both:
public Response yourControllerMethod(#RequestBody YourDTO dto, #RequestBody String rawPayload) {

How to get #PathParam and request body in same web-services?

I am calling services and want to map the data from path param and from request body. web-services call is type of POST.
Problem here is if i use #Form then I am getting only path param
Here are the scenario.
Case1 Url :- Response updateDepartment(#Form #Valid Department departmentObject)
I am getting path param value as departmentNumber, departmentSNumber and departmentName
but not getting request body which contain addRequest[{ }] and removeRequest[{ }]
In case If i use only #Valid then
Case 2 Url :- Response updateDepartment(#Valid Department departmentObject)
In this case, I am getting addRequest[{ }] and removeRequest[{ }] values.
but Object but now missing with departmentNumber, departmentSNumber and departmentName
Department.java
public class Department
{
#PathParam(DEPTNUMBER_PARAM)
private String deptNumber;
#PathParam(DEPT_S_PARAM)
private String deptSNumber;
#PathParam(DEPARTMENT_NAME)
private String departmentName;
private List<String> addRole;
private List<String> removeRole;
}
How to map both the values form #PathParam as well as request body values.
Here is the call
http://localhost:8080/myweb/departments/1234/4321/finance
{
"addRole": [
101,
181
],
"removeRole": [
42,
12
]
}
Why don't you use this:
Response updateDepartment(#Valid Department departmentObject, #PathParam("departmentNumber") String departmentNumber....)
What you want it will not work. I will try to explain.
First of all, I think you are using resteasy not JAX-RS reference, correct?
In RestEasy for #Form annotation it said:
This can be used as a value object for incoming/outgoing request/responses. You can re-use #*Param annotations on fields/methods of the parameter to unmarshall from the request or marshall to the response depending if you're using server-side JAX-RS or the Resteasy client framework
In your case, you use #PathParam on the fields of the parameter, that is why was populated.
When you do not use #Form, whatever type you specify for the parameter it tries to match with request body, which, in your case contain only the two arrays and not the PathParam. It will not look into the path params for anything else, thus ignoring your annotation.

RestEasy - Jax-rs - Sending custom Object in response body

How do I send my custom object in a response. I just want the values printed from my object.
Lets say I have an object of type Person. I am trying to send in REST response body like this.
ResponseBuilder response = Response.ok().entity(personObj);
return response.build();
But I get 500 error.
Tried this one too:
ResponseBuilder response = Response.status(Status.OK).entity(personObj);
return response.build();
Same error.
Tried setting content type as text/xml. No use.
What am I missing here? I tried googling. But not many examples out there, especially with the custom objects;
It returns fine, if I just pass a string to entity() method.
In order to return data from a Resteasy resource method you need to do several things depending on what you are trying to return.
You need to annotate your resource method with the #Produces
annotation to tell Resteasy what the return type of the method should
be.
For example, the method below returns XML and JSON depending on what the client asks for in their Accept header.
#GET
#Produces({MediaType.APPLICATION_JSON,
MediaType.APPLICATION_XML})
public Response foo()
{
PersonObj obj = new PersonObj();
//Do something...
return Response.ok().entity(obj).build();
}
Resteasy supports marshalling the following datatypes by default:
If the datatypes you wish to support are in this table then that
means they are supported by JAXB and all you need to do is annotate
your PersonObj class with JAXB annotations to tell it how to
marshall and unmarshall the object.
#XmlRootElement
#XmlType(propOrder = {"firstName", "lastName"})
public class PersonObj
{
private String firstName;
private String lastName;
//Getters and Setters Removed For Brevity
}
What if your content-type is not supported out of the box?
If you have a custom content-type that you would like to marshall then you need to create a MessageBodyWriter implementation that will tell Resteasy how to marshall the type.
Provider
#Produces({"application/x-mycustomtype"})
public class MyCustomTypeMessageBodyWriter implements MessageBodyWriter {
}
Just implement the interface and register it like any other Provider.
If you would like to read a custom content-type then you need to implement a custom MessageBodyReader to handle the incoming type and add it to the #Consumes annotation on your receiving method.

Categories

Resources