Skip to content

Commit

Permalink
Merge pull request #873 from csokol/catching-interceptors-validation-…
Browse files Browse the repository at this point in the history
…errors

Handling ValidationExceptions in interceptors
  • Loading branch information
csokol committed Dec 29, 2014
2 parents 2f884e2 + 9503b57 commit 3268b17
Show file tree
Hide file tree
Showing 11 changed files with 292 additions and 99 deletions.
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()) {
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

0 comments on commit 3268b17

Please sign in to comment.