Se voce tiver validacoes no setter do seu modelo que sera chamado pelo VRaptor, você pode lançar excecoes de negocio dentro do setter e eles serao considerados erros de validacao (o Validator entra em acao). Pra isso, basta que a sua exception de negocio seja anotada com @ValidateException.
Ségio, na verdade penso que o vraptor cuidaria disso, hehe. Algo semelhante ao que ocorria com o struts, que tinha um bom exception handler.
Eu achei que nesse caso eu fazendo isso no meu controller:
@Post
@Path("/security/user/")
public void store(UserDTO user) {
validator.onErrorUse(Results.page()).of(getClass()).edit(user.getId());
userService.store(user);
}
Quando no meu EJB desse um erro, por exemplo new MyBusinessException(“A data do aniversário está errada”), o vraptor iria me direcionar para o método edit. Por enquanto eu estou usando o pseudo exception-handler do web.xml direcionando a um servlet que então vê o referer e devolve para ele. É uma boa solução até, mas penso que seria interessante ficar dentro do vraptor, assim posso, como no caso do validator, direcionar de forma mais elegante como com o Result.use().
Aliás, aproveitando, notei que eu devo primeiro adicionar as mensagens de erro, e depois chamar o validator.onErrorUse(). Caso eu fazer o validator.onErrorUse e depois adicionar as mensagens via validator.add() ele me lança a exception:
br.com.caelum.vraptor.InterceptionException: There are validation errors and you forgot to specify where to go. Please add in your method something like:
validator.onErrorUse(page()).of(AnyController.class).anyMethod();
or any view that you like.
at br.com.caelum.vraptor.interceptor.ExecuteMethodInterceptor.intercept(ExecuteMethodInterceptor.java:64)
at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:57)
at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:71)
at br.com.caelum.vraptor.interceptor.ParametersInstantiatorInterceptor.intercept(ParametersInstantiatorInterceptor.java:93)
at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:57)
at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:71)
at br.com.caelum.vraptor.interceptor.InstantiateInterceptor.intercept(InstantiateInterceptor.java:54)
at br.com.caelum.vraptor.core.InstantiatedInterceptorHandler.execute(InstantiatedInterceptorHandler.java:51)
at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:71)
at br.com.caelum.vraptor.interceptor.multipart.MultipartInterceptor.intercept(MultipartInterceptor.java:58)
at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:57)
Estava lendo agora a documentação sobre validações em http://vraptor.caelum.com.br/documentacao/validacao/, mas não achei como imprimir isso no jsp. Nesse caso é exportada em algum atributo no request? Ou preciso de algum tratamento especial via interceptor?
Você precisa adicionar as mensagens antes e por ultimo (para indicar o fim da validação) chamar o onErrorUse…
As mensagens são adicionadas no result com o identificador ‘errors’ no jsp basta fazer:
<c:forEach var="error" items="errors">
${error.category} ${error.message}
</c:forEach>
Sérgio, me dê uma sugestão… minha aplicação é distribuida, sendo o módulo EJB remoto. Como faço para anotar minhas service-exceptions com @ValidateException se o módulo EJB não conhece a camada web?
Eu teria que fazer alguma wapper dela chegando na camada web?
na minha resposta eu estava imaginando as suas classes de modelo sendo populadas nos argumentos do metodo do vraptor.
no caso de vc invocar um EJB dentro do metodo, acho que o melhor eh fazer um try/catch mesmo e usar o Validator
[quote=Sergio Lopes]na minha resposta eu estava imaginando as suas classes de modelo sendo populadas nos argumentos do metodo do vraptor.
no caso de vc invocar um EJB dentro do metodo, acho que o melhor eh fazer um try/catch mesmo e usar o Validator[/quote]
Foi o que eu fiz por enquanto. Vou repensar em uma forma de não precisar ficar no try-and-catch.
Abraços
Fiz um interceptor bem simples por enquanto para testes de um futuro exception-handler.
[code]try {
stack.next(method, resourceInstance);
} catch (Exception e) {
try {
ValidationMessage m = new ValidationMessage(ExceptionUtils.getRootCauseMessage(e), “error”);
validator.add(m);
Class<UserController> whoCalls = (Class<UserController>) method.getMethod().getDeclaringClass();
validator.onErrorUse(Results.logic()).forwardTo(whoCalls).list();
} catch (Exception ex) {
throw new InterceptionException(ex);
}
}[/code]
Minha duvida agora é… na linha 8 e 9 eu fiz uma gambiarra para indentificar o controller e redirecionar para um método qualquer. Pensei em duas hipóteses: 1) quando der erro voltar para o referer ou 2) redirecionar para uma página especificada no método que está sendo executando quando deu erro.
No caso 1 o controller é facil saber (via method.getMethod().getDeclaredClass()), porém como eu posso descobrir para onde redirecionar? Por exemplo, pelo referer “/user/edit” eu descobrir a rota (qual método do controller chamar) ou algo assim. Eu tenho acesso a tabela de rotas via injeção no construtor?
Abraços
Estou exibindo as mensages de erro utilizando o seguinte loop no JSP:
<c:forEach items="${errors}" var="error">
<li class="${error.category}">${error.message}</li>
</c:forEach>
Mas as mensagens aparecem com “???” antes e depois da mensagem. Por ex.: “usuário inválido” aparece no JSP como “???usuário inválido???”.
Alguma luz?
T+
Fiz um debug rápido e percebi que o problema é que ele usa a mensagem como chave para um resource-bundle. Não existe uma forma mais prática, utilizando a mensagem de erro diretamente??? Aliás, nem na documentação nem na matéria da MundoJava #38 é mencionada a necessidade de um ResourceBundle, e eu particularmente prefiro evitar ter que fazer esse tipo de amarração em um arquivo externo.
Observando um pouco mais, tenho a impressão que se utilizar a validação clássica isso não ocorre (há controle na criação do ValidationMessage), enquanto na validação fluente ele sempre tenta utilizar o resource bundle. No caso vejo duas alternativas:
- ativar o uso do ResourceBundle ou não no Validations via um parâmetro no web.xml
- adicionar uma variante do método that() que receba um ValidationMessage. Nesse caso, algo que ache o que poderia ser legal era adicionar um método add(String category, String message) no Validator, com o mesmo comportamento do that() que recebe categoria e mensagem. Talvez alterar o nome do parâmetro de “message” para “resourceKey” criaria algo mais fácil de compreender (que será usado um resourceBundle).
T+
Hmm, muito estranho. Não sei se é porque no meu caso todas as validações ficam no módulo EJB, mas as mensagens para mim estão vindo corretamente. Como você está usando? Pode passar o pedaço do JSP e do controller?
No meu caso eu faço:
[code] if (result.getErrorMethod() == null) {
throw new RuntimeException(ExceptionUtils.getRootCause(e));
}
final List<Message> messages = Lists.newLinkedList();
Throwable cause = ExceptionUtils.getRootCause(e);
if (cause instanceof InvalidStateException) {
for (InvalidValue v : ((InvalidStateException) cause).getInvalidValues())
messages.add(new ValidationMessage(v.toString(), "error"));
} else {
messages.add(new ValidationMessage(ExceptionUtils.getMessage(cause), "error"));
}[/code]
duron, abre uma issue sobre isso que vc quer fazer por favor (se possível em ingles)?
por padrão a forma clássica não é internacionalizada e a forma fluente é. Você pode colocar sua mensagem de erro no campo category, que não é internacionalizado, se vc quiser.
[]'s
Havia no struts há algum tempo uma gambiarra que verificava se a mensagem existia no resource-bundle. Caso sim mostrava a mensagem, caso contrário exibia assim como ela era especificada. 
Obrigado garcia-jj, vc me deu uma solução “boa” pra isso:
Crie a classe:
public class NoResourceBundle extends ResourceBundle {
@Override
public Enumeration<String> getKeys() {
return Collections.enumeration(Arrays.<String>asList());
}
@Override
protected Object handleGetObject(String key) {
return key;
}
}
e quando for usar a validação fluente:
validator.checking(new Validations(new NoResourceBundle()) {
that(...);
});
isso deve funcionar… você pode até cachear essa instância do NoResourceBundle em algum lugar
e usar import estático pra ficar mais legível…
[]'s
Amigo,
De acordo com o que li neste post, implementei um interceptor que deverá exibir a mesma pagina caso seja lancada alguma exceção. Assim sendo ficou:
[code]@Intercepts
@RequestScoped
public class ExceptionInterceptor implements Interceptor{
Logger log = Logger.getLogger(getClass());
private final Result result;
public ExceptionInterceptor(Result result) {
this.result = result;
}
/*
* Este interceptor deve interceptar todos os métodos.
* method.getMethod() retorna o método java que está sendo executado
* method.getResourceClass().getType() retorna a classe que está sendo executada
*/
public boolean accepts(ResourceMethod method) {
return true;
}
public void intercept(InterceptorStack stack, ResourceMethod method, Object resourceInstance) throws InterceptionException {
try {
log.debug("Antes");
stack.next(method, resourceInstance);
log.debug("Depois");
} catch(Exception exception){
log.debug("exception: "+exception.getMessage());
try {
this.result.include("error", exception.getMessage());
this.result.use(Results.page()).forward();
} catch (Exception e) {
log.error("Erro no Interceptor: "+e.getMessage());
e.printStackTrace();
}
}
}
}[/code]
Sendo que quando é lancada a exceção no controller, por exemplo, e o interceptor “pega” essa exceção, ele está dando um nullpointer nesta linha:
Meu controller está assim:
[code]@Resource
public class ClienteController {
private final ClienteDAO dao;
private final Result result;
private final Validator validator;
public ClienteController(ClienteDAO dao, Result result, Validator validator) {
this.dao = dao;
this.result = result;
this.validator = validator;
}
…
}[/code]
Estou fazendo certo?
Lembrando que a ideia é permanecer na página que originou o erro.
Abs,
Creio que não, hehe. Eu não sei o que faz o forward() vazio (preguiça de ler a documentação, hehe), mas creio que você precise especificar para onde você quer ir.
Caso você queira, assim como eu, voltar para a tela anterior, precisa elaborar umas coisinhas a mais. No meu caso eu alterei uma série de coisas com a ajuda do Lucas, fazendo assim um exception-handler. http://guj.com.br/posts/list/143727.java
Lucas e Paulo, o que vocês acham de colocar esse exception handler no core do vraptor3?
Então… nunca é bom dar try…catch em Exception no Interceptor… Faz isso pra uma Exception mais específica…
o result.use(page()).forward() vai pra página padrão do método atual
o NullPointer está acontecendo no result, nessa linha?
this.result.use(Results.page()).forward();
ou em algo dentro da chamada forward? (não faz sentido dar exception nesse ponto…)
@garcia-jj acho que a gente pode pensar em algo do tipo sim, abre uma issue lá no github por favor?
e lembrando que se vc quer apenas um redirect generico qdo alguma exception vazar, nada de maracutaias! use o error-page no web.xml 
Lucas,
Acho que o null pointer está no forward. Debuguei e percebi que o result nao estã nulo (inclusive, na linha acima ele é utilizado sem problemas (this.result.incluide…).
Segue abaixo o log do erro:
SEVERE: Servlet.service() for servlet jsp threw exception
java.lang.NullPointerException
at br.com.caelum.vraptor.interceptor.OutjectResult.accepts(OutjectResult.java:47)
at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:45)
at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:59)
at br.com.caelum.vraptor.core.DefaultRequestExecution.execute(DefaultRequestExecution.java:62)
at br.com.caelum.vraptor.VRaptor$1.insideRequest(VRaptor.java:91)
at br.com.caelum.vraptor.ioc.spring.SpringProvider.provideForRequest(SpringProvider.java:49)
at br.com.caelum.vraptor.VRaptor.doFilter(VRaptor.java:88)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:646)
at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:436)
at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:374)
at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:302)
at br.com.caelum.vraptor.view.DefaultPageResult.forward(DefaultPageResult.java:72)
at br.com.lojavirtual.controller.ExceptionInterceptor.intercept(ExceptionInterceptor.java:51)
at br.com.caelum.vraptor.core.InstantiatedInterceptorHandler.execute(InstantiatedInterceptorHandler.java:41)
at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:59)
at br.com.caelum.vraptor.util.hibernate.HibernateTransactionInterceptor.intercept(HibernateTransactionInterceptor.java:45)
at br.com.caelum.vraptor.core.InstantiatedInterceptorHandler.execute(InstantiatedInterceptorHandler.java:41)
at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:59)
at br.com.caelum.vraptor.interceptor.InterceptorListPriorToExecutionExtractor.intercept(InterceptorListPriorToExecutionExtractor.java:46)
at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:46)
at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:59)
at br.com.caelum.vraptor.interceptor.FlashInterceptor.intercept(FlashInterceptor.java:80)
at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:46)
at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:59)
at br.com.caelum.vraptor.interceptor.ResourceLookupInterceptor.intercept(ResourceLookupInterceptor.java:67)
at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:46)
at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:59)
at br.com.caelum.vraptor.core.DefaultRequestExecution.execute(DefaultRequestExecution.java:62)
at br.com.caelum.vraptor.VRaptor$1.insideRequest(VRaptor.java:91)
at br.com.caelum.vraptor.ioc.spring.SpringProvider.provideForRequest(SpringProvider.java:55)
at br.com.caelum.vraptor.VRaptor.doFilter(VRaptor.java:88)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:849)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:583)
at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:454)
at java.lang.Thread.run(Thread.java:595)
03/12/2009 - 15:34:58 [br.com.lojavirtual.controller.ExceptionInterceptor] ERROR - Erro no Interceptor: null
java.lang.NullPointerException
at br.com.caelum.vraptor.interceptor.OutjectResult.accepts(OutjectResult.java:47)
at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:45)
at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:59)
at br.com.caelum.vraptor.core.DefaultRequestExecution.execute(DefaultRequestExecution.java:62)
at br.com.caelum.vraptor.VRaptor$1.insideRequest(VRaptor.java:91)
at br.com.caelum.vraptor.ioc.spring.SpringProvider.provideForRequest(SpringProvider.java:49)
at br.com.caelum.vraptor.VRaptor.doFilter(VRaptor.java:88)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:646)
at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:436)
at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:374)
at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:302)
at br.com.caelum.vraptor.view.DefaultPageResult.forward(DefaultPageResult.java:72)
at br.com.lojavirtual.controller.ExceptionInterceptor.intercept(ExceptionInterceptor.java:51)
at br.com.caelum.vraptor.core.InstantiatedInterceptorHandler.execute(InstantiatedInterceptorHandler.java:41)
at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:59)
at br.com.caelum.vraptor.util.hibernate.HibernateTransactionInterceptor.intercept(HibernateTransactionInterceptor.java:45)
at br.com.caelum.vraptor.core.InstantiatedInterceptorHandler.execute(InstantiatedInterceptorHandler.java:41)
at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:59)
at br.com.caelum.vraptor.interceptor.InterceptorListPriorToExecutionExtractor.intercept(InterceptorListPriorToExecutionExtractor.java:46)
at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:46)
at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:59)
at br.com.caelum.vraptor.interceptor.FlashInterceptor.intercept(FlashInterceptor.java:80)
at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:46)
at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:59)
at br.com.caelum.vraptor.interceptor.ResourceLookupInterceptor.intercept(ResourceLookupInterceptor.java:67)
at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:46)
at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:59)
at br.com.caelum.vraptor.core.DefaultRequestExecution.execute(DefaultRequestExecution.java:62)
at br.com.caelum.vraptor.VRaptor$1.insideRequest(VRaptor.java:91)
at br.com.caelum.vraptor.ioc.spring.SpringProvider.provideForRequest(SpringProvider.java:55)
at br.com.caelum.vraptor.VRaptor.doFilter(VRaptor.java:88)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:849)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:583)
at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:454)
at java.lang.Thread.run(Thread.java:595)
bom… isso é um bug que foi corrigido já, mas não foi lançado ainda…
ele acontece pois a página padrão da sua lógica não existe… e o VRaptor se perdia com isso…
você pode atualizar a versão do repositório do VRaptor, esperar o próximo release, ou não fazer essa lógica que vc está fazendo…
ela é ruim pq nem sempre suas lógicas vão para a página padrão…
se dentro de uma lógica vc estiver fazendo:
result.use(...)....
o result.use(page()).forward() do seu interceptor não vai fazer mais sentido…
o melhor a fazer quando dar uma exception é ir para uma página fixa (usando o error-page como o Sergio disse), ou no máximo ir pro referer (que não é sempre garantido):
result.use(Results.referer()).forward();
http://vraptor.caelum.com.br/cookbook/usando-o-header-referer-para-fazer-redirecionamentos/