Correcting Springfox setting allowEmptyValue on path parameter - java

I have a method annotated with #ApiParam as follows:
#RestController
#RequestMapping({LinksBuilder.BASE_URL})
#Api(tags = "Stuff Initiation", description="Stuff Initiation Service")
public class StuffResource {
#ApiOperation(value = "some description", tags = "Stuff Initiation")
#PostMapping(value = "/{stuffProduct}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Stuff InitiationResponse> postInitiateStuff (
#ApiParam(required=true,value="Stuff initiation payload")
#Valid #RequestBody Stuff Initiation stuffInitiation,
#ApiParam(name="stuffProduct", required= true, allowableValues="productStuff1,productStuff2,productStuff3")
#PathVariable String stuffProduct) throws StuffServiceException { ... }
...
}
The issue is that the swagger document generated by springfox (2.9.2) has a "allowEmptyValue":false which is disallowed on a path parameter by the swagger standard.
In an attempt to remedy this, I have implemented a solution similar to springfox hide allowEmptyValue when field annotated with #ApiModelProperty:
package com.example.config;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import com.google.common.base.Optional;
import io.swagger.annotations.ApiParam;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.ParameterBuilderPlugin;
import springfox.documentation.spi.service.contexts.ParameterContext;
import springfox.documentation.swagger.common.SwaggerPluginSupport;
#Component
#Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER + 100)
public class CustomizedParameterBuilderPlugin implements ParameterBuilderPlugin {
#Override
public boolean supports(final DocumentationType arg0) {
return true;
}
#Override
public void apply(ParameterContext context) {
//Optional<ApiModelProperty> annotation = empty();
Optional<ApiParam> apiParam = context.resolvedMethodParameter().findAnnotation(ApiParam.class);
if (apiParam.isPresent()) {
//apiParam.get().allowEmptyValue();
context.parameterBuilder().allowEmptyValue(null);
System.err.println(apiParam.get().name() + "\t" + apiParam.get().type());
}
}
}
I get the right elements, but apparently the setting of context.parameterBuilder().allowEmptyValue(null); doesn't work... the elements are still generated
I am aware that the root cause is a known bug, and is set as status fixed, but I have not got the possibility of using 3.0.0-SNAPSHOT

Related

Catch cast exception in #PathVariable when type is Long and Client sent String(not number)

I have a Spring Boot controller with param #PathVariable long stopPointId, when user will be send request like "url/StopPoints/1" everything work perfect, but when request will be look like "url/StopPoints/StopPointNumber" nothing will be happening. I want to catch this situation and throw my custom error because user need to know, that param only take long value. For instance: You are not able to pass String as parameter. You should use number value e.g 123."
One way would be to handle the NumberFormatException that would be thrown by Spring Boot while trying to typecast a String into a Long.
This is my custom HTTP-Response class, but I trust you have your own...
package com.stackoverflow.rav.StackOverflowExamples.api;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.http.HttpStatus;
import java.util.Date;
public class HttpResponse {
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "MM-dd-yyyy hh:mm:ss", timezone = "UTC")
private Date timeStampUTC;
private int httpStatusCode;
private HttpStatus httpStatus;
private String reason;
private String message;
/* Include setters, getters, constructors, or use Lombok */
}
Then the exception handler... (exception message should be generic in order to increase reusability)
package com.stackoverflow.rav.StackOverflowExamples.api;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
#RestControllerAdvice
public class ExampleExceptionHandler {
#ExceptionHandler(NumberFormatException.class)
public ResponseEntity<HttpResponse> accountDisabledException() {
return createHttpResponse(HttpStatus.BAD_REQUEST, "Should pass long not string!");
}
private ResponseEntity<HttpResponse> createHttpResponse(HttpStatus httpStatus, String message) {
return new ResponseEntity<>(new HttpResponse(
httpStatus.value(),
httpStatus,
httpStatus.getReasonPhrase().toUpperCase(),
message.toUpperCase()),
httpStatus);
}
}
Finally the controller...
package com.stackoverflow.rav.StackOverflowExamples.api;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class ExampleController extends ExampleExceptionHandler {
#GetMapping("/url/{someValue}")
public String hello(#PathVariable("someValue") long someValue) {
return "You entered: " +someValue;
}
}
If all goes well you should get a response like the screen-snippet below when doing http://localhost:8080/url/abcd
This answer might look lengthy, but we are Java developers :D

How to turn off deserialization for enum

I would like to turn off deserialization for concrete enum. Is it possible?
Exercise model class:
package main.exercise;
import lombok.*;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Entity
#Builder
public class Exercise {
#Id
#GeneratedValue
private int id;
#NonNull
private String name;
#NonNull
private ExerciseType exerciseType;
private double caloriesBurned;
private String exerciseDescription;
}
I got method in controller:
#PostMapping("/addExercise")
public List<String> addExercise(#RequestBody Exercise exercise) {
return exerciseCrudActivitiesService.addExercise(exercise);
}
which takes Exercise body and if type of Exercise is wrong I got error while POST http request:
Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `main.exercise.ExerciseType` from String "CARdDIO": not one of the values accepted for Enum class: [CARDIO, WEIGHTLIFTING]; nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `main.exercise.ExerciseType` from String "CARdDIO": not one of the values accepted for Enum class: [CARDIO, WEIGHTLIFTING]
at [Source: (PushbackInputStream); line: 4, column: 25] (through reference chain: main.exercise.Exercise["exerciseType"])]
The point is in service I got validator, which validates whether type of enum is right or wrong and return string to list of errors from all validators. Unfortunately this code cannot be reached because of error.
public List<String> addExercise(Exercise exercise) {
ExerciseValidator validator = new ExerciseValidator();
List<String> messages = validator.validate(exercise);
if (messages.isEmpty()) {
exerciseRepository.save(exercise);
}
return messages;
}
Validator
package main.exercise.validator.attributesvalidators;
import main.exercise.Exercise;
import main.exercise.ExerciseType;
import main.exercise.validator.ExerciseAttributesValidator;
import java.util.InputMismatchException;
public class ExerciseTypeValidator implements ExerciseAttributesValidator {
#Override
public String validate(Exercise exercise) {
if (exercise.getExerciseType() == null) {
return "You didn't put exercise type!";
}
try {
ExerciseType.forName(exercise.getExerciseType().name());
} catch (InputMismatchException e) {
return "Wrong exercise type!";
}
return null;
}
}
To turn off (de-)serialization, you can add the #JsonIgnore to the exerciseType field. However, I don't think this will help you anyways.
If serialization is ignored, the field would always be null which is not the intended behavior.
Your validator is too late. Note: the validate method takes an Exercise object as a parameter. The problem occurs during the creation of this object already.
When you get to the point that the line ExerciseType.forName(exercise.getExerciseType().name()); get's executed, it will NEVER throw an exception, because getExerciseType() is already a valid enum.
Instead of this custom validator, you could make use of a Spring #ControllerAdvice to register your own Exception handler for that error type.
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import com.fasterxml.jackson.databind.exc.InvalidFormatException
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler(InvalidFormatException.class)
public ResponseEntity<?> badFormatException(InvalidFormatException ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST);
}
}
See e.g. https://www.springboottutorial.com/spring-boot-exception-handling-for-rest-services for more details.

Changing Spring RequestMapping annotation value at runtime

I am trying to change the value of the RequestMapping annotation at runtime for a HTTP GET method - hello (which returns a simple string) inside a rest service class - SpringRestController.
The value of the uri defined in the #RequestMapping annotation on the hello method is "/hello/{name}". I am able to change the value of the annotation at runtime to "hi/{name}" using reflection in the constructor of the SpringRestController class.
I am able to verify the modified value by printing the value of the annotation inside an init method annotated with #PostConstruct annotation and also inside another controller. However, when I am trying to access the GET method in a browser:
with the modified value - http://localhost:9090/spring-boot-rest/rest/hi/Pradeep (does not work)
with the original value - http://localhost:9090/spring-boot-rest/rest/hello/Pradeep (works fine)
I expect the HTTP GET method hello to be accessible using the modified path value at runtime - "/hi/{name}" instead of the original path value - "/hello/{name}".
P.S - This is a requirement for us and needs to be done this way so that value of #RequestMapping can be configured externally without changes to the source code.
Here is the code - SpringRestController.java
package com.example.spring.rest.controller;
import javax.annotation.PostConstruct;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.spring.rest.custom.annotations.ConfigurableRequestMapping;
import com.example.spring.rest.reflection.ReflectionUtils;
#RestController
#RequestMapping("/rest")
public class SpringRestController {
public SpringRestController() {
RequestMapping rm = SpringRestController.class.getAnnotation(RequestMapping.class);
System.out.println("Old annotation : " + rm.value()[0]);
RequestMapping rmNew = new ConfigurableRequestMapping("/rest");
ReflectionUtils.alterAnnotationValueJDK8_v2(SpringRestController.class, RequestMapping.class, rmNew);
RequestMapping rmModified = SpringRestController.class.getAnnotation(RequestMapping.class);
System.out.println("Constructor -> New annotation : " + rmModified.value()[0]);
}
#RequestMapping(value = "/hello/{name}")
public String hello(#PathVariable String name) {
System.out.println("Name : " + name);
return "Hello " + name;
}
#PostConstruct
private void init(){
System.out.println("Annotations initialization post construct.");
RequestMapping rmModified = SpringRestController.class.getAnnotation(RequestMapping.class);
System.out.println("Init method -> New annotation : " + rmModified.value()[0]);
}
}
Code for changing annotation value -
ReflectionUtils.java
package com.example.spring.rest.reflection;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.springframework.web.bind.annotation.RequestMapping;
import com.example.spring.rest.controller.SpringRestController;
import com.example.spring.rest.custom.annotations.ConfigurableRequestMapping;
import com.example.spring.rest.utils.PropertyReader;
public class ReflectionUtils {
#SuppressWarnings("unchecked")
public static Object changeAnnotationValue(Annotation annotation, String key, Object newValue){
Object handler = Proxy.getInvocationHandler(annotation);
Field f;
try {
f = handler.getClass().getDeclaredField("memberValues");
} catch (NoSuchFieldException | SecurityException e) {
throw new IllegalStateException(e);
}
f.setAccessible(true);
Map<String, Object> memberValues;
try {
memberValues = (Map<String, Object>) f.get(handler);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
Object oldValue = memberValues.get(key);
if (oldValue == null || oldValue.getClass() != newValue.getClass()) {
throw new IllegalArgumentException();
}
memberValues.put(key,newValue);
return oldValue;
}
}
This is not possible to change the annotation value in runtime since Spring already registered that value. Aside from being curious about what do you really try to achieve, feel free to use multiple #PathVariable parameters, and handle the evaluation yourself.
// Either hardcoded values or loaded from elsewhere
private static List<String> GREETINGS = Arrays.asList("Hello", "Hi");
...
#GetMapping(value = "/{greeting}/{name}")
public String greeting(#PathVariable String greeting, #PathVariable String name) {
System.out.println("Name : " + name);
if (GREETINGS.stream().anyMatch(greeting::equalsIgnoreCase)) {
return greeting + " " + name;
}
throw new ResponseStatusException(HttpStatus.BAD_REQUEST,
"Unknown greeting " + greeting, e);
}
Moreover, the point of the REST API endpoints is to be predictable. What you try to achieve seems like a contradiction to it. You can have multiple endpoints such as /hi/{name} and /hello/{name}, however, in this particular case either the usage of multiple parameters is correct, or this following endpoint that respects the resource and uses #RequestParam. I'd design it rather with this way since greeting is the resource.
A sample endpoint: /greeting?greeting={greeting}&name={name}
A sample call: /greeting?greeting=Hello&name=Pradeep%20Prabhakaran

Spring 4 Request driven Bean creation

I am implementing a Rest WS using Spring 4 (Spring Boot).
The basic idea is I want to consume a JSON payload specifying an identifier (e.g. social security number or something) and run multiple subServices on that identifier.
Here is a sample payload:
{
"ssNumber" : "1111111111111111",
"subServicesDetails" :
[
{ "subServiceName" : "Foo" , "requestParameters" : {} },
{ "subServiceName" : "Dummy", "requestParameters" : {} }
]
}
In my code I have multiple "sub-services" (FooService, DummyService) implementing the SubService interface:
package com.johnarnold.myws.service;
import com.johnarnold.myws.model.SubServiceDetails;
public interface SubService {
public boolean service(String ssNumber, SubServiceDetails ssd);
}
And below is the FooService code.
package com.johnarnold.myws.service;
import java.util.HashMap;
import java.util.Map;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.johnarnold.myws.dao.FooDao;
import com.johnarnold.myws.model.Foo;
import com.johnarnold.myws.model.SubServiceDetails;
#Component
public class FooService implements SubService{
private static Logger log = Logger.getLogger(FooService.class);
#Autowired
private FooDao dao;
public FooService()
{
log.debug("FooService ctor");
}
public boolean service(String ssNumber, SubServiceDetails ssd)
{
log.debug("FooService service");
Map <String, String> responseParameters = new HashMap<String, String>();
try
{
Foo foo = dao.getFoo(ssNumber);
if(foo.isCompromised())
{
responseParameters.put("listed", "true");
}
else
{
responseParameters.put("listed", "false");
}
ssd.setResponseParameters(responseParameters);
return true;
}
catch(Throwable ex)
{
log.error("Exception in service ", ex);
}
return false;
}
}
Now I wrote my own factory to create the subservices but when I did that of course because I am explictly creating my beans (e.g. FooService) below - my container is not auomatically injecting any of the #Autowired members - FooDao for example:
package com.johnarnold.myws.service;
public class SubServiceFactory {
/*
* Instantiates a SubService for the supplied subServiceName or throws an exception if
* no valid SubService exists
*/
public static SubService createSubService(String subServiceNameStr)
{
SubService subService = null;
System.out.println("subServiceName [" + subServiceNameStr + "]");
if(subServiceNameStr.equals("Foo"))
{
subService = new FooService();
}
if(subServiceNameStr.equals("Dummy"))
{
subService = new DummyService();
}
else
{
System.out.println("subServiceName [" + subServiceNameStr + "] is not defined");
}
return subService;
}
}
For completeness here is the Controller:
package com.johnarnold.myws.controller;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import org.apache.log4j.Logger;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.johnarnold.myws.model.RawsPayload;
import com.johnarnold.myws.model.SubServiceDetails;
import com.johnarnold.myws.service.SubService;
import com.johnarnold.myws.service.SubServiceFactory;
import com.johnarnold.myws.web.ValidMessage;
#RestController
#RequestMapping("/raws/")
public class RawsController {
private static final Logger logger = Logger.getLogger(RawsController.class);
//#Autowired
//SubService [] subSvcs;
#RequestMapping(value="/{version}/status", method=RequestMethod.GET)
public ResponseEntity<ValidMessage> getServiceStatus()
{
return new ResponseEntity<>(new ValidMessage() , HttpStatus.OK);
}
/*
* Main entry point - orchestrates all of the WS Sub Services
*/
#RequestMapping(value="/{version}/raws", method=RequestMethod.PUT)
public ResponseEntity<String> raws(#Valid #RequestBody RawsPayload rawsPayload,
HttpServletRequest request)
{
logger.info("Request received");
System.out.println("payl " + rawsPayload);
System.out.println("ssNumber=" + rawsPayload.getSsNumber());
System.out.println("sub svcs details=" + rawsPayload.getSubServicesDetails().length);
SubServiceDetails[] subServiceDetails = rawsPayload.getSubServicesDetails();
for(SubServiceDetails ssd : subServiceDetails)
{
String subServiceNameStr = ssd.getSubServiceName();
System.out.println("svcname=" + subServiceNameStr);
System.out.println("svc req params=" + ssd.getRequestParameters());
System.out.println("svc resp params=" + ssd.getResponseParameters());
SubService subService = SubServiceFactory.createSubService(subServiceNameStr);
// Probably wrap the below with some timings
subService.service(rawsPayload.getSsNumber(), ssd);
}
//System.out.println("svcs are " + subSvcs + "size=" + subSvcs.length);
return new ResponseEntity<>("foo" , HttpStatus.OK);
}
}
And here is the main payload class:
package com.johnarnold.myws.model;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.apache.log4j.Logger;
import org.hibernate.validator.constraints.Length;
public class RawsPayload {
static Logger log = Logger.getLogger(RawsPayload.class);
#NotNull
#Length(min=16, max=19)
private String ssNumber;
#Valid
#NotNull
#Size(min=1, max=3)
private SubServiceDetails [] subServicesDetails;
public String getSsNumber() {
return ssNumber;
}
public void setSsNumber(String ssNumber) {
log.info("setSsNumber()");
this.ssNumber = ssNumber;
}
public SubServiceDetails[] getSubServicesDetails() {
return subServicesDetails;
}
public void setSubServicesDetails(SubServiceDetails[] subServicesDetails) {
this.subServicesDetails = subServicesDetails;
}
}
I've read a number of answers on StackOverflow regarding Spring 4 Conditional Beans - but this functionality appears to be targeted at Context / Configuration type information rather than Request message content (as in this case).
Can anyone point me in the right direction. I can provide further context if necessary
KRgds
John
Two possible ways of solving this problem:
Add all your subService beans to the Spring context then select from them using a ServiceLocatorFactoryBean. This is the nicer approach (from architectural point of view), but it might require a bit more time to implement if you have never used this concept before.
There is a simpler alternative below if you want to stick with basic Spring solutions:
Have the subservice beans injected into your main service as a list, and then select from that. It would look something like this:
#Bean
public List<SubService> subServices(){
List<SubService> list = new SubService<>();
list.add(new AService());
list.add(new BService());
return list;
}
THEN
public SubService selectServiceByName() {
//iterate through the list, pick the service with the right name and return - this solution will require you to bind by beannames
}
#john-arnold First, crate all the services like this, or annotate them with #Service/#Component with explicit names like below: names are start with the values of subServiceName param and contains a common suffix, "Service" here, thats important.
#Bean("FooService")
public SubService fooService() {
return new FooService();
}
#Bean("DummyService")
public SubService dummyService() {
return new DummyService();
}
Then change your factory like this:
#Component
public class SubServiceFactory implements BeanFactoryAware{
private BeanFactory beanFactory;
private static final String MY_SERVICE_SUFFIX = "Service";
#Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
public <T> T getServiceImplementation(String name, Class<T> requiredType) {
return beanFactory.getBean(name + MY_SERVICE_SUFFIX, requiredType);
}
}
Now what we have here is a BeanFactoryAware class that you can inject to your Rest Endpoint and instead of if statement, try this:
subServiceFactory.getServiceImplementation(subServiceNameStr, SubService.class);
This will return your bean or an exception if it doesn't find one. If you don't want an exception, you can catch it and return null or you can create a Service imp. just for these and return that instance. Your choice.
Edit:
As a shortcut, you can define your imp. Beans and than add this to your rest endpoint
#Autowired
private Map<String, SubService> mySubServices;
Spring will automatically inject all your imp. ref. so you can just use get() method of the map. But i prefer the first one..
You don't need anything fancy here. Just implement all your services that implement your service interface, annotate them all with either #Component or #Service and scan them as usual.
Then, wherever you have to choose a concrete service implementation, autowire all implementations of your service like this:
#Autowired
Map<String, SubService> subServices;
The key of the map will be the name of the service as specified in the #Component annotation of every sub service implementation, and the value will be the instance.
So, when you receive you JSON, just get the name of the sub service (i.e. Foo), and get the specific service of the map:
SubService fooSubService = subServices.get(subServiceName + "Service");
where subServiceName is the uncapitalized name of the sub service you're receiving in your JSON (i.e. if you're receiving Foo this would be foo).
The convention is to use the uncapitalized name of the class that implements the interface as the bean name, i.e. for the FooService class the bean name will be fooService, and this is the key you have to look for in the map.

In Java, how do I return meaningful, JSON formatted errors from Resteasy validation?

I have a RESTFul API consuming/returning JSON in the request/response body. When the client sends invalid data (valid JSON but invalid values for the fields) I want to be able to return a JSON structure (as well as the relevant 400+ code).
This structure would then allow the frontend to parse the errors on a per-field basis and render the errors alongside the input fields.
E.g. ideal output:
{
"errors":{
"name":["invalid chars","too long","etc"]
"otherfield":["etc"]
}
}
I am using Resteasy for the API, and using violation exceptions it's fairly easy to get it to render JSON errors:
#Provider
#Component
public class ValidationExceptionHandler implements ExceptionMapper<ResteasyViolationException> {
public Response toResponse(ResteasyViolationException exception) {
Multimap<String,String> errors = ArrayListMultimap.create();
Consumer<ResteasyConstraintViolation> consumer = (violation) -> {
errors.put(violation.getPath(), violation.getMessage());
};
exception.getParameterViolations().forEach(consumer);
Map<String, Map<String, Collection<String>>> top = new HashMap<>();
top.put("errors", errors.asMap());
return Response.status(Status.BAD_REQUEST).entity(top)
.build();
}
}
However, the error paths (violation.getPath()) are property-centric rather than XmlElement-name-centric.
E.g. the above outputs:
{
"errors":{"createCampaign.arg1.name":["invalid chars","etc"]}
}
I have tried stripping the index back from the last dot to get "name" but there are other issues with that hack.
E.g. if my "name" property isn't "name" it doesn't work:
#XmlElement(name="name")
#NotNull
private String somethingelse;
"somethingelse" will be returned to client, but they client has no idea what that is:
{
"errors":{"somethingelse":["cannot be null"]}
}
The client wants "name" since that is what the field was called when they sent it.
My resource:
package com.foo.api;
import org.springframework.stereotype.Service;
import javax.validation.Valid;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import com.foo.dto.CarDTO;
#Service
#Path("/car")
public class CarResource {
#POST
#Produces({MediaType.APPLICATION_JSON})
#Consumes(MediaType.APPLICATION_JSON)
public CarDTO create(
#Valid CarDTO car
) {
//do some persistence
return car;
}
}
example dto:
package com.foo.dto;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Min;
import javax.validation.constraints.Max;
import javax.xml.bind.annotation.XmlElement;
public class CarDTO {
#Min(1)
#Max(10)
#NotNull
#XmlElement(name="gears")
private int cogs;
}
This article describes quite well what you need to do.
Basically you should implement an ExceptionMapper.
#Provider
public class ValidationExceptionMapper implements ExceptionMapper<ValidationException> {
#Override
public Response toResponse(ValidationException exception) {
Response myResponse;
// build your Response based on the data provided by the exception
return myResponse;
}
}
A custom error message can be used so you wouldnt need to look at the path
#NotNull(message="name cannot be null")

Categories