4

I have implemented the JSR303 JavaBean Validation in the Service layer of my web app (according to this article). Now I want to convert all validation exceptions (e.g. javax.validation.ConstraintViolationException) to my custom exception.

I created an Aspect that is invoked whenever an exception is thrown in the Service layer:

@Aspect
public class ExceptionConversionAspect {

    @AfterThrowing(pointcut="execution(* com.myexample.service.*.*(..))", throwing="e")
    public void convertServiceException(Exception e) {

        if (e instanceof ConstraintViolationException) {
             // this is my custom exception
            throw new InvalidServiceInputException("The service inputs failed validation", e);
        }
    }
}

But my exception conversion aspect is not triggered when my service fails validation with ConstraintViolationException. I suspect this is because the validation exception is itself triggered by an Aspect:

@Aspect
public class ValidateAspect {

    @Autowired
    private Validator validator;

    // Match any public methods in a class annotated with @AutoValidating
    @Around("execution(public * *(..)) && @within(com.myexample.validator.annotation.Validate)")
    public Object validateMethodInvocation(ProceedingJoinPoint pjp) throws Throwable {

    // some code here
    ....
}

How do I chain my aspects in the correct order? ValidateAspect first, followed by ExceptionConversionAspect?

schnitz
  • 190
  • 2
  • 9
citress
  • 889
  • 3
  • 13
  • 35
  • Maybe it would help to create aspect for validateMethodInvocation method - it would be triggered by exception thrown by that method or override ValidateAspect so it is throwing proper exception? – Piotr Kochański Apr 20 '12 at 13:49

2 Answers2

1

Raul Bertone is almost right, but not quite. The logic must be reversed and the ExceptionConversionAspect must be the first in precedence.

Fully working sample for Java SE (I just emulate the Java EE exception):

Helper classes:

package javax.validation;

public class ConstraintViolationException extends RuntimeException {
    private static final long serialVersionUID = -8041265519275356912L;

    public ConstraintViolationException(String arg0) {
        super(arg0);
    }
}
package com.myexample.validator.annotation;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface Validate {}
package com.myexample.service;

public class InvalidServiceInputException extends RuntimeException {
    public InvalidServiceInputException(String arg0, Throwable arg1) {
        super(arg0, arg1);
    }
}

Sample driver application:

The driver application is annotated by @Validate and emulates a service - see package name. It loops through 10 method calls, catching exceptions and printing them to standard output so as to show that they are indeed converted as desired.

package com.myexample.service;

import com.myexample.validator.annotation.Validate;

@Validate
public class Application {
    public void doSomething(int i) {
        System.out.printf("Doing something #%d%n", i);
    }

    public static void main(String[] args) {
        Application application = new Application();
        for (int i = 0; i < 10; i++) {
            try {
                application.doSomething(i + 1);
            }
            catch (Exception e) {
                System.out.println(e);
                System.out.println("  cause: " + e.getCause());
            }
        }
    }
}

Aspects:

The validation aspect randomly throws a ConstraintViolationException for demo purposes.

package com.myexample.aspect;

import java.util.Random;
import javax.validation.ConstraintViolationException;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class ValidateAspect {
    private static final Random RANDOM = new Random();

    @Around("execution(public !static * *(..)) && @within(com.myexample.validator.annotation.Validate)")
    public Object validateMethodInvocation(ProceedingJoinPoint thisJoinPoint) throws Throwable {
        Object result = thisJoinPoint.proceed();
        if (RANDOM.nextBoolean())
            throw new ConstraintViolationException("uh-oh");
        return result;
    }
}

The exception conversion aspect has an additional @DeclarePrecedence annotation now.

package com.myexample.aspect;

import javax.validation.ConstraintViolationException;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclarePrecedence;
import com.myexample.service.InvalidServiceInputException;

@Aspect
@DeclarePrecedence("ExceptionConversionAspect, *")
public class ExceptionConversionAspect {
    @AfterThrowing(pointcut = "execution(* com.myexample.service..*(..))", throwing = "e")
    public void convertServiceException(Exception e) {
        if (e instanceof ConstraintViolationException) {
            throw new InvalidServiceInputException("The service inputs failed validation", e);
        }
    }
}

Console output:

Doing something #1
Doing something #2
com.myexample.service.InvalidServiceInputException: The service inputs failed validation
  cause: javax.validation.ConstraintViolationException: uh-oh
Doing something #3
com.myexample.service.InvalidServiceInputException: The service inputs failed validation
  cause: javax.validation.ConstraintViolationException: uh-oh
Doing something #4
Doing something #5
Doing something #6
com.myexample.service.InvalidServiceInputException: The service inputs failed validation
  cause: javax.validation.ConstraintViolationException: uh-oh
Doing something #7
Doing something #8
Doing something #9
Doing something #10
kriegaex
  • 63,017
  • 15
  • 111
  • 202
0

When several aspects share a common joinpoint, you can manually set the execution order using the @DeclarePrecedence statement. In your case you can create a new aspect:

@Aspect
@DeclarePrecedence("ValidateAspect, ExceptionConversionAspect")
    public class SystemArchitecture {
        // ...
    }

If no precedence is specified, the execution order defaults to the rules defined here

Raul Bertone
  • 146
  • 7