Vraptor3: Condição para execução de interceptor [resolvido]

Possuo um interceptor que basicamente gera logs das URLs acessadas pelos usuários, bem como parametros e afins.

[code]@Intercepts
@RequestScoped
public class UserLogInterceptor
implements Interceptor {

private final UserSession userSession;
private final HttpServletRequest request;

public UserLogInterceptor(UserSession userSession, HttpServletRequest request) {
    this.userSession = userSession;
    this.request = request;
}

@Override
public boolean accepts(ResourceMethod method) {
    return true;
}

@Override
public void intercept(InterceptorStack stack, ResourceMethod method, Object object)
    throws InterceptionException {
    [...] //faz alguma coisa
}

}[/code]

O objeto UserSession é um objeto session-scoped que possui os dados do usuário. Como uso JAAS faço uma post-authentication para buscar os dados do usuário a partir do principal retornado no request. Isso funciona bem.

[code]@Component
@SessionScoped
public class UserSession
implements Serializable {

private final UserCredential myCredential;

public UserSession(HttpServletRequest request)
    throws PolicyContextException {
    final AuthenticationService service = ServiceLocator.getEJB(AuthenticationService.class);
    myCredential = service.postJaasAuthentication(request.getUserPrincipal().getName(), request.getRemoteAddr());
}

@PreDestroy
public void destroy()
    throws PolicyContextException {
    ServiceLocator.getEJB(AuthenticationService.class).unauthenticate(myCredential.getEmail());
}

public UserCredential getMyCredential() {
    return myCredential;
}

}[/code]

Até aqui tudo bem. Porém alterando algumas coisas no projeto houve a necessidade de avisar ao módulo EJB quando há tentativas de login inválidas. Então como uso JAAS via JDBC pensei: vou avisar via algum controller do Vraptor. Então alterei meu web.xml:

Enfim, essa volta toda para explicar que quando dá um erro de login, o vraptor dispara sempre o UserLogInterceptor. Tentei colocar alguma condição no método accepts, porém antes ele instancia o objeto UserSession que está no seu construtor, dando null-pointer-exception.

Então penso: não está errado instanciar para depois testar se ele pode ser aceito? Não seria interessante algum método que eu pudesse dizer quais as classes podem chamar esse interceptor? Pensei em primeira instância fazer um método extra estático que eu pudesse passar as classes. Ou então há alguma forma de eu evitar que o interceptor sempre seja chamado?

Claro que eu poderia colocar um if para evitar o nullpointer, porém achei um pouco estranho o comportamento de instanciar para depois testar se pode ser chamado.

UserLogInterceptor.acceptClasses { return new Class[] {Fooclass, MyOtherClass.class, Bar.class }; }

esse problema foi descutido aqui => http://www.guj.com.br/posts/list/141041.java#759620

acredito que algo LAZY resolve seu problema, ou seja, instanciar um objeto apenas se o acceptor rodar positivamente… na proxima versão a 3.0.2 esta previsto para os objetos serem opicionalmente injetados LAZY

@Intercepts(lazy=true)

assim o construtor só roda depois d eum accept, e depois que vc usar o objeto dentro do interceptor.

parece o mesmo caso de necessidade do lazy! tomaz tem razao.

bem, no 3.0.2 teremos essa possibilidade!

tenta colocar a lógica de criação no @PostConstruct, ou fazer lazy no getCredentials, que isso deve resolver…

evita fazer lógicas no construtor principalmente quando tem injeção de dependências… use sempre um @PostConstruct pra isso,
ou algum tipo de inicialização lazy…

[]'s

Lavieri, lí agora o post. Como é um pouco extenso vou reler e depois posto meus comentários. Muito boa iniciativa.

Lucas, não posso usar o @PostConstruct porque preciso do HttpServletRequest. Tentei receber a injeção como parametro de método, porém não foi injetado. Pelo que notei os beans são injetados apenas quando no construtor. Não seria ideal te-lo como atributo de classe já que é um componente session-scoped.

Sei que minha solução está um pouco POG, mas está sendo muito novo para mim autenticar usuário no JAAS com certificados. Então como no JAAS + Glassfish não consigo ter um objeto Principal estendido, tive de fazer um pós-jaas-login para pegar as informações estendidas do usuário (nome, email, informações de último login).

Abraços

garcia,

o post-construct não recebe parâmetro mesmo… mas vc pode fazer o seguinte:

 @Component  
 @SessionScoped  
 public class UserSession  
     implements Serializable {  
   
     private UserCredential myCredential;  
     private final HttpServletRequest request;   

     public UserSession(HttpServletRequest request) {
           this.request = request;
     }
 
    @PostConstruct
    public void create() throws PolicyContextException {  
         final AuthenticationService service = ServiceLocator.getEJB(AuthenticationService.class);  
         myCredential = service.postJaasAuthentication(request.getUserPrincipal().getName(), request.getRemoteAddr());  
     }  
     //...

ou ainda:

 @Component  
 @SessionScoped  
 public class UserSession  
     implements Serializable {  
   
     private UserCredential myCredential;  
     private final HttpServletRequest request;   

     public UserSession(HttpServletRequest request) {
           this.request = request;
     }
 
    public UserCredential getMyCredential() throws PolicyContextException {  
         if (myCredential == null) {
              final AuthenticationService service = ServiceLocator.getEJB(AuthenticationService.class);  
              myCredential = service.postJaasAuthentication(request.getUserPrincipal().getName(), request.getRemoteAddr());  
         }
         return myCredential;
     }  
     //...

tem que trocar o scopo de session para request não ?? afinal vc ta pedindo injeção do request

Lucas, não posso usar tua segunda sugestão, pois o meu bean é session-scoped. Preciso pegar o request tudo no ato da primeira requisição, senão terei uma IllegalStateException. Por isso usei tudo no construtor.

Na verdade meu bean é session-scoped. Uso a injeção do request apenas no construtor para ter acesso ao request.remoteUser e request.remoteAddr. Se você olhar na minha classe no primeiro post verá que ele não fica como atributo de classe. Na verdade estou pensando em mudar um pouco a forma na qual uso esses dois beans (interceptor + objeto de sessão com dados do usuário).

Estou pensando em deixar esse UserSession como fantoche mesmo, contendo apenas os dados do usuário, e deixar o interceptor fazer o lookup do ejb e popular os dados no UserSession. Dessa forma evito esse problema na construção e não misturo as coisas, afinal como o Lucas disse em outro tópico: coisas de infraestrutura devem ficar nos interceptors mesmo.

Abraços e obrigado pelas sugestões.

como vc só precisa do request.remoteUser e request.remoteAddr (e isso não muda durante a sessão certo?)

criar um component session scoped que guarda essas duas informações (no construtor), e receba esse componente
no UserSession…

e inicializa as coisas de forma lazy no UserSession

(comentário no tópico errado)

novo flash interceptor com esse bug corrigido:

Alterei para armazenar como local somente o ip e email do usuário. Assim via PostConstruc e PreDestroy chamo os EJBs necessários.

[code]@Component
@SessionScoped
public class UserSession
implements Serializable {

private UserCredential credential;
private final String _email;
private final String _remoteAddr;

public UserSession(MutableRequest request) {
    _email = request.getUserPrincipal().getName();
    _remoteAddr = request.getRemoteAddr();
}

@PostConstruct
public void init() {
    final AuthenticationService service = ServiceLocator.getEJB(AuthenticationService.class);
    credential = service.postJaasAuthentication(_email, _remoteAddr);
}

@PreDestroy
public void destroy() {
    ServiceLocator.getEJB(AuthenticationService.class).unauthenticate(_email, _remoteAddr);
}

}[/code]

Obrigado Lucas e Lavieri.