Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handling ValidationExceptions in interceptors #873

Merged
merged 13 commits into from
Dec 29, 2014
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import br.com.caelum.vraptor.interceptor.StepInvoker;
import br.com.caelum.vraptor.ioc.Container;

import br.com.caelum.vraptor.observer.ExecuteMethodExceptionHandler;
import com.google.common.base.Supplier;

/**
Expand All @@ -44,25 +45,28 @@ public class DefaultInterceptorHandlerFactory implements InterceptorHandlerFacto
private final InterceptorAcceptsExecutor acceptsExecutor;
private final CustomAcceptsExecutor customAcceptsExecutor;
private final InterceptorExecutor interceptorExecutor;
private final ExecuteMethodExceptionHandler executeMethodExceptionHandler;

/**
* @deprecated CDI eyes only
*/
protected DefaultInterceptorHandlerFactory() {
this(null, null, null, null, null, null);
this(null, null, null, null, null, null, null);
}

@Inject
public DefaultInterceptorHandlerFactory(Container container, StepInvoker stepInvoker,
CacheStore<Class<?>, InterceptorHandler> cachedHandlers, InterceptorAcceptsExecutor acceptsExecutor,
CustomAcceptsExecutor customAcceptsExecutor, InterceptorExecutor interceptorExecutor) {
CustomAcceptsExecutor customAcceptsExecutor, InterceptorExecutor interceptorExecutor,
ExecuteMethodExceptionHandler executeMethodExceptionHandler) {

this.container = container;
this.stepInvoker = stepInvoker;
this.cachedHandlers = cachedHandlers;
this.acceptsExecutor = acceptsExecutor;
this.customAcceptsExecutor = customAcceptsExecutor;
this.interceptorExecutor = interceptorExecutor;
this.executeMethodExceptionHandler = executeMethodExceptionHandler;
}

@Override
Expand All @@ -74,7 +78,7 @@ public InterceptorHandler get() {
return new AspectStyleInterceptorHandler(type, stepInvoker, container, customAcceptsExecutor,
acceptsExecutor, interceptorExecutor);
}
return new ToInstantiateInterceptorHandler(container, type);
return new ToInstantiateInterceptorHandler(container, type, executeMethodExceptionHandler);
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,16 @@

package br.com.caelum.vraptor.core;

import javax.enterprise.inject.Vetoed;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import br.com.caelum.vraptor.InterceptionException;
import br.com.caelum.vraptor.controller.ControllerMethod;
import br.com.caelum.vraptor.interceptor.Interceptor;
import br.com.caelum.vraptor.ioc.Container;
import br.com.caelum.vraptor.observer.ExecuteMethodExceptionHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.enterprise.inject.Vetoed;
import java.util.concurrent.Callable;

/**
* Instantiates the interceptor on the fly and executes its method.
Expand All @@ -39,28 +40,43 @@ public class ToInstantiateInterceptorHandler implements InterceptorHandler {

private final Container container;
private final Class<?> type;
private final ExecuteMethodExceptionHandler executeMethodExceptionHandler;

public ToInstantiateInterceptorHandler(Container container, Class<?> type) {
public ToInstantiateInterceptorHandler(Container container, Class<?> type, ExecuteMethodExceptionHandler executeMethodExceptionHandler) {
this.container = container;
this.type = type;
this.executeMethodExceptionHandler = executeMethodExceptionHandler;
}

@Override
public void execute(InterceptorStack stack, ControllerMethod method, Object controllerInstance)
public void execute(final InterceptorStack stack, final ControllerMethod method, final Object controllerInstance)
throws InterceptionException {
Interceptor interceptor = (Interceptor) container.instanceFor(type);
final Interceptor interceptor = (Interceptor) container.instanceFor(type);
if (interceptor == null) {
throw new InterceptionException("Unable to instantiate interceptor for " + type.getName()
+ ": the container returned null.");
}
if (interceptor.accepts(method)) {
logger.debug("Invoking interceptor {}", interceptor.getClass().getSimpleName());
interceptor.intercept(stack, method, controllerInstance);
executeSafely(stack, method, controllerInstance, interceptor);
} else {
stack.next(method, controllerInstance);
}
}

private void executeSafely(final InterceptorStack stack, final ControllerMethod method, final Object controllerInstance, final Interceptor interceptor) {
Try result = Try.run(new Callable<Void>() {
@Override
public Void call() throws Exception {
interceptor.intercept(stack, method, controllerInstance);
return null;
}
});
if (result.failed()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We always need the if, right? maybe we could add a handleOnFail
method (or something like that) to avoid duplicating... what do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👌

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm calling the method onError

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onError looks much better 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a question here, should I create a new interface to use as parameter? something like java 8's Consumer? Or I could use guava Function and use something like Function<? extends Exception, Void>. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we decided to drop this idea. In the future, when we start using java 8 we can improve this api.

executeMethodExceptionHandler.handle(result.getException());
}
}

@Override
public String toString() {
return "ToInstantiateHandler for " + type.getName();
Expand Down
86 changes: 86 additions & 0 deletions vraptor-core/src/main/java/br/com/caelum/vraptor/core/Try.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package br.com.caelum.vraptor.core;

import java.util.concurrent.Callable;

/**
* A class to wrap code that can possibly throw exceptions.
*
* Use the static method Try#run to instantiate this class, passing down
* the dangerous code and use its methods to retrieve the result or the exception
* of your computation. Example using java 8:
*
* <code>
* Try try = Try.run(() -> someDangerousMethod());
* if (try.failed()) {
* Exception e = try.getException();
* handleError(e);
* }
* try.result(); //do something with the result
* </code>
*
* @author Chico Sokol
* @param <T> the type of the result of your computation
*/
public abstract class Try<T> {
public static <T> Try run(Callable<T> callable) {
try {
T call = callable.call();
return new Success(call);
} catch (Exception e) {
return new Failed(e);
}
}

public abstract boolean failed();

public abstract T result();

public abstract Exception getException();

public static class Success<T> extends Try {
private final T result;

private Success(T result) {
this.result = result;
}

@Override
public boolean failed() {
return false;
}

@Override
public Object result() {
return result;
}

@Override
public Exception getException() {
throw new UnsupportedOperationException("A Success doesn't have an exception.");
}
}

public static class Failed<T> extends Try {
private final Exception e;

private Failed(Exception e) {
this.e = e;
}

@Override
public boolean failed() {
return true;
}

@Override
public Object result() {
throw new UnsupportedOperationException("A Failed doesn't have a result.");
}

@Override
public Exception getException() {
return e;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,31 +17,27 @@

package br.com.caelum.vraptor.observer;

import static org.slf4j.LoggerFactory.getLogger;

import java.lang.reflect.Method;

import javax.enterprise.context.Dependent;
import javax.enterprise.event.Event;
import javax.enterprise.event.Observes;
import javax.inject.Inject;

import org.slf4j.Logger;

import br.com.caelum.vraptor.InterceptionException;
import br.com.caelum.vraptor.controller.ControllerMethod;
import br.com.caelum.vraptor.core.MethodInfo;
import br.com.caelum.vraptor.core.ReflectionProvider;
import br.com.caelum.vraptor.core.ReflectionProviderException;
import br.com.caelum.vraptor.core.Try;
import br.com.caelum.vraptor.events.InterceptorsExecuted;
import br.com.caelum.vraptor.events.MethodExecuted;
import br.com.caelum.vraptor.events.MethodReady;
import br.com.caelum.vraptor.interceptor.ApplicationLogicException;
import br.com.caelum.vraptor.validator.Messages;
import br.com.caelum.vraptor.validator.ValidationException;
import org.slf4j.Logger;

import javax.enterprise.context.Dependent;
import javax.enterprise.event.Event;
import javax.enterprise.event.Observes;
import javax.inject.Inject;
import java.lang.reflect.Method;
import java.util.concurrent.Callable;

import static org.slf4j.LoggerFactory.getLogger;

/**
* Interceptor that executes the logic method.
* Observer that executes the logic method.
*
* @author Guilherme Silveira
* @author Rodrigo Turini
Expand All @@ -58,49 +54,44 @@ public class ExecuteMethod {

private final Event<MethodExecuted> methodExecutedEvent;
private final Event<MethodReady> methodReady;
private final ExecuteMethodExceptionHandler executeMethodExceptionHandler;

@Inject
public ExecuteMethod(MethodInfo methodInfo, Messages messages, Event<MethodExecuted> methodExecutedEvent,
Event<MethodReady> methodReady, ReflectionProvider reflectionProvider) {
public ExecuteMethod(MethodInfo methodInfo, Messages messages,
Event<MethodExecuted> methodExecutedEvent, Event<MethodReady> methodReady,
ExecuteMethodExceptionHandler exceptionHandler, ReflectionProvider reflectionProvider) {
this.methodInfo = methodInfo;
this.messages = messages;
this.methodExecutedEvent = methodExecutedEvent;
this.methodReady = methodReady;
this.executeMethodExceptionHandler = exceptionHandler;
this.reflectionProvider = reflectionProvider;
}

public void execute(@Observes InterceptorsExecuted event) {
try {
ControllerMethod method = event.getControllerMethod();
methodReady.fire(new MethodReady(method));
Method reflectionMethod = method .getMethod();
Object[] parameters = methodInfo.getParametersValues();

log.debug("Invoking {}", reflectionMethod);
Object instance = event.getControllerInstance();
Object result = reflectionProvider.invoke(instance, reflectionMethod, parameters);

messages.assertAbsenceOfErrors();

this.methodInfo.setResult(result);
methodExecutedEvent.fire(new MethodExecuted(method, methodInfo));
} catch (IllegalArgumentException e) {
throw new InterceptionException(e);
} catch (ReflectionProviderException e) {
throwIfNotValidationException(e, e.getCause());
} catch (Exception e) {
throwIfNotValidationException(e, e);
public void execute(@Observes final InterceptorsExecuted event) {
Try run = Try.run(new Callable<Void>() {
@Override
public Void call() throws Exception {
ControllerMethod method = event.getControllerMethod();
methodReady.fire(new MethodReady(method));
Method reflectionMethod = method.getMethod();
Object[] parameters = methodInfo.getParametersValues();

log.debug("Invoking {}", reflectionMethod);
Object instance = event.getControllerInstance();
Object result = reflectionProvider.invoke(instance, reflectionMethod, parameters);

messages.assertAbsenceOfErrors();

methodInfo.setResult(result);
methodExecutedEvent.fire(new MethodExecuted(method, methodInfo));
return null;
}
});
if (run.failed()) {
Exception exception = run.getException();
executeMethodExceptionHandler.handle(exception);
}
}

private void throwIfNotValidationException(Throwable original, Throwable alternativeCause) {
Throwable cause = original.getCause();

if (original instanceof ValidationException || cause instanceof ValidationException) {
// fine... already parsed
log.trace("swallowing {}", cause);
} else {
throw new ApplicationLogicException(alternativeCause);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package br.com.caelum.vraptor.observer;

import br.com.caelum.vraptor.InterceptionException;
import br.com.caelum.vraptor.core.ReflectionProviderException;
import br.com.caelum.vraptor.interceptor.ApplicationLogicException;
import br.com.caelum.vraptor.validator.ValidationException;
import org.slf4j.Logger;

import static org.slf4j.LoggerFactory.getLogger;

/**
* Handles exceptions thrown by a controller method
*
* @author Chico Sokol
*/
public class ExecuteMethodExceptionHandler {
private final static Logger log = getLogger(ExecuteMethodExceptionHandler.class);

public void handle(Exception exception) {
if (exception instanceof IllegalArgumentException) {
throw new InterceptionException(exception);
}
if (exception instanceof ReflectionProviderException) {
throwIfNotValidationException(exception, exception.getCause());
}
throwIfNotValidationException(exception, exception);
}

private void throwIfNotValidationException(Throwable original, Throwable alternativeCause) {
Throwable cause = original.getCause();

if (original instanceof ValidationException || cause instanceof ValidationException) {
// fine... already parsed
log.trace("swallowing {}", cause);
} else {
throw new ApplicationLogicException(alternativeCause);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.Assert.assertThat;

import br.com.caelum.vraptor.observer.ExecuteMethodExceptionHandler;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
Expand Down Expand Up @@ -55,8 +56,9 @@ public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);

CacheStore<Class<?>, InterceptorHandler> cachedHandlers = new DefaultCacheStore<>();
ExecuteMethodExceptionHandler executeMethodExceptionHandler = new ExecuteMethodExceptionHandler();
factory = new DefaultInterceptorHandlerFactory(container, stepInvoker,
cachedHandlers, acceptsExecutor, customAcceptsExecutor, interceptorExecutor);
cachedHandlers, acceptsExecutor, customAcceptsExecutor, interceptorExecutor, executeMethodExceptionHandler);
}

static interface RegularInterceptor extends Interceptor {}
Expand Down
Loading