SpringValidator(net.sf.oval):递归报错
SpringValidator (net.sf.oval): Recursive Error Reporting
我正在我的应用程序中重构一个页面,并试图对本身具有验证约束的对象执行递归验证。然而,发生了什么,我只得到一个描述对象无效的错误。我想要的是嵌套对象的验证错误 return 到页面。
ConsumerManagementController.java
@Controller
@SessionAttributes(ConsumerManagementController.CMD_NAME)
public class ConsumerManagementController {
private org.springframework.validation.Validator validator;
public ConsumerManagementController() {
validator = new net.sf.oval.integration.spring.SpringValidator.SpringValidator(new net.sf.oval.Validator());
}
@RequestMapping(value = FORM_VIEW, method = RequestMethod.POST)
protected ModelAndView processAdjustment(HttpServletRequest request,
Model model,
@ModelAttribute(CMD_NAME) ConsumerManagementCommand cmd,
BindingResult errors) throws Exception {
...
// Validating the ConsumerManagementCommand object
validator.validate(cmd, errors);
// RESULT
// org.springframework.validation.BeanPropertyBindingResult: 1 errors
// Field error in object 'cmc' on field 'consumerAdjustment': rejected value [[com.company.core.dto.ConsumerAdjustment - memo=, -- ]]; codes [net.sf.oval.constraint.AssertValid.cmc.consumerAdjustment,net.sf.oval.constraint.AssertValid.consumerAdjustment,net.sf.oval.constraint.AssertValid.com.company.core.dto.ConsumerAdjustment,net.sf.oval.constraint.AssertValid]; arguments []; default message [com.company.web.ops.commands.consumer.account.ConsumerManagementCommand.consumerAdjustment is invalid]
// Validating the ConsumerAdjustment object
ConsumerAdjustment consumerAdjustment = cmd.getConsumerAdjustment();
BeanPropertyBindingResult consumerAdjustmentErrors = new BeanPropertyBindingResult(consumerAdjustment, "consumerAdjustment");
validator.validate(consumerAdjustment, consumerAdjustmentErrors);
// RESULT
// org.springframework.validation.BeanPropertyBindingResult: 2 errors
// Field error in object 'consumerAdjustment' on field 'memo': rejected value []; codes [consumerAdjustment.memo.err.null.consumerAdjustment.memo,consumerAdjustment.memo.err.null.memo,consumerAdjustment.memo.err.null.java.lang.String,consumerAdjustment.memo.err.null]; arguments []; default message [com.company.core.dto.ConsumerAdjustment.memo cannot be empty]
...
}
}
ConsumerManagementCommand.java
@Guarded
public class ConsumerManagementCommand implements Serializable{
@AssertValid
private ConsumerAdjustment consumerAdjustment = new ConsumerAdjustment();
}
ConsumerAdjustment.java
public class ConsumerAdjustment extends AbstractDTO implements Serializable {
@NotNull(errorCode = ERR_MEMO_NULL)
@NotEmpty(errorCode = ERR_MEMO_NULL)
@Length(max = 500, errorCode = ERR_MEMO_LENGTH)
private String memo;
}
查看错误报告的结果注释 ConsumerManagementController。
过一会儿再回到这个问题。我想出了如何实现我想要的。我不会详细介绍太多,但解决方案的根源相当复杂。
首先,我必须通过扩展 SpringValidator 创建自己的验证器:
class ConsumerManagementCommandSpringValidator extends SpringValidator{
Logger log = org.slf4j.LoggerFactory.getLogger(ConsumerManagementCommandSpringValidator.class);
public ConsumerManagementCommandSpringValidator(net.sf.oval.Validator validator) {
super(validator);
}
/**
* {@inheritDoc}
* Overridden method here to handle the nested validated objects of the {@link ConsumerManagementCommand}
* type. If a nested object fails validation, we need to identify the path to the field using
* the object name.
*/
@Override
public void validate(final Object objectToValidate, final Errors errors)
{
try
{
for (final ConstraintViolation violation : super.getValidator().validate(objectToValidate))
{
final OValContext ctx = violation.getContext();
final String errorCode = violation.getErrorCode();
final String errorMessage = violation.getMessage();
if (ctx instanceof FieldContext) {
Field field = ((FieldContext) ctx).getField();
String fieldName = field.getName();
try{
errors.rejectValue(fieldName, errorCode, errorMessage);
}catch(NotReadablePropertyException nrpe){
// Resolve the property location based on the object. Uses the class
// name of the field, tweaks the case and concatenates the value
StringBuilder objectReference = new StringBuilder(field.getDeclaringClass().getSimpleName());
fieldName = objectReference.substring(0, 1).toLowerCase().concat(objectReference.substring(1)).concat(".").concat(fieldName);
errors.rejectValue(fieldName, errorCode, errorMessage);
}
} else{
errors.reject(errorCode, errorMessage);
}
}
} catch (final ValidationFailedException ex) {
log.error("Unexpected error during validation", ex);
errors.reject(ex.getMessage());
}
}
}
其次,我创建了另一个扩展 net.sf.oval.Validator:
的验证器
class ConsumerManagementCommandValidator extends net.sf.oval.Validator{
@Override
protected void checkConstraintAssertValid(
List<ConstraintViolation> violations, AssertValidCheck check,
Object validatedObject, Object valueToValidate,
OValContext context, String[] profiles) throws OValException {
if (valueToValidate == null) return;
// ignore circular dependencies
if (isCurrentlyValidated(valueToValidate)) return;
// changed here access to private class variable to getter
final List<ConstraintViolation> additionalViolations = getCollectionFactory().createList();
validateInvariants(valueToValidate, additionalViolations, profiles);
if (additionalViolations.size() != 0) {
final String errorMessage = renderMessage(context, valueToValidate, check.getMessage(), check.getMessageVariables());
violations.add(new ConstraintViolation(check, errorMessage, validatedObject, valueToValidate, context, additionalViolations));
//add the violations to parent list :-)
violations.addAll(additionalViolations);
}
}
@Override
public List<ConstraintViolation> validate(Object validatedObject, String... profiles) throws IllegalArgumentException, ValidationFailedException {
// TODO Auto-generated method stub
return super.validate(validatedObject, profiles);
}
}
现在,我可以创建我的验证器以供使用:
private ConsumerManagementCommandSpringValidator validator = new ConsumerManagementCommandSpringValidator(new ConsumerManagementCommandValidator());
我在哪里调用验证:
// Validate the form
validator.validate(cmd, errors);
现在是命令对象。由于命令对象上有两个对象,因此我需要能够根据条件验证每个对象。 javascript EL:
的 AssertValid
// Custom assertion here, when the selected option is defined, only validate which is necessary
// We're using javascript expression language to do the comparison
@AssertValid(when="js:_this.selectedOption == com.company.core.adjustment.ConsumerManagementOption.ADJUST_CARD_VALUE")
private ConsumerAdjustment consumerAdjustment = new ConsumerAdjustment();
@AssertValid(when="js:_this.selectedOption == com.company.core.adjustment.ConsumerManagementOption.COMBINE_CARDS")
private ConsumerCombineAccounts consumerCombineAccounts = new ConsumerCombineAccounts();
现在,当我验证命令对象时,需要验证的嵌套对象将得到处理,错误将上升到命令对象级别。
我正在我的应用程序中重构一个页面,并试图对本身具有验证约束的对象执行递归验证。然而,发生了什么,我只得到一个描述对象无效的错误。我想要的是嵌套对象的验证错误 return 到页面。
ConsumerManagementController.java
@Controller
@SessionAttributes(ConsumerManagementController.CMD_NAME)
public class ConsumerManagementController {
private org.springframework.validation.Validator validator;
public ConsumerManagementController() {
validator = new net.sf.oval.integration.spring.SpringValidator.SpringValidator(new net.sf.oval.Validator());
}
@RequestMapping(value = FORM_VIEW, method = RequestMethod.POST)
protected ModelAndView processAdjustment(HttpServletRequest request,
Model model,
@ModelAttribute(CMD_NAME) ConsumerManagementCommand cmd,
BindingResult errors) throws Exception {
...
// Validating the ConsumerManagementCommand object
validator.validate(cmd, errors);
// RESULT
// org.springframework.validation.BeanPropertyBindingResult: 1 errors
// Field error in object 'cmc' on field 'consumerAdjustment': rejected value [[com.company.core.dto.ConsumerAdjustment - memo=, -- ]]; codes [net.sf.oval.constraint.AssertValid.cmc.consumerAdjustment,net.sf.oval.constraint.AssertValid.consumerAdjustment,net.sf.oval.constraint.AssertValid.com.company.core.dto.ConsumerAdjustment,net.sf.oval.constraint.AssertValid]; arguments []; default message [com.company.web.ops.commands.consumer.account.ConsumerManagementCommand.consumerAdjustment is invalid]
// Validating the ConsumerAdjustment object
ConsumerAdjustment consumerAdjustment = cmd.getConsumerAdjustment();
BeanPropertyBindingResult consumerAdjustmentErrors = new BeanPropertyBindingResult(consumerAdjustment, "consumerAdjustment");
validator.validate(consumerAdjustment, consumerAdjustmentErrors);
// RESULT
// org.springframework.validation.BeanPropertyBindingResult: 2 errors
// Field error in object 'consumerAdjustment' on field 'memo': rejected value []; codes [consumerAdjustment.memo.err.null.consumerAdjustment.memo,consumerAdjustment.memo.err.null.memo,consumerAdjustment.memo.err.null.java.lang.String,consumerAdjustment.memo.err.null]; arguments []; default message [com.company.core.dto.ConsumerAdjustment.memo cannot be empty]
...
}
}
ConsumerManagementCommand.java
@Guarded
public class ConsumerManagementCommand implements Serializable{
@AssertValid
private ConsumerAdjustment consumerAdjustment = new ConsumerAdjustment();
}
ConsumerAdjustment.java
public class ConsumerAdjustment extends AbstractDTO implements Serializable {
@NotNull(errorCode = ERR_MEMO_NULL)
@NotEmpty(errorCode = ERR_MEMO_NULL)
@Length(max = 500, errorCode = ERR_MEMO_LENGTH)
private String memo;
}
查看错误报告的结果注释 ConsumerManagementController。
过一会儿再回到这个问题。我想出了如何实现我想要的。我不会详细介绍太多,但解决方案的根源相当复杂。
首先,我必须通过扩展 SpringValidator 创建自己的验证器:
class ConsumerManagementCommandSpringValidator extends SpringValidator{
Logger log = org.slf4j.LoggerFactory.getLogger(ConsumerManagementCommandSpringValidator.class);
public ConsumerManagementCommandSpringValidator(net.sf.oval.Validator validator) {
super(validator);
}
/**
* {@inheritDoc}
* Overridden method here to handle the nested validated objects of the {@link ConsumerManagementCommand}
* type. If a nested object fails validation, we need to identify the path to the field using
* the object name.
*/
@Override
public void validate(final Object objectToValidate, final Errors errors)
{
try
{
for (final ConstraintViolation violation : super.getValidator().validate(objectToValidate))
{
final OValContext ctx = violation.getContext();
final String errorCode = violation.getErrorCode();
final String errorMessage = violation.getMessage();
if (ctx instanceof FieldContext) {
Field field = ((FieldContext) ctx).getField();
String fieldName = field.getName();
try{
errors.rejectValue(fieldName, errorCode, errorMessage);
}catch(NotReadablePropertyException nrpe){
// Resolve the property location based on the object. Uses the class
// name of the field, tweaks the case and concatenates the value
StringBuilder objectReference = new StringBuilder(field.getDeclaringClass().getSimpleName());
fieldName = objectReference.substring(0, 1).toLowerCase().concat(objectReference.substring(1)).concat(".").concat(fieldName);
errors.rejectValue(fieldName, errorCode, errorMessage);
}
} else{
errors.reject(errorCode, errorMessage);
}
}
} catch (final ValidationFailedException ex) {
log.error("Unexpected error during validation", ex);
errors.reject(ex.getMessage());
}
}
}
其次,我创建了另一个扩展 net.sf.oval.Validator:
的验证器class ConsumerManagementCommandValidator extends net.sf.oval.Validator{
@Override
protected void checkConstraintAssertValid(
List<ConstraintViolation> violations, AssertValidCheck check,
Object validatedObject, Object valueToValidate,
OValContext context, String[] profiles) throws OValException {
if (valueToValidate == null) return;
// ignore circular dependencies
if (isCurrentlyValidated(valueToValidate)) return;
// changed here access to private class variable to getter
final List<ConstraintViolation> additionalViolations = getCollectionFactory().createList();
validateInvariants(valueToValidate, additionalViolations, profiles);
if (additionalViolations.size() != 0) {
final String errorMessage = renderMessage(context, valueToValidate, check.getMessage(), check.getMessageVariables());
violations.add(new ConstraintViolation(check, errorMessage, validatedObject, valueToValidate, context, additionalViolations));
//add the violations to parent list :-)
violations.addAll(additionalViolations);
}
}
@Override
public List<ConstraintViolation> validate(Object validatedObject, String... profiles) throws IllegalArgumentException, ValidationFailedException {
// TODO Auto-generated method stub
return super.validate(validatedObject, profiles);
}
}
现在,我可以创建我的验证器以供使用:
private ConsumerManagementCommandSpringValidator validator = new ConsumerManagementCommandSpringValidator(new ConsumerManagementCommandValidator());
我在哪里调用验证:
// Validate the form
validator.validate(cmd, errors);
现在是命令对象。由于命令对象上有两个对象,因此我需要能够根据条件验证每个对象。 javascript EL:
的 AssertValid// Custom assertion here, when the selected option is defined, only validate which is necessary
// We're using javascript expression language to do the comparison
@AssertValid(when="js:_this.selectedOption == com.company.core.adjustment.ConsumerManagementOption.ADJUST_CARD_VALUE")
private ConsumerAdjustment consumerAdjustment = new ConsumerAdjustment();
@AssertValid(when="js:_this.selectedOption == com.company.core.adjustment.ConsumerManagementOption.COMBINE_CARDS")
private ConsumerCombineAccounts consumerCombineAccounts = new ConsumerCombineAccounts();
现在,当我验证命令对象时,需要验证的嵌套对象将得到处理,错误将上升到命令对象级别。