* @return a Resolution if any interceptor determines that the request processing should
* be aborted in favor of another Resolution, null otherwise.
*/
public static Resolution doCustomValidation(final ExecutionContext ctx,
final boolean alwaysInvokeValidate) throws Exception {
final ValidationErrors errors = ctx.getActionBeanContext().getValidationErrors();
final ActionBean bean = ctx.getActionBean();
final Method handler = ctx.getHandler();
final boolean doBind = handler != null && handler.getAnnotation(DontBind.class) == null;
final boolean doValidate = doBind && handler.getAnnotation(DontValidate.class) == null;
Configuration config = StripesFilter.getConfiguration();
// Run the bean's methods annotated with @ValidateMethod if the following conditions are met:
// l. This event is not marked to bypass binding
// 2. This event is not marked to bypass validation (doValidate == true)
// 3. We have no errors so far OR alwaysInvokeValidate is true
if (doValidate) {
ctx.setLifecycleStage(LifecycleStage.CustomValidation);
ctx.setInterceptors(config.getInterceptors(LifecycleStage.CustomValidation));
return ctx.wrap( new Interceptor() {
public Resolution intercept(ExecutionContext context) throws Exception {
// Run any of the annotated validation methods
Method[] validations = findCustomValidationMethods(bean.getClass());
for (Method validation : validations) {
ValidationMethod ann = validation.getAnnotation(ValidationMethod.class);
boolean run = (ann.when() == ValidationState.ALWAYS)
|| (ann.when() == ValidationState.DEFAULT && alwaysInvokeValidate)
|| errors.isEmpty();
if (run && applies(ann, ctx.getActionBeanContext().getEventName())) {
Class<?>[] args = validation.getParameterTypes();
if (args.length == 1 && args[0].equals(ValidationErrors.class)) {
validation.invoke(bean, errors);