VRaptor 3 - tratamento de erro nas aplicações

Lucas, o problema de usar referer é que se der um erro e eu ir pro referer funciona da primeira vez. Mas se der um erro de novo meu referer é a própria página de erro, assim você entra em loop infinito.

A solução do Sérgio é boa para erros que você não consegue mais recuperar a aplicação, exemplo, banco fora do ar e afins.

Mas quando dá erros de regra de negócio ou até mesmo o estouro de uma constraint, o que você faz? Exibir uma página padrão é muito ruim, pois você tem que fazer o usuário clicar em voltar e você ainda precisa cuidar para trazer os dados de novo. Em muitos casos history.back não serve. O mais ideal, na minha visão, é você voltar para alguma tela e exibir alguma mensagem que explique o que aconteceu.

Exemplo: estou na tela de edição do usuário, então quando eu vou salvar o objeto ocorre um erro de lock otimista. O que eu vou fazer? Volto para a tela de edição do usuário, exibo a mensagem “Versão do objeto inválida”. Assim ao mesmo tempo eu mostro a mensagem do erro e o usuário já está na própria tela de edição. Imagina que saco você mandar o usuário para uma página em branco dizendo “Versão do objeto inválida” e apenas isso? Aí ele tem que voltar por conta própria, além de você ter um processamento de tela e requisição a mais.

Obvio que você pode fazer try and catch para tratar os erros que vêm da regra de negócio, mas pense o quão trabalhoso seria fazer isso em uma aplicação do tamanho da que eu tenho, que são aproximadamente 350 controllers.

Achei que nesse ponto ficou um espaço vazio no vraptor. Por isso há algum tempo eu tenho corrido atrás de implementar isso, que consegui com a tua ajuda. FIz algo baseado no Struts 1x (que eu gostava muito), onde você diz antes de tudo para onde ir caso der um erro, e se der erro ele vai para tal tela.

Eu estarei muito ocupado até os dias do sun tech days, mas logo após isso vou passar um gist com meu exception handlers e te passo. Há uns detalhes que ainda estou revendo nesse exception handler, mas tudo culpa do waffle :slight_smile:

issue criada =)

vamos pensar em algo legal, talvez usando interceptors e uma interface que vc pode implementar e funcionar automaticamente…

Lucas, o que eu fiz foi assim, baseado nas tuas sugestões em outro tópico:

Criei uma classe CustomResult que implementa Result. Essa classe possui um método chamado onErrorUse tem o mesmo comportamento que o Result.use.

[code]public T onErrorUse(Class controller) {
return proxifier.proxify(controller, new MethodInvocation() {

	@Override
	public Object intercept(T proxy, Method method, Object[] args, SuperMethod superMethod) {
		CustomResult.this.method = method;
		CustomResult.this.args = args;
		return null;
	}
});

}[/code]

Depois criei uma classe copiando o conteúdo de ExecuteMethodInterceptor, alterei o try and catch para apenas um catch com meu handler.

[code]public void intercept(InterceptorStack stack, ResourceMethod method, Object resourceInstance)
throws InterceptionException {
try {
Method reflectionMethod = method.getMethod();
Object[] parameters = this.info.getParameters();
Object result = reflectionMethod.invoke(resourceInstance, parameters);
if (validator.hasErrors()) { // method should have thrown
// ValidationError
throw new InterceptionException(“There are validation errors and you forgot to specify where to go.”);
}

	if (reflectionMethod.getReturnType().equals(Void.TYPE)) {
		// vraptor2 compatibility
		this.info.setResult("ok");
	} else {
		this.info.setResult(result);
	}
	stack.next(method, resourceInstance);
} catch (Exception e) {
	// if onErrorUse is null, throws the exception
	if (result.getErrorMethod() == null) {
		throw new InterceptionException(ExceptionUtils.getRootCause(e));
	}

	// get the cause
	Throwable cause = ExceptionUtils.getRootCause(e);

	// if is validation exceptions
	if (cause instanceof InvalidStateException) {
		for (InvalidValue v : ((InvalidStateException) cause).getInvalidValues())
			validator.add(new ValidationMessage(v.toString(), "error"));
	} else {
		validator.add(new ValidationMessage(ExceptionUtils.getMessage(cause), "error"));
	}

	Method errorMethod = result.getErrorMethod();
	Object[] args = result.getErrorArgs();
	Object target = result.use(Results.logic()).forwardTo(errorMethod.getDeclaringClass());

	try {
		errorMethod.invoke(target, args);
	} catch (Exception e0) {
		throw new InterceptionException(e0);
	}
}

}[/code]

Além disso coloquei no web.xml a página de erros para cuidar das exceptions que não consigo recuperar a aplicação:

<error-page> <exception-type>java.lang.Exception</exception-type> <location>/WEB-INF/jspx/error.jspx</location> </error-page>

Para usar basta fazer algo como isso aqui:

[code]public void store(Registration registration) {
result.onErrorUse(getClass()).edit(registration.getId());

registrationService.store(registration);
result.use(Results.logic()).redirectTo(getClass()).list(Paging.first());

}[/code]

Há um problema nisso tudo. Quando eu volto para a tela de edição os dados não estão vindos preenchidos. Há um bug que já foi aberto para corrigir o OutjectResult. Eu fiz um componente bem gambiarrento para isso, mas tenho vergonha de postar o código, hahahahhaha.

That’s all :smiley:

Complementando, se vocês acharem interessante colocar esse exception handler no vraptor, que o onErrorUse fique no próprio Result, assim como temos o Result.use.

Uma outra idéia é colocar umas constantes no ValidationMessage para os valores mais usados como ValidationMessage.ERROR, ValidationMessage.WARNING e afins.

Meus dois pitacos :slight_smile:

obrigado garcia…

vou dar uma olhada nesse código e ver se coloco algo bem parecido no core do vraptor =)

[quote=lucascs]obrigado garcia…

vou dar uma olhada nesse código e ver se coloco algo bem parecido no core do vraptor =)[/quote]

Lucas, essa semana estava revisando as issues lá no github para ver no que eu poderia ajudar. E notei que essa issue do exception handler e a do outjector estão abertas.

Fiquei preocupado em ver que novas funcionalidades foram colocadas como prioridade maior que correção de bugs. No caso do outjector é um bug que impacta até mesmo no validator, que é uma coisa tão trivial de usarmos. Já o exception handler é uma feature nova, embora eu pense que é uma feature muito importante.

Não sei como anda o tempo de vocês, mas se de alguma forma eu puder ajudar fico a disposição.

Desculpa a demora em responder (estava de férias, sem internet)…

realmente nesse último mes estivemos sem tempo, e não conseguimos resolver muitas issues…

vou tentar resolver essas issues rapidamente nos proximos dias

[]'s

Lucas, fez bem, pois computadores não combinam com férias, haha.

Será que sai na 3.1? Se precisar de alguma coisa, me avise. Abraços

então… ainda não sei mto bem como fazer isso…

colocar um onError no Result não parece mto legal… talvez a gente possa usar o próprio validator pra isso:

validator.onExceptionUse(...)

ou melhor:

validator.onException(SqlException.class).use(...);

mas o que seria melhor mesmo, é criar uma interface que cuida disso, que acha?

public interface ExceptionHandler<T extends Exception> {
       void handle(T exception, ResourceMethod method);
}

daí vc faz o que quiser dentro desse metodo…

Lucas, na verdade a idéia de usar o onErrorUse foi você que me deu :slight_smile: http://guj.com.br/posts/list/143727.java

Eu não usei nada no validator porque sinto que validação é uma coisa, erros da aplicação é outra. Você pode ter erros causados pela validação, porém pode acontecer erros que não são de validações.

Mas acho que sua idéia da interface é muito boa. Pelo que notei seria igual ao comportamento do Results, certo?

Não… o comportamento seria algo do tipo:

se a exceção é SQLException e existe um ExceptionHandler então executa ele…

Se SQLException extends IOException e existir um ExceptionHandler executa ele

se não existir nenhum Handler da exceção, executa o comportamento padrão (que é o que está hoje)

:smiley: Mas assim fica mais do que bom, hehe.

lucascs, agora lembrei de uma coisa. Minha sugestão para o exception handler é que a cada ação você possa definir para onde ir. Um exemplo, se você está na página de salvar e dá um erro você vai para a tela de edição. Dessa forma que você sugeriu o handler fica por excessão. Só que sendo genérico assim dá para fazer isso?

dá se ele receber o ResourceMethod… aí a partir do resourceMethod vc consegue saber…

mas o controle fica no handler, não no meio do código da lógica…

mas é aquela coisa: excessões são coisas que não deveriam acontecer… não é bom vc ficar se prevenindo delas toda hora…

por exemplo vc que usa ejbs, qdo acontecer uma RemoteException (não sei se eh essa mesmo) vc pode tentar se recuperar… mas tem q ser meio genérico

Lucas, mas se você coloca o código do tratamento dentro do handler você não consegue ter algo especifico sobre para onde ir. Da forma proposta fica como no web.xml, ou seja, vai para uma tela genérica.

Há um post meu aqui explicando o quão trabalhoso fica ter essa visão de que exceptions não deveriam acontecer: http://guj.com.br/posts/list/30/136307.java#789504

Normalmente quando é lançado um erro de regra de negócio o ideal é voltar para a tela de edição e resolver o erro. Porém se eu estou em uma classe genérica não tenho como saber onde é a tela de edição, ou qualquer outra tela que eu queria voltar. Aí a gente cai no problema que falei de ter que ficar fazendo try and catch a cada chamada do EJB. Isso se torna improdutivo para os projetos que eu tenho usado Vraptor, já que um deles tem aproximadamente 350 controllers.

Por isso eu havia colocado o Result.onErrorUse, pois assim eu posso ter um controle melhor para onde vou de forma bem especifica. Exemplo de como o código fica bem simples:

[code] public void edit(Long id) {
if (id != null) {
result.include(“degree”, coreService.loadDegree(id));
}
}

public void store(DegreeDTO degree) {
    result.onErrorUse(getClass()).edit(degree.getId());

    coreService.storeDegree(degree);
    result.use(Results.logic()).redirectTo(getClass()).list(null);
}

public void delete(final Long id) {
    result.onErrorUse(getClass()).edit(id);

    coreService.deleteDegree(id);
    result.use(Results.logic()).redirectTo(getClass()).list(null);
}[/code]

Senão eu teria de fazer isso:

public void delete(final Long id) { try { coreService.deleteDegree(id); result.use(Results.logic()).redirectTo(getClass()).list(null); } catch(MyBusinessException e) { ... adiciona as mensagens result.use(getClass()).edit(id); } }

Se for genérico separado por tipos de exceptions eu não consigo saber para onde mandar meu fluxo.

Enfim, é apenas uma sugestão minha para deixar o vraptor melhor. A decisão final é de vocês da Caelum.

minha preocupação em fazer um onErrorUse, sem passar um tipo de exceção específico, é que isso vai mascarar os problemas que acontecerem…

se der uma nullPointer, vai voltar pra tela de edição e vc nem vai ficar sabendo, qdo vc só queria se recuperar da sua MyBusinessException…

é como se vc estivesse dando um try{…}catch (Throwable t) {…} que é uma péssima prática de programação…

e outra coisa: o que você manda pro cliente qdo dá uma exception nesse caso?

[quote=lucascs]minha preocupação em fazer um onErrorUse, sem passar um tipo de exceção específico, é que isso vai mascarar os problemas que acontecerem…

se der uma nullPointer, vai voltar pra tela de edição e vc nem vai ficar sabendo, qdo vc só queria se recuperar da sua MyBusinessException…

é como se vc estivesse dando um try{…}catch (Throwable t) {…} que é uma péssima prática de programação…

e outra coisa: o que você manda pro cliente qdo dá uma exception nesse caso?

[/quote]

Ahh, Lucas, entendi sua posição. No meu caso o onErrorUse faz quase um try/catch para Exception, não Throwable. Mas mesmo assim não fica bom, confesso.

Será que é possível fazer algo como abaixo? Minha preocupação é apenas eu poder excolher para onde eu quero ir.

result.onExceptionUse(MyException.class).forwardTo(MyController.class).edit();
result.onExceptionUse(MyBusiness.class).forwardTo(OutraCoisa.class).displayError();

Oi Garcia!

A parte dos outjectors o lucas ja fez.

Sobre essa parte de tratamento de excecoes, nao estou vendo ainda a melhor solucao. Acho melhor esperarmos, ja que da pra contonar isso atraves de interceptadores ou mesmo do mecanismo padrao do web.xml de configurar as error pages, correto? ai poderiamos debater um pouco mais

Paulo e Lucas, perfeito.

Muito obrigado por enquanto, tanto pela implementação quanto pela oportunidade de opinar sobre isso.