Java SpringValidator(net.sf.oval):递归错误报告

Java SpringValidator(net.sf.oval):递归错误报告,java,recursion,bean-validation,Java,Recursion,Bean Validation,我正在重构应用程序中的一个页面,并试图对本身具有验证约束的对象执行递归验证。然而,发生的情况是,我只得到一个错误,描述对象无效。我想要的是验证嵌套对象返回页面时出现的错误 ConsumerManagementController.java @Controller @SessionAttributes(ConsumerManagementController.CMD_NAME) public class ConsumerManagementController { private org

我正在重构应用程序中的一个页面,并试图对本身具有验证约束的对象执行递归验证。然而,发生的情况是,我只得到一个错误,描述对象无效。我想要的是验证嵌套对象返回页面时出现的错误

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());
    }
}
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);
}
}

其次,我创建了另一个扩展net.sf.oval.validator的验证器:

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());
    }
}
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);
}
我称之为验证:

// Validate the form
validator.validate(cmd, errors);
现在是命令对象。因为命令对象上有两个对象,所以我需要能够根据条件验证每个对象。该资产对javascript EL有效:

// 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();
现在,当我验证命令对象时,需要验证的嵌套对象将被处理,错误将上升到命令对象级别