/* * Copyright 2014 Jakub Jirutka <jakub@jirutka.cz>. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cz.jirutka.spring.exhandler.handlers; import cz.jirutka.spring.exhandler.messages.ErrorMessage; import cz.jirutka.spring.exhandler.messages.ValidationErrorMessage; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.convert.ConversionException; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.util.Assert; import javax.servlet.http.HttpServletRequest; import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolationException; import javax.validation.ElementKind; import javax.validation.Path; import javax.validation.Path.Node; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY; import static org.springframework.util.StringUtils.isEmpty; public class ConstraintViolationExceptionHandler extends ErrorMessageRestExceptionHandler<ConstraintViolationException> { private ConversionService conversionService = new DefaultConversionService(); public ConstraintViolationExceptionHandler() { super(UNPROCESSABLE_ENTITY); } @Override public ValidationErrorMessage createBody(ConstraintViolationException ex, HttpServletRequest req) { ErrorMessage tmpl = super.createBody(ex, req); ValidationErrorMessage msg = new ValidationErrorMessage(tmpl); for (ConstraintViolation<?> violation : ex.getConstraintViolations()) { Node pathNode = findLastNonEmptyPathNode(violation.getPropertyPath()); // path is probably useful only for properties (fields) if (pathNode != null && pathNode.getKind() == ElementKind.PROPERTY) { msg.addError(pathNode.getName(), convertToString(violation.getInvalidValue()), violation.getMessage()); // type level constraints etc. } else { msg.addError(violation.getMessage()); } } return msg; } /** * Conversion service used for converting an invalid value to String. * When no service provided, the {@link DefaultConversionService} is used. * * @param conversionService must not be null. */ @Autowired(required=false) public void setConversionService(ConversionService conversionService) { Assert.notNull(conversionService, "conversionService must not be null"); this.conversionService = conversionService; } private Node findLastNonEmptyPathNode(Path path) { List<Node> list = new ArrayList<>(); for (Iterator<Node> it = path.iterator(); it.hasNext(); ) { list.add(it.next()); } Collections.reverse(list); for (Node node : list) { if (!isEmpty(node.getName())) { return node; } } return null; } private String convertToString(Object invalidValue) { if (invalidValue == null) { return null; } try { return conversionService.convert(invalidValue, String.class); } catch (ConversionException ex) { return invalidValue.toString(); } } }