Patterns Java

Autor: Pedro Rodolfo Kalva

 

Um exemplo prático de como desenvolver um aplicativo com o uso de padrões

Muito se fala sobre padrões java, os famosos “patterns java”, mas pouco se utiliza na prática, e isto pode ser ocasionado por mitos que levam a crer que a aplicação dos padrões em um sistema computacional seja muito difícil. A dificuldade que muitos arquitetos de software e desenvolvedores encontram pode estar na falta de informação. Muitos somente ouviram falar ou leram algo a respeito sem entrar em detalhes, e acabaram com más impressões a respeito dos padrões.

É fácil entender os padrões

O objetivo deste trabalho é mostrar como uma aplicação de padrão pode ser feita de maneira muito simples e como ela vai facilitar nossa vida. O padrão escolhido para ser apresentado é o Intercepting Filter, um dos padrões apresentados no livro Core J2EE Patterns. Este padrão é utilizado para facilitar a posterior manutenção e ajudar um outro desenvolvedor a “entender” o código sem ter participado do projeto do sistema, mas logicamente é necessário que o desenvolvedor conheça os padrões.

O detalhamento a seguir supõe que o leitor conheça um pouco da linguagem Java e um pouco da tecnologia Servlets.

Segurança para as páginas da web

O intercepting filter é um padrão utilizado para verificar a validade da solicitação que está sendo feita para uma requisição de página na web. Toda vez que um usuário abre um navegador Internet (browser) e digita um endereço, uma solicitação é gerada e entregue a um servidor na web, que por sua vez interpreta a solicitação e devolve uma resposta (página web).

Imagine um site que contém uma área restrita, onde o internauta deve fazer um cadastro e se identificar toda vez que quiser acessar aquela parte do site. Tem-se que criar uma proteção para que os não-identificados não obtenham sucesso em uma tentativa mal-intencionada.

Isto pode ser feito com uma simples validação na sessão do usuário. Sessão são informações de um usuário que está navegando pelo site, elas ficam armazenadas no servidor web agregadas a um identificador, que é enviado para o navegador do usuário no momento do login e toda vez que este usuário realizar uma nova solicitação, esta identificação é enviada junto, assim o servidor web pode identificar o usuário de uma forma única.

Muitas vezes são necessárias mais que uma simples validação na sessão para ter uma autenticação válida, pode ser que seja necessário saber se o usuário está em dia com seu pagamento correspondente à assinatura no site ou se o usuário tem um nível de acesso que permite o sucesso na obtenção da página solicitada.

Solução, ou não, para o problema

É muito comum, ao analisar um código-fonte de um sistema, encontrar vários “if’s” fazendo as validações necessárias para autenticar o usuário, e pior ainda é ver este treco de código repetido em todos os arquivos que necessitem deste recurso. Quando precisar incluir uma nova verificação, o desenvolvedor vai copiando em todos os locais que necessita da autenticação, podendo ser penalizado pelo “esquecimento” em algum dos arquivos que tinha necessidade da proteção.

Aplicando o padrão

A solução proposta utiliza o modelo mais simples deste padrão (Intercepting Filter), seguindo o objetivo de que o leitor possa entender como o padrão foi aplicado e, conseqüentemente, para maiores detalhes o livro deve ser consultado. Uma representação gráfica desta aplicação pode ser vista na figura 1.

Figura 1 - Diagrama do padrão Intercepting Filter

A solicitação chega até o servidor, que por sua vez invoca o Servlet FornecePagina, que verifica qual é a página que o usuário solicitou por meio de um parâmetro chamado de “pagina”. Instancia a classe correspondente e antes de efetuar seu processamento chama a classe FilterMananger, responsável por gerenciar os filtros. Nela é instanciada a classe FilterChain que funciona como um ordenador seqüencial de funcionalidades de verificação. Estes verificadores são as classes Filter (DebugFilter, LoginFilter e AuditFilter no diagrama). Se nenhuma exceção ocorrer, então é chamado o método execute da classe alvo, solicitada pelo usuário.

Interpretando o código-fonte

Pareceu difícil? Uma análise no código-fonte dos exemplos deve clarear as idéias. Note que independentemente de aplicar o padrão, a estrutura adotada consiste em fazer solicitações sempre para o Servlet FornecePagina passando um parâmetro denominado “pagina”, e o valor deste parâmetro é o nome da classe que pretendemos instanciar e executar. A classe alvo deve implementar a interface Filter, descrito na listagem 4. Esta interface apenas implementa o método “execute”. Um exemplo de solicitação poderia ser:

Se esta solicitação for feita por um navegador web, o método doGet do Servlet FornecePagina será invocado, chamando em seguida o método doPost que processará a solicitação. Os métodos doGet e doPost são chamados pelo navegador dependendo de como a solicitação é feita, no nosso exemplo o tratamento é o mesmo para qualquer um deles, portanto apontamos o método doGet para chamar o método doPost.

Na seqüência é coletado o parâmetro “pagina” que se não for enviada será gerada uma exceção com a mensagem “não foi solicitado uma página”. O valor atribuído a este atributo deve ser o nome de uma classe, que será instanciada e sua referência será atribuída à variável “target”. Se não fosse utilizar o padrão para verificar se a solicitação pode invocar a classe, os dois últimos comandos da listagem um poderia ser substituído pelo comando:

Seguindo o objetivo de utilizar os padrões, a classe FilterMananger (listagem 2) é instanciada e invocado o seu método processFilter, passando como parâmetro a classe a ser executada, além das referências request e response.

Seguindo em frente e analisando o código da classe FilterMananger, ela instancia a classe FilterChain e invoca o método processFilter que, não ocorrendo problemas, irá invocar a classe alvo, requisitada pelo usuário.

A classe FilterChain (listagem 3) tem a responsabilidade de fazer verificações seqüenciais de filtros. É nesta classe que devem ser incluídas as novas classes verificadoras. Basta criar uma classe que implementa a interface Filter e incluir através do método addFilter desta classe. No método processFilter cada um dos filtros cadastrados será chamado seqüencialmente. Note que os filtros podem ser cadastrados também na classe FilterManager.

As outras classes DebugFilter, LoginFilter e AuditFilter (listagens 5, 6 e 7 respectivamente) são, neste exemplo, bastante parecidas e a única coisa que fazem é imprimir na janela do console uma mensagem para provar que foi executada, mas numa aplicação real cada um destes filtros irá executar uma operação específica para autenticar a solicitação.

Conclusões

Com a utilização do padrão, todas as solicitações passam pelo processo de verificação que tem os filtros acopláveis, se houver mudança no processo de autenticação, basta criar uma nova classe e acoplar ao FilterChain. Se houver muitos filtros e estes forem independentes uns dos outros, então pode ser interessante abrir threads para facilitar o trabalho.

Caso queira utilizar este recurso em páginas JSP, uma boa idéia seria utilizar TAGLIB, criando-se uma tag específica de autenticação e colocando-a em todas as páginas que necessitam de autenticação.

Pode dar um pouco mais de trabalho para criar o processo de autenticação padronizado, mas é muito mais fácil de um outro desenvolvedor entender o que ocorre, auxiliado da documentação do sistema (projeto). Assim, a manutenção é reduzida com probabilidade quase nula do desenvolvedor “esquecer” de atualizar em alguma página ou arquivo. Roger S. Presmmam cita em seu livro Engenharia de Software: “a manutenção é responsável por cerca de 80% do custo total do software, durante todo seu ciclo de vida”.

1. FornecePagina.java

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import java.io.IOException;

public class FornecePagina extends HttpServlet{

public void init(ServletConfig conf) throws ServletException {
super.init(conf);
}

public void doGet(HttpServletRequest request,HttpServletResponse response)
throws ServletException,IOException {
doPost(request,response);
}
public void doPost(HttpServletRequest request,HttpServletResponse response)
throws ServletException,IOException {

// pega a página destino
Filter target;
String sPagina = request.getParameter(“pagina”);
if(sPagina==null)
throw new ServletException(“Não foi solicitado uma página!”);
try {
Class cls=Class.forName(sPagina);
target =(Filter)cls.newInstance();
}
catch(Exception _e) {
throw new ServletException(“Não existe a página solicitada!”);
}

// envia para a classe FilterManager
FilterManager filterManager=new FilterManager();
filterManager.processFilter(target,request,response);
}
}

2. FilterManager.java

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;
import java.io.IOException;

public class FilterManager{
public void processFilter(Filter target,
HttpServletRequest request,HttpServletResponse response)
throws ServletException, IOException {

// chama a FilterChain
FilterChain filterChain = new FilterChain();
filterChain.processFilter(request,response);

// Se não ocorreu nenhuma exceção, a página pode ser liberada
target.execute(request,response);
}
}

3. FilterChain.java

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;
import java.io.IOException;
import java.util.Vector;
import java.util.Iterator;

public class FilterChain{

private Vector myFilters = new Vector();

public FilterChain() {
// neste exemplo mais simples os filtros são incluidos aqui
addFilter(new DebugFilter());
addFilter(new LoginFilter());
addFilter(new AuditFilter());
}

public void processFilter(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
Filter filter;

// chama sequenciamente cada um dos filtros
Iterator filters = myFilters.iterator();
while(filters.hasNext())
{
filter=(Filter)filters.next();
filter.execute(request,response);
}
}

public void addFilter(Filter filter){
myFilters.add(filter);
}
}

4. Filter.java

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;
import java.io.IOException;

public interface Filter
{
public void execute(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException;
}

5. DebugFilter.java

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;
import java.io.IOException;

public class DebugFilter implements Filter{
public void execute(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {

// implementação do filtro

System.out.println(“DebugFilter”);
}
}

6. LoginFilter.java

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;
import java.io.IOException;

public class LoginFilter implements Filter{
public void execute(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// implementação do filtro

System.out.println(“LoginFilter”);
//throw new ServletException(“Login inválido !”);
}
}

7. AuditFilter.java

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;
import java.io.IOException;

public class AuditFilter implements Filter{
public void execute(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {

// implementação do filtro

System.out.println(“AuditFilter”);
}
}

8. PaginaExemplo.java

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;
import java.io.PrintWriter;
import java.io.IOException;

public class PaginaExemplo implements Filter{
public void execute(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {

PrintWriter out = response.getWriter();

// Implementação da página
out.println(“<html><body>Página solicitada”);
out.println(“</body></html>”);
}
}

Referências

1. ALUR, D. et al. Core J2EE Patterns. Rio de Janeiro: Campus, 2002.

2. BODOFF, S.;GREEN, D. Tutorial do J2EE. Rio de Janeiro: Campus, 2002.

3. GAMMA, E. et al. Padrões de projeto: soluções reutilizáveis de software orientado a objetos. Porto Alegre: Bookman, 2000.

4. PRESSMAN, R. S. Engenharia de software. Rio de Janeiro: Mc Graw Hill, 2002.