Tratando erros com Ajax e VRaptor

Esse post foi motivado pela forma não elegante que eu estava tratando as exceções em requisições ajax em minhas aplicações com VRaptor.

Sempre que eu fazia uma requisição ajax e precisava de tratar um erro acabava fazendo de uma forma bem procedural. Utilizava o retorno do callback do ajax e verificava se o retorno era um erro ou o dado esperando. Mas isso está bem longe de ser uma forma elegante de se tratar erros, sem falar que para cada ocasião é necessário verificar se o dado é erro ou não de forma diferente.

Então tive a ideia de não tratar mais o erro no controller e deixar para tratar no callback de erro do ajax, mas ai tive outro problema.

Como ter mais de uma error page? Uma vez que a página de erro customizada para o usuário não seria muito útil para receber no callback de erro. Postei minha dúvida para a lista do VRaptor da Caelum (caelum-vraptor@googlegroups.com) e o Lucas Cavalcante (@lucascs) me deu a idéia de fazer um interceptor com um try…catch e utilizar o accept do Header para saber se a requisição é ajax. Gostei da ideia, implementei e resolvi fazer este post.

A implementação foi bem fácil e tranquila. Primeiro criei um Interceptor que chamei de AjaxExceptionInterceptor.

        @Intercepts
        public class AjaxExceptionInterceptor implements Interceptor {
            
            private HttpServletRequest request;
            private Result result;
            
            public AjaxExceptionInterceptor(HttpServletRequest request, Result result) {
                this.request = request;
                this.result = result;
            }
        
            public boolean accepts(ResourceMethod method) {
                return request.getHeader("accept").contains("application/json"); 
            }
        
            public void intercept(InterceptorStack stack, ResourceMethod method,
                    Object resourceInstance) throws InterceptionException {
                try {
                    stack.next(method, resourceInstance);
                } catch (Exception e) {
                    result.use(Results.http()).body(e.getCause().getMessage());
                }
            }
        
        }
    

Reparem nas implementações dos métodos “accepts” e “intercept” exigidas pelo contrato da interface Interceptor.

Você verá que na implementação do método “accepts” é verificado se é uma chamada ajax pelo conteúdo do accept do Header.

E na implementação do método “intercept” foi colocado um try…catch e caso o controller lance uma excessão, a mesma será capturada e então é enviada uma resposta com a mensagem da exception e não o html padrão do tomcat ou algum outro customizado no web.xml.

O controller é implementado sem tratar as exceções ou lançando as exceções customizadas.

        @Resource
        public class IndexController {
        
            private final Result result;
        
            public IndexController(Result result) {
                this.result = result;
            }
        
            @Path("/")
            public void index() {
            }
            
            @Post
            @Path("/testaCodigo")
            public void testaCodigo(String codigo) {
                if (codigo == null || codigo.isEmpty()) {
                    throw new RuntimeException("Código Inválido");
                } 
                
                result.use(Results.json()).withoutRoot().from("Sucesso!!!!").serialize();
            }
        
        }
    

E por último basta fazer a chamada ajax na página.

        $.ajax({
            url : "<c:url value='/testaCodigo' />",
            type: "post",
            dataType: "json",
            data: "codigo=" + $("#codigo").val(),
            success: function (data) {
                alert(data);
            },
            error: function (jXHR, textStatus, errorThrown) {
                alert(jXHR.responseText);
            }
        });
    

Repare que agora temos um código mais elegante onde não precisamos verificar o retorno do callback de sucesso para saber se é um erro ou o dado esperado. Agora usamos o callback de erro do $.ajax do jQuery para capturar o erro caso ele ocorra e dar o tratamento desejado.

Clique aqui para ver o projeto completo que fiz com esse exemplo funcional no github.