Mostrando postagens com marcador java. Mostrar todas as postagens
Mostrando postagens com marcador java. Mostrar todas as postagens

quarta-feira, 3 de março de 2010

Objetos fake em diversas linguagens para divertir minha vó

Quase todas as linguagens que trabalhei possuem ferramentas para criar objetos fakes e assim auxiliar na construção de testes. Mas e se essas ferramentas não existissem? Estaríamos perdidos? Claro que não! Mocks e stubs podem ser criados de diversas formas diferentes nas diversas linguagens.

Esse é um ótimo assunto pra se discutir em um jantar de família. Quer coisa mais divertida que explicar pra sua avó como construir objetos fake em diversas linguagens? Por exemplo um simples stub que conta quantas vezes um método que notifica visualização de um filme em um serviço externo é chamado. Algo similar ao que o código abaixo faz. Diversão garantida!

servico.should_receive(:notificar_visualizacao!).with(filme).once

Em Ruby

Em ruby podemos simplesmente criar uma nova classe em tempo de execução ou adicionar um método a um objeto qualquer. É a maneira mais simples de garantir um sorrisão da sua vó tamanha facilidade. No caso do exemplo abaixo resolvi adicionar métodos a um objeto qualquer e ao final executo o teste utilizando o rspec.

servico_fake = Object.new
servico_fake.instance_eval do
def notificar_visualizacao!(filme)
@filme_visualizado = filme
@quantidade_visualizacoes = quantidade_visualizacoes + 1
end
def filme_visualizado
@filme_visualizado
end
def quantidade_visualizacoes
@quantidade or 0
end
end

filme = Filme.new
filme.servico = servico_fake
filme.visualizar!

servico_fake.filme.should be_equal(filme)
servico_fake.quantidade.should == 1

Em Python

Em Python é possivel alterar métodos de instancias em tempo de execução, mas não é possivel adicionar métodos a um objeto da classe object. Neste momento minha vozinha fica decepcionada. Para fazer algo semelhante ao que fizemos em ruby teríamos então que instanciar um objeto da classe original e só depois modificar o método. Ficaria mais ou menos assim:

filme_visualizado = None
quantidade_visualizacoes = 0

def notificar_visualizacao_fake(filme):
global filme_visualizado, filme_visualizado
filme_visualizado = filme
quantidade_visualizacoes += 1

servico_fake = ServicoExterno()
servico_fake.notificar_visualizacao = notificar_visualizacao_fake

Esta opção passa a ser ruim quando o construtor da classe original executa algumas tarefas que são custosas para o nosso teste. Por isso, acho que em python o melhor para o este caso é criar uma classe em tempo de execução, deixando minha vó um pouco mais alegre, conforme exemplo abaixo:

class ServicoFake(object):
def __init__(self):
self.filme_visualizado = None
self.quantidade_visualizacoes = 0
def notificar_visualizacao(self,filme):
self.filme_visualizado = filme
self.quantidade_visualizacoes += 1

servico_fake = ServicoFake()

filme = Filme()
filme.servico = servico_fake
filme.visualizar()

assert servico_fake.filme_visualizado is filme
assert servico_fake.quantidade_visualizacoes == 1

Em Java

Uma forma "simples" de fazer em Java é criando uma nova classe para herdar a original e sobrescrever somente o método desejado. Mas aí cairíamos no mesmo problema que discutimos sobre um possível construtor com código muito custoso na superclasse. E minha vó, que apesar de repetir muitas vezes as mesmas histórias, não gosta de ouvir as nossas repetidas.

Uma alternativa que temos é extrair uma interface da classe original para que em nosso teste a gente possa implementar essa interface da maneira que quisermos. Ficaria mais ou menos assim:

//ServicoExterno.java
public interface ServicoExterno {
void notificarVisualizacao(Filme filme);
}

//ServicoExternoHTTP.java
public class ServicoExternoHTTP implements ServicoExterno {
public ServicoExternoHTTP() {
//Faz um monte de coisas
}
public void notificarVisualizacao(Filme filme) {
//Faz mais coisas ainda
}
}

Então em nosso teste a gente cria uma classe implementando a interface recém criada:

public class ServicoExternoFake implements ServicoExterno {
public int quantidade_visualizacoes = 0;
public Filme filme_visualizado = null;

public void notificarVisualizacao(Filme filme) {
filme_visualizado = null;
quantidade_visualizacoes++;
}
}

E depois, mesmo que neste ponto minha vó já esteje dormindo, a gente utiliza a classe fake criada no teste:

ServicoExternoFake servicoFake = new ServicoExternoFake();

Filme filme = new Filme();
filme.setServico(servicoFake);
filme.visualizar();

assertSame(filme,servicoFake.filme_visualizado);
assertEquals(1,servicoFake.quantidade_visualizacoes);

Parece que minha avó não gostou da história. Ela começou tão dinâmica e foi ficando cadas vez mais devagar. Sem dúvida eu deveria ter contato ao contrário.

quinta-feira, 18 de setembro de 2008

Cópia exclusiva de coleção

Por serem de díficil identificação, problemas de concorrência acabam indo pra produção sem sequer serem notado em ambiente de testes e desenvolvimento. Erros acabam ocorrendo de maneira intermitente, causando dores de cabeça e acessos de insanidade nos desenvolvedores. Martin Fowler em seu livro Patterns of Enterprise Application Architecture identifica algumas soluções para evitar problemas de concorrência relativos a integridade de dados. Contudo há um outro problema que não está catalogado neste livro: A concorrência de informações na memória.

Ao guardar objetos em uma coleção que é compartilhada por outras threads, é necessário tomar providências para que não sejam lançadas exceções inesperadas. A medida básica é garantir que os pontos de acesso às coleções compartilhadas devem obter exclusividade sobre seu uso com a diretiva synchronized. Veja abaixo um exemplo de classe que torna o uso de uma lista à prova de erros de concorrência:

public class ListaSegura {
private List lista = new ArrayList();
public void adiciona(Object objeto) {
synchronized (lista) {
lista.add(objeto);
}
}
public void remove(Object objeto) {
synchronized (lista) {
lista.remove(objeto);
}
}
public void listar() {
synchronized (lista) {
for (Object o : lista) {
System.out.println(o);
}
}
}
}

É uma solução simples e que resolve em parte o problema. Contudo na maioria das vezes não podemos obter a exclusividade para iterar sobre uma coleção. No caso mostrado acima isso é possível pois estamos apenas imprimindo o objeto. Mas existem alguns casos em que obter essa exclusividade para a iteração nos traria alguns problemas. Esses casos estão descritos abaixo:

1- Alteração da coleção: No caso de você iterar pela coleção para remover ou adicionar algum objeto a ela. A exclusão ou adição ocorreria no meio da iteração e com isso seria lançada uma exceção de concorrência.Um exemplo simples disso são classes que gerenciam cache e precisam periodicamente remover objetos expirados.

2- Iteração prolongada: Quando a coleção possui objetos demais ou o processo executado durante a iteração é lento, tornando o tempo de exclusividade total muito longo. Isso faria com que o restante do sistema que precisasse utilizar essa coleção ficasse muito tempo aguardando pela exclusividade terminar. Um exemplo comum é a execução de métodos que acessem banco de dados dentro da iteração de um objeto exclusivo.

Para resolver esses dois casos identifiquei o padrão de desenvolvimento Cópia Exclusiva de Coleção. A idéia é obter a exclusividade da lista somente para fazer uma cópia dos itens em outra coleção e então poder iterar sobre esta cópia sem muitas preocupações. Abaixo mostro um exemplo de uma classe Armario que possui muitas Coisas. Se alguma coisa for lixo, ela deve ser removida na execução do método removeLixo.

public class Armario {
private List coisas = new LinkedList();
public void removeLixo() {
List copia = lista();
for (Coisa coisa : copia) {
if (coisa.isLixo())
remove(coisa);
}
}
public List lista() {
List copia = null;
synchronized (coisas) {
copia = new ArrayList(coisas.size());
copia.addAll(coisas);
}
return copia;
}
public void adiciona(Coisa coisa) {
synchronized (coisas) {
coisas.add(coisa);
}
}
public void remove(Coisa coisa) {
synchronized (coisas) {
coisas.remove(coisa);
}
}
}

Com isso o tempo de exclusividade fica restrito ao tempo da cópia para a outra coleção, que ocorre no método lista. Dessa forma temos uma folga no tempo de iteração total e a possibilidade de rearranjar a coleção, adicionando ou removendo itens a ela. É importante ressaltar que o melhor jeito de evitar o calafrio de receber um java.util.ConcurrentModificationException é evitar a utilização de objetos compartilhados entre threads. Quando isso não é possível, o jeito é utilizar um padrão como Cópia Exclusiva de Coleção.

quarta-feira, 3 de setembro de 2008

KeyDown e KeyUp para applets java

Um grande problema da interface KeyListener do Java é não possuir uma maneira de saber se uma ou mais teclas estão pressionadas ao mesmo tempo, e quando elas realmente são soltas. Quando você mantém uma tecla pressionada os eventos keyPressed e keyReleased são ativados consecutivamente, e se você pressiona e segura outra tecla, o keyReleased da tecla anterior para de ser notificado, seguido por uma notificação consecutiva do keyPressed e keyReleased da nova tecla. Isso é facilmente demonstrado no applet abaixo:
public class ComProblema extends Applet implements KeyListener {
    @Override
    public void init() {
        addKeyListener(this);
    }
    public void keyPressed(KeyEvent e) {
        System.out.println("Pressionou: " + e.getKeyCode());
    }
    public void keyReleased(KeyEvent e) {
        System.out.println("Soltou: " + e.getKeyCode());
    }
    public void keyTyped(KeyEvent e) {}
}
Ao executarmos esse applet clicando e segurando a tecla "a" por alguns segundos, depois apertar e segurar a "w" por outros tantos e ao final soltar as duas, teremos o seguinte resultado:
Pressionou: 65
Soltou: 65
Pressionou: 65
Soltou: 65
Pressionou: 65
Soltou: 65
Pressionou: 65
Soltou: 65
Pressionou: 65
Soltou: 65
Pressionou: 65
Soltou: 65
Pressionou: 65
Pressionou: 87
Soltou: 87
Pressionou: 87
Soltou: 87
Pressionou: 87
Soltou: 87
Pressionou: 87
Soltou: 87
Pressionou: 87
Soltou: 87
Pressionou: 87
Soltou: 87
Pressionou: 87
Soltou: 87
Pressionou: 87
Soltou: 65
Soltou: 87
Encontrei em diversos fóruns pessoas com o mesmo problema e nenhuma solução. Então, para resolvê-lo implementei uma espécie de KeyAdapter para facilitar a notificação de quando uma tecla é apertada e quando ela é solta. O uso de exemplo dela ficaria como o mostrado abaixo:
public class SemProblema extends Applet {
    @Override
    public void init() {
        super.init();
        addKeyListener(new CumulativeKeyAdapterImpl());
    }
}
class CumulativeKeyAdapterImpl extends AcumuladorKeyAdapter {
    @Override
    public void keyDown(KeyEvent e) {
        System.out.println("Down: " + e.getKeyCode());
    }
    @Override
    public void keyUp(KeyEvent e) {
        System.out.println("Up: " + e.getKeyCode());
    }
}
O resultado fazendo a mesma combinação de teclas apertadas no teste anterior neste outro applet, seria como o mostrado a seguir:
Down: 65
Down: 87
Up: 65
Up: 87
Você também pode ver outros exemplos de applets utilizando o AcumuladorKeyAdapter aqui. Para utilizar esse recurso basta baixar os fontes e binários da timotta-api e colocar o timotta-api-applet.jar no atributo archive da sua tag applet, como demonstrado no exemplo abaixo:
<applet code="br.com.timotta.applet.MostraTeclas" 
    archive="timotta-api-applet-exemplos.jar,timotta-api-applet.jar"
    width=256 height=256 >
</applet>

terça-feira, 17 de junho de 2008

Erro esotérico ao inserir clobs e blobs no oracle

A mensagem de erro do Oracle "table or view does not exist" é simples e direta. O problema acontece quando você está fazendo uma série de inserções em uma tabela, utilizando a mesma String de SQL para todas, e lá pela sexta inserção a mensagem de erro aparece:
java.sql.SQLException: ORA-00942: table or view does not exist

Até que você descubra que pode haver algum problema com o sexto registro da tabela, você perde um bom tempo tentando encontrar algum problema de lógica ou alguma desatenção sua. Até mesmo bufa de raiva achando que é algum bug do Oracle. E de certa forma é.

No meu caso descobri que o erro acontecia quando inseria textos grandes demais em uma coluna clob. Encontrei então um documento da oracle explicando como gravar clobs no oracle utilizando JDBC. O macete é configurar o JDBC com o parâmetro SetBigStringTryClob, assim:
Properties propriedades = new Properties();
propriedades.put("user", "meuUsuario" );
propriedades.put("password", "pensaQueVouTeContar?");
propriedades.put("SetBigStringTryClob", "true");
return DriverManager.getConnection("jdbc:oracle:thin:@meuserver.com:filmes", propriedades);

Fazendo isso o estranho erro parou de acontecer. De certa forma acredito que este é um bug do Oracle, pois no mínimo a mensagem de erro deveria ser algo informando que o tamanho do valor excede o permitido na coluna. Ou até informando que não foi possivel inserir devido a erros no preenchimento da coluna tal.

Contudo, essa não foi a unica vez que este erro aconteceu comigo. Ao inserir dados em um campo blob o maldito "table or view does not exist" deu as caras novamente. No caso eu errei o tipo de dado utilizando setString. Mas dessa vez eu já estava calejado, então bastou verificar que para inserir blobs bastaria fazer algo como o mostrado abaixo:
byte[] arr = new byte[] {0,1};
ByteArrayInputStream b = new ByteArrayInputStream(arr);
statement.setBinaryStream(i, b, b.available() );

Mas que fique bem claro que o fato de eu estar inserindo clobs e blobs no banco não quer dizer que eu ache isso uma boa prática e que eu recomende. Ao contrário, acredito que seja melhor na maioria das situações que esses dados fiquem no sistema de arquivos.

quarta-feira, 27 de fevereiro de 2008

Component do VRaptor só pode ter um construtor público

Nem tudo são flores com o framework MVC VRaptor. Uma classe anotada com Component só pode ter um construtor público. Senão o seguinte erro é exibido na inicialização do container web:
org.vraptor.config.ConfigException: cannot load classes
...
Caused by: org.vraptor.component.InvalidComponentException:
com.globo.ekzameno.vraptor.QuestionarioLogic component has 2 accessible constructors.
This is not desirable as it may reflect optional arguments and create complex attributes.
Ok, ok, isso não é o fim do mundo para o VRaptor, mas me atrapalhou um pouco com os testes unitários, pois eu pretendia fazer injeção de dependência por construtor. Como esse framework não me permite isso, terei que fazer por método set. Ou seja, ao invés de fazer assim:
public TiagoLogic() {
this(new RepositorioImpl());
}
public TiagoLogic(Repositorio repositorio) {
this.repositorio = repositorio;
}
Terei que fazer assim:
public TiagoLogic() {
setRepositorio(new RepositorioImpl());
}
public void setRepositorio(Repositorio repositorio) {
this.repositorio = repositorio;
}
Não é um grande problema, só questão de preferência mesmo. Prefiro por construtor.

NoClassDefFoundError de Expectations no VRaptor 2


Atenção, não consegui mais replicar o problema descrito neste post. Provavelmente a necessidade da classe não era do VRaptor.


Comecei a utilizar o VRaptor em um projeto pessoal e na primeira tentativa obtive um erro que foi simples corrigir, mas que achei pertinente postar aqui porque não encontrei no google ninguém com o mesmo problema e porque a documentação deste framework não informava nada sobre.

O erro que obtive foi o seguinte:
javax.servlet.ServletException: Servlet.init() for servlet vraptor2 threw exception
Cuja causa foi:
java.lang.NoClassDefFoundError: org/jmock/Expectations
Para resolver isso bastou incluir no classpath da aplicação o jar do JMock. Esse jar não é incluso no blank-project oferecido no site do VRaptor, por isso acho que outras pessoas poderão passar pelo mesmo problema.

quinta-feira, 6 de setembro de 2007

Como utilizar uma versão nova do Rhino no Weblogic 8

Em recente post mostrei a vocês como descobri que o weblogic.jar contém as classes do Rhino numa versão antiga. Agora vou mostrar porque tentava descobrir isso e como resolvi a situação.

Tudo começou quando ao subir nossa aplicação, que utiliza o js.jar do Rhino, para o weblogic, começamos a obter a seguinte exceção.
java.lang.NoSuchMethodError: org.mozilla.javascript.Context.initStandardObjects()Lorg/mozilla/javascript/ScriptableObject;
Logo descobri a referida bizarrice do weblogic.jar. Começou então o nosso desafio: Como fazer a nossa aplicação utilizar as classes do Rhino contidas no js.jar que levávamos no WEB-INF/lib e não os do weblogic.jar?

O Phillip Calçado, líder de nossa equipe, me deu uma ótima sugestão. Criar um Class Loader para carregar as classes do jar contido em nossa aplicação. Para fazer isso, extendi a classe URLClassLoader e implementei o método loadClass(String). Veja como ficou o código:
public class RhinoClassLoader extends URLClassLoader {
private static final String PREFIXO_JAVASCRIPT = "org.mozilla.javascript.";
private static final String PREFIXO_CLASSFILE = "org.mozilla.classfile.";

private Map classesCarregadas;

public RhinoClassLoader(URL url) {
super(new URL[]{ url }, RhinoClassLoader.class.getClassLoader() );
classesCarregadas = new HashMap();
}

public Class loadClass(String name) throws ClassNotFoundException {
if( name!=null && ( name.indexOf(PREFIXO_JAVASCRIPT)!=-1 || name.indexOf(PREFIXO_CLASSFILE)!=-1 ) ) {
Object classeCarregada = classesCarregadas.get(name);
if( classeCarregada != null ) {
return (Class) classeCarregada;
}

Class classe = findClass(name);
classesCarregadas.put(name, classe);

return classe;
}
return super.loadClass(name);
}
}
Observe que o construtor recebe uma url, que deve ser o caminho pro arquivo jar do Rhino e a repassa para o contrutor da superclasse. Já no método loadClass nós passamos a carregar somente as classes do Rhino através do método findClass da superclasse URLClassLoader. Este procurará essas classes no jar que informamos. Para as demais classes nós repassamos a chamada para o método loadClass default. Ou seja, somente as classes do Rhino estarão com o ClassLoader diferente.

Outra coisa importante do código é o cache de classes anteriormente carregadas. Ele é extremamente necessário, senão pode ocorrer um erro indicando que duas classes com o mesmo nome foram carregadas.

Para obter então a classe correta do Rhino, basta utilizar o método forName da classe Class para recebê-la. Veja abaixo como ficaria:
URL url = new URL( "file:///path/para/o/WEB-INF/lib/js.jar" );
RhinoClassLoader loader = new RhinoClassLoader( url );
Class classeContext = Class.forName("org.mozilla.javascript.Context",true,loader);
Utilizando o objeto classeContext que representa a classe desejada, você pode então criar novas instancias utilizando a api de reflexão do Java. Mas tome cuidado para não causar um ClassCastException. Isso porque a classe representada em classeContext é diferente da classe de mesmo nome que você faria o cast, pois são de ClassLoaders diferentes. Logo, o seguinte cógido causaria a exceção:
Context contexto = (Context)classeContext.newInstance();
Ou seja, você só pode utilizar classes de outro ClassLoader via api de relection. Um exemplo de como seria utilizar um método de Context nesse caso é mostrado a seguir:
Object contexto = (Object)classeContext.newInstance();
Method metodoDebug = classeContext.getMethod("isGeneratingDebug",null);
Boolean gerandoDebug = (Boolean) metodoDebug.invoke( contexto, null );
Repare que no retorno da invocação do método refletido eu faço um cast. Neste caso eu posso, pois somente as classes dos pacotes org.mozilla.javascript e org.mozilla.classfile foram carregadas com outro ClassLoader.

Esse trabalho todo porque o weblogic não sabe brincar. Custava colocar o jar do Rhino separadinho? Tem certas coisas que não dá pra entender. Já falei isso antes, mas repetir nunca é demais. Cada dia que passa mais odeio o weblogic.

terça-feira, 19 de junho de 2007

Cuidado com SubReports aninhados

Com JasperReport é bom prestar atenção no uso de SubReports aninhados. O problema é com os parâmetros, pois o parâmetro de um relatório não persiste em seus SubReports. Parece pouca coisa, mas tive uma péssima experiência com isso.

Um projeto em que estou trabalhando possui um relatório com um SubReport, e este SubReport possui inclui um outro SubReport. Para achar os arquivos jaspers dos SubReports utilizamos o default de concatenação do parâmetro ${SUB_DIRECTORY}. Acontece que ao definirmos na aplicação este parâmetro, o SubReport não recebe esta definição, de forma que ele acaba utilizando o default na inclusão de seu SubReport. Resultado: Funciona no ambiente de desenvolvimento, mas no ambiente de distribuição o bug acontece.

Para resolver isso é preciso repassar o valor do parâmetro ${SUB_DIRECTORY} para os demais SubReports. Para fazer isso no IReport aperte com botão direito no local de inclusão do SubReport e aperte em properties. Na aba em que se configura o path do SubReport, logo abaixo deste campo existe uma caixa onde se pode definir os valores que devem ser repassados por parâmetro.

quarta-feira, 13 de junho de 2007

Classes de auxílio no uso de JasperReport e IReport

Popular um relatório jasper com uma coleção de beans é simples, o incoveniente é quando se deseja popular com uma coleção de beans que possuem instancias de outros beans como atributo. Para resolver essa sitação, criei dois DataSources do Jasper implementando algo parecido com a EL (Expression language) de forma a navegar seus atributos. São as classes ObjetoDataSource e ColecaoDataSource.

Veja como essas classes funcionam:
- Como funciona a classe ObjetoDataSource para JasperReport
- Como funciona a classe ColecaoDataSource para JasperReport

Se você gostou, pode baixar o projeto timotta-api clicando aqui.

Classe ObjetoDataSource para JasperReport

Com a classe ObjetoDataSource é possível acessar os atributos de qualquer objeto. Basta colocar os campos separados por ponto. Essa classe faz parte do pacote de auxílio no uso de JasperReport e IReport. Mostro abaixo um exemplo simples. Primeiro vejamos as classes de dominio.
public class Pessoa
{
 String nome;
 Date nascimento;
 Endereco endereco;
 List<String> telefones;
 // ... Gets e Sets omitidos ...
}
public class Endereco
{
 String rua;
 short numero;
 // ... Gets e Sets omitidos ...
}

Agora digamos que a gente possui uma intância da classe Pessoa e desejamos imprimir todos seus campos em um relatório Jasper. Para fazer isso basta instanciar um novo ObjetoDataSource, como mostrado abaixo:
Pessoa eu = loadPessoa();
ObjetoDataSource dataSource = new ObjetoDataSource(eu);
JasperReport compilado = JasperCompileManager.compileReport("/tmp/Relatorio.xml");
JasperPrint preenchido = JasperFillManager.fillReport(compilado, new HashMap(), dataSource);
JasperViewer.viewReport(preenchido);

No seu relatorio, para acessar os campos da Pessoa, basta declarar os campos da seguinte forma:
<field name="nome" class="java.lang.String" />
<field name="nascimento" class="java.util.Date" /> 
<field name="telefones" class="java.lang.Object" />
<field name="endereco.rua" class="java.lang.String" />
<field name="endereco.numero" class="java.lang.Short" />

Repare que o campo telefones é declarado como Object, porque a resposta dele não é um List, e sim um ColecaoDataSource. Isso é feito para que se você possa iterar nele utilizando um SubReport.

Neste exemplo, o relatório possuiria apenas uma pessoa, no caso de termos que exibir várias pessoas utiliza-se o ColecaoDataSource. Ou poderiamos ter uma classe, por exemplo, Equipe que possui um atributo do tipo List. Com essas classes as possibilidades são infinitas.

Classe ColecaoDataSource para JasperReport

Com a classe ColecaoDataSource é possível iterar e acessar os atributos dos objetos presentes em uma coleção. Funciona da mesma forma que a ObjetoDataSource, com a diferença de que por ser uma coleção, permite iterar entre uma coleção de objetos. Essa classe também faz parte do pacote de auxílio no uso de JasperReport e IReport. Mostro abaixo um exemplo simples. Primeiro vejamos as classes de dominio:
public class Pessoa
{
 String nome;
 Date nascimento;
 Endereco endereco;
 List<String> telefones;
 // ... Gets e Sets omitidos ...
}
public class Endereco
{
 String rua;
 short numero;
 // ... Gets e Sets omitidos ...
}

Agora vou preencher o relatorio com uma lista de pessoas:
List pessoas = loadPessoas();
ColecaoDataSource dataSource = new ColecaoDataSource(eu);
JasperReport compilado = JasperCompileManager.compileReport("/tmp/Relatorio.xml");
JasperPrint preenchido = JasperFillManager.fillReport(compilado, new HashMap(), dataSource);
JasperViewer.viewReport(preenchido);

No seu relatorio, para acessar os campos da Pessoa, basta declarar os campos da mesma forma como foi feita com a classe ObjetoDataSource. Viu como é fácil?

Agora digamos que ao invés de List, queremos utilizar um Map. É a mesma coisa, só a declaração dos campos no relatório fica um pouco diferente. Isso porque passamos a iterar não por objetos Pessoa, mas por objetos Entry. Veja abaixo:
Map<Integer,Pessoa> pessoas = loadPessoas();
ColecaoDataSource dataSource = new ColecaoDataSource(eu);
JasperReport compilado = JasperCompileManager.compileReport("/tmp/Relatorio.xml");
JasperPrint preenchido = JasperFillManager.fillReport(compilado, new HashMap(), dataSource);
JasperViewer.viewReport(preenchido);

No relatório então, os campos ficariam assim:
No seu relatorio, para acessar os campos da Pessoa, basta declarar os campos da seguinte forma:
<field name="key" class="java.lang.Integer" />
<field name="value.nome" class="java.lang.String" />
<field name="value.nascimento" class="java.util.Date" /> 
<field name="value.telefones" class="java.lang.Object" />
<field name="value.endereco.rua" class="java.lang.String" />
<field name="value.endereco.numero" class="java.lang.Short" />


Simples não é?

Download da timotta-api

Faça o download dos fontes, testes, exemplos e binários:

Versão 0.0.2: http://www.divshare.com/download/5301025-124
Versão 0.0.1: http://www.divshare.com/download/936101-637

Veja as funcionalidades disponíveis na timotta-api 0.0.2:

quarta-feira, 30 de maio de 2007

Load alto causado por um HashMap

Recentemente tivemos um problema em produção com nosso sistema. O load do servidor aumentava gradativamente chegando a um ponto em que era necessário reinicia-lo. Para quem não sabe, o load é um cálculo que indica quantas threads estão executando ou tentando executar em um determinado período. Ou seja, este problema de load alto poderia então ser causado por uma grande quantidade de acessos, no entanto, o load não baixava em periodos de pouco acesso, e até mesmo quando tirávamos o servidor do balanceamento.

Para analisar as threads que estavam executando, lançamos mão de um Thread Dump em dois momentos, antes de tirarmos a máquina do balanceamento, e outra uma hora depois de tirá-la. No Java, para obter um Thread Dump basta executar o seguinte comando, onde o processo_id é o pid do processo Java que queremos analisar.
kill -QUIT processo_id

Com o resultado em mãos descobrimos que antes e depois de tirarmos o servidor do balanceamento havia uma thread executando (runnable) travada na linha 325 de um HashMap. Veja a descrição dessa thread abaixo:
"ExecuteThread: '94' for queue: 'default'" daemon prio=5 tid=0x00b525a0 nid=0x2c runnable [0x63bfe000..0x63bffc28]
at java.util.HashMap.get(HashMap.java:325)
(...)

Foi então que descobrimos o problemas. Procuramos no Google por esta linha e encontramos diversas pessoas com o mesmo problema e até mesmo notificações deste possível bug para Sun. Contudo, isto não é um bug. O que acontece é que a classe HashMap não é thread safe, nem mesmo para seus métodos put e get. Portanto, se diversas threads gravam e recuperam valores de um HashMap, pode ocorrer problemas como esse. De fato, se olharmos o código de HashMap, podemos perceber que o método get tem grandes possibilidades de entrar em loop infinito.

A solução recomendade pela Sun é utilizar o wrapper sincronizado para mapas Collections.synchronizedMap(new HashMap()), ou utilizar a classe ConcurrentHashMap da api de concorrência incorporada ao Java 5, a java.util.concurrent. Para quem não pode utilizar o Java 5, pode-se utilizar a versão back-port da java.util.concurrent. No nosso caso, utilizamos essa versão backport.

quinta-feira, 24 de maio de 2007

Configurando um tópico JMS no JBoss

Para configurar um tópico de Java Message Service no JBoss (a versão que estou utilizando é 4.0.5.GA) basta editar o arquivo %JBOSS_HOME%/server/default/deploy/jms/jbossmq-destinations-service.xml iserindo uma tag XML como mostrada abaixo:
<mbean code="org.jboss.mq.server.jmx.Topic" 
name="jboss.mq.destination:service=Topic,name=nomeDoMeuTopico">
<depends optional-attribute-name="DestinationManager">jboss.mq:service=DestinationManager</depends>
<depends optional-attribute-name="SecurityManager">jboss.mq:service=SecurityManager</depends>
<attribute name="SecurityConf">
<security>
<role name="guest" read="true" write="true"/>
<role name="publisher" read="true" write="true" create="false"/>
<role name="durpublisher" read="true" write="true" create="true"/>
</security>
</attribute>
</mbean>

Não sei bem para que serve cada um dos parâmetros pois estou começando a estudar JMS agora.

Depois de configurar um tópico eu crio um servlet de exemplo que lê um parâmetro e o escreve no tópico como uma mensagem de texto. Observer que o lookup que fazemos no InitialContext para pegar o tópico utiliza o mesmo nome do tópico que configuramos adicionado com o prefixo "topic/":
@Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
Context jndiContext = null;
TopicConnectionFactory topicConnectionFactory = null;
TopicConnection topicConnection = null;
TopicSession topicSession = null;
Topic topic = null;
TopicPublisher topicPublisher = null;

try
{
jndiContext = new InitialContext();

topicConnectionFactory = (TopicConnectionFactory) jndiContext.lookup("TopicConnectionFactory");
topic = (Topic) jndiContext.lookup("topic/nomeDoMeuTopico");

topicConnection = topicConnectionFactory.createTopicConnection();
topicSession = topicConnection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
topicPublisher = topicSession.createPublisher(topic);

TextMessage mensagem = topicSession.createTextMessage();
mensagem.setText( request.getParameter("m") );
topicPublisher.publish(mensagem);
}
catch (NamingException e)
{
log.error("",e);
}
catch (JMSException e)
{
log.error("",e);
}
finally
{
if (topicPublisher != null) try { topicPublisher.close(); } catch (JMSException e) {}
if (topicConnection != null) try { topicConnection.close(); } catch (JMSException e) {}
}

response.getWriter().print("Mensagem publicada");
}

Para ler as mensagens enviadas ao tópico é preciso definir um listener, implementando a interface MessageListener, como é mostrado no código abaixo:
public class MensagemDeTextoListener implements MessageListener
{
public void onMessage(Message mensagem)
{
TextMessage msg = null;

try
{
if (mensagem instanceof TextMessage)
{
msg = (TextMessage) mensagem;
System.out.println("Leu mensagem: " + msg.getText());
}
else
{
System.out.println("Mensagem de tipo invalido: " + mensagem.getClass().getName());
}
}
catch (JMSException e)
{
log.error("",e);
}
}
}

Finalmente é preciso inscrever uma instancia deste listener como leitor do tópico. No exemplo abaixo é mostrado como inscrever um listener no tópico anteriormente definido, através do método contextInitializer de um ServletContextListener.
private TopicConnection topicConnection;

private void contextInitialized(ServletContextEvent evento)
{
Context jndiContext = null;
TopicConnectionFactory topicConnectionFactory = null;
TopicSession topicSession = null;
Topic topic = null;
TopicSubscriber topicSubscriber = null;
MensagemDeTextoListener topicListener = null;
TextMessage message = null;

try
{
jndiContext = new InitialContext();
topicConnectionFactory = (TopicConnectionFactory)
jndiContext.lookup("TopicConnectionFactory");
topic = (Topic) jndiContext.lookup("topic/mensagemTopic");

topicConnection = topicConnectionFactory.createTopicConnection();
topicSession = topicConnection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
topicSubscriber = topicSession.createSubscriber(topic);
topicListener = new MensagemDeTextoListener();
topicSubscriber.setMessageListener(topicListener);
topicConnection.start();
}
catch (NamingException e)
{
log.error("",e);
}
catch (JMSException e)
{
log.error("",e);
}
}
private void finalizarSubscriber()
{
try
{
topicConnection.close();
}
catch (JMSException e)
{
log.error("",e);
}
}

Pronto, aparentemente o exemplo funcionaria, mas não, quando inicializar seu JBoss, ocorrerá o seguinte erro: javax.jms.IllegalStateException in JBOSS: This method is not applicable inside the application server. See the J2EE spec, e.g. J2EE1.4 Section 6.6. Esse erro se dará exatamente na linha em que você adiciona o listener das mensagens ao tópico.

Ainda não entendi bem o porque isso acontece, mas achei a solução: Basta alterar o arquivo %JBOSS_HOME%/server/default/deploy/jms/jms-ds.xml adicionando o parâmetro Strict à connection-factory de jndi-name JmsXA. Veja abaixo:
<tx-connection-factory>
<jndi-name>JmsXA</jndi-name>
<xa-transaction/>
<rar-name>jms-ra.rar</rar-name>
<connection-definition>org.jboss.resource.adapter.jms.JmsConnectionFactory</connection-definition>
<config-property name="SessionDefaultType" type="java.lang.String">javax.jms.Topic</config-property>
<config-property name="JmsProviderAdapterJNDI" type="java.lang.String">java:/DefaultJMSProvider</config-property>
<config-property name="Strict" type="java.lang.Boolean">false</config-property>
<max-pool-size>20</max-pool-size>
<security-domain-and-application>JmsXARealm</security-domain-and-application>
</tx-connection-factory>

quarta-feira, 23 de maio de 2007

Cliente de IRC em Java

Na verdade neste artigo não faremos um cliente de IRC, faremos um simple bot. Mas, a biblioteca que apresento também serve para fazer clientes de IRC, é a PircBot (Java IRC Bot Framework). Para funcionar você pode utilizar um servidor de IRC já existente, ou instalar um na sua máquina. No caso eu preferi intalar um, e utilizei o UnrealIRCd, que possui versões para Windows e Linux.

O UnrealIRCd é um bom servidor de IRC pois é fácil, na medida do possível, de configurar. Isso porque as mensagens de erro dele são bem explicativa, dizendo até mesmo em qual linha do arquivo de configuração há uma entrada incorreta ou faltando.

Depois de escolher servidor criamos uma classe extendendo org.jibble.pircbot.PircBot, essa classe representará o usuário que criamos para se conectar no IRC. A idéia é fazer um usuário que desconecte do IRC quando receber uma mensagem "Sai vagabundo" de qualquer pessoa em qualquer sala.
public class BotVagabundo extends PircBot
{
public BotVagabundo(String nome)
{
this.setName(nome);
}

@Override protected void onMessage(String channel, String sender, String login, String hostname, String message)
{
System.out.println("Recebi em [" + channel + "] do usuario [" + sender + "] a mensagem [" + message + "]");

if("Sai vagabundo".equals(message))
{
this.disconnect();
this.dispose();
}
}
}

O método PircBot.setName(String) define o nick do usuário. O método onMessage, é acionado quando alguma mensagem é recebida, seja num dos canais em que ele se encontra ou em um chat privado.

Agora vamos ver como se faz para conectar esse usuário no IRC:
BotVagabundo bot = new BotVagabundo("Chapun");

bot.setVerbose(true);
bot.connect("irc.chapun.com");
bot.joinChannel("#brasil");

bot.sendMessage("#brasil", "Eu sou um bot");

No caso eu não só fiz o bot conectar no servidor irc.chapun.com (coloquei o meu servidor local com este host) como fiz ele entrar no canal #brasil e enviar uma mensagem informando que é um bot.

Viu como é fácil?

Além disso, o PircBot tem diversas outras opções, como pegar a lista de canais, lista de usuários em um canal, kikar um usuário... Vale o tempo perdido dar uma olhada no site dele.

quarta-feira, 16 de maio de 2007

Teste unitário em funções javascript utilizando o JUnit

Um dos grandes problemas do javascript é a falta de testabilidade. Vou mostrar aqui nesse artigo uma forma que encontrei de testar as funções de arquivos javascripts utilizando o JUnit. Ah! Claro, isso só é possível se você estiver utilizando o Java 6, que possui suporte a linguagens de script e já vem nativamente com suporte a javascript.

Primeiro criamos o arquivo funcoes.js contendo uma função para validar um determinado formulário. O objetivo é retornar verdadeiro se todos os campos estiverem preenchidos e falso no caso contrário. O formulário html deve ter dois campos: nome e senha. Inicialmente deixaremos essa função sem nenhum código, pois criaremos os testes primeiro:
function validaFormulario(f)
{
return false;
}

Em seguida criamos uma classe para realizar os testes unitários contendo o método testValidaFormulario e outras duas classes internas para servir de Mock do formulário html e dos campos do formulário, HtmlFormMock e HtmlFieldMock respectivamente. Segue o código:
package junit.script;
import junit.framework.TestCase;

public class FuncoesTest extends TestCase
{
public void testValidaFormulario() throws FileNotFoundException, ScriptException, NoSuchMethodException
{
//Aqui entrarão os testes
}

public class HtmlFormMock implements Serializable
{
public HtmlFieldMock nome = new HtmlFieldMock();
public HtmlFieldMock senha = new HtmlFieldMock();
}
public class HtmlFieldMock implements Serializable
{
public String name;
public String value;
}
}

No método testValidaFormulario da classe de testes começamos então importanto o script funcoes.js, e criando um Invocable para poder executar funções no javascript:
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("js");
engine.eval( new FileReader( "C:/Projetos/testes/web/funcoes.js" ) );
Invocable invocable = (Invocable) engine;

Em seguida preenche-se o Mock de formulário da seguinte forma:
HtmlFormMock f1 = new HtmlFormMock();
f1.nome.name = "nome";
f1.nome.value = "Tiago";
f1.senha.name = "senha";
f1.senha.value = "123456";

Tendo o invocador de funções de script (Invocable) e o objeto mock, basta alterar os atributos deste objetos para utilizá-lo como parâmetro do formulário por diversas vezes. Veja abaixo os testes que defini para esta função, repare que fiz dois testes para verificar se o campo "nome" e o campo "senha" existem no formulário, pois é possível que criem um formulário html sem o campos:

//Todo formulario preenchido
Boolean b = (Boolean) invocable.invokeFunction("validaFormulario", new Object[] { f1 } );
assertTrue("Formulario todo preenchido deve retornar true", b.booleanValue());

//Senha preenchida com nulo
f1.senha.value = null;
b = (Boolean) invocable.invokeFunction("validaFormulario", new Object[] { f1 } );
assertFalse("Senha preenchida com nulo deve retornar falso", b.booleanValue());

//Senha preenchida com vazio
f1.senha.value = "";
b = (Boolean) invocable.invokeFunction("validaFormulario", new Object[] { f1 } );
assertFalse("Senha preenchida com vazio deve retornar falso", b.booleanValue());

//Nome preenchido com nulo
f1.senha.value = "outra";
f1.nome.value = null;
b = (Boolean) invocable.invokeFunction("validaFormulario", new Object[] { f1 } );
assertFalse("Nome preenchido com nulo deve retornar falso", b.booleanValue());

//Nome preenchido com vazio
f1.nome.value = "";
b = (Boolean) invocable.invokeFunction("validaFormulario", new Object[] { f1 } );
assertFalse("Nome preenchido com vazio deve retornar falso", b.booleanValue());

//Campo nome do formulario nao existir deve retornar falso
f1.nome = null;
b = (Boolean) invocable.invokeFunction("validaFormulario", new Object[] { f1 } );
assertFalse("Campo nome do formulario nao existe entao deve retornar falso", b.booleanValue());

//Campo senha nao pode ser nulo
f1.nome = new HtmlFieldMock();
f1.nome.name = "nome";
f1.nome.value = "Tiago";
f1.senha = null;
b = (Boolean) invocable.invokeFunction("validaFormulario", new Object[] { f1 } );
assertFalse("Campo senha do formulario nao existe entao deve retornar falso", b.booleanValue());

Executando esse teste com o JUnit teremos uma falha logo no primeiro teste, pois o este espera que haja um retorno de true e nossa função javascript está sempre retornando false. Alteremos então a função javascript dando-lhe um código aceitável:
function validaFormulario(f)
{
if( f.nome && f.nome.value && f.nome.value.length() > 0 &&
f.senha && f.senha.value && f.senha.value.length() > 0 )
{
return true;
}
return false;
}

Pronto, tendo a função javascript codificada, basta rodarmos o teste no JUnit novamente para verificar que finalmente a função está funcionando de acordo com o específicado. E o melhor, sempre que ocorrer uma alteração no código dela, saberemos se houve alguma quebra de contrato.

Também é possível testar métodos de classes javascript. Basta utilizar o método invokeMethod da interface Invocable, ao invés de invokeFunction. Mas isso eu deixo pra outro artigo.

quarta-feira, 25 de abril de 2007

Como criar funções em sua taglib

Caso você tenha necessidade de uma função que não existe na JSTL, você pode criar facilmente sua própria função. Nesse mini tutorial explicarei como fazer isto. Faremos uma função que retira os simbolos de maior e de menor (<>) dos textos para impedir que tags htmls sejam inseridas numa página. A primeira coisa a se fazer é criar uma classe com um método estático com essa funcionalidade. Veja:
package meupacote;

public class MinhaClasse
{
public static String escapeHtml(String html)
{
String texto = null;
if( html != null )
{
texto = html.replaceAll("<", "& lt;");
texto = texto.replaceAll(">", "& gt;");
}
return texto;
}
}
Como podemos ver, o método escapeHtml recebe um html e substitui todas as ocorrências de < > pelo seu código equivalente em html. O segundo passo é definir no seu arquivo tld essa function, inserindo a tag function e suas sub-tags de configuração. Veja:

<function>
<description>
Substitui tags simbolos de maior e maior pelo seu codigo html
</description>
<name>escapeHtml</name>
<function-class>meupacote.MinhaClasse</function-class>
<function-signature>boolean escapeHtml(java.lang.String)</function-signature>
</function>
As tags mais importantes da configuração da função são "name", que indica por qual nome ela será referenciada no jsp, "function-class", que indica qual classe possui o método equivalente à função e "funtion-signature", que indica qual o método responderá pelas ações da função.

Tendo isso, basta utilizar essa função no seu jsp, importando sua taglib e chamando a função no formato prefixo:nomefuncao(parametros). Veja o exemplo:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="mt" uri="http://programandosemcafeina.blogspot.com/jsp/taglib/mt" %>

<h1><c:out value="${mt:escapeHtml(materia.titulo)}" /></h1>
<p><c:out value="${mt:escapeHtml(materia.texto)}" /></p>
É claro que por eu usar a tag c:out não seria necessária a função escapeHtml, pois essa tag já faz a conversão que implementamos (e muitas outras). Contudo existem situações em que ela seria necessária. Um exemplo é se utilizarmos a Json Taglib. Essa taglib não tem a opção de escapar o html, para ela então faz-se necessário o uso de uma função como a que fizemos no exemplo.

terça-feira, 17 de abril de 2007

Caracteres especiais representados em Unicode

Para evitar problemas com encoding de caracteres especiais é bom utilizar em suas classes Java código unicode ao invés de letras acentuadas. Abaixo relaciono uma tabela de alguns dos caracteres especiais que mais usamos no dia a dia com seu cógigo unicode:
á = \u00e1
à = \u00e0
â = \u00e2
ã = \u00e3
ä = \u00e4
Á = \u00c1
À = \u00c0
 = \u00c2
à = \u00c3
Ä = \u00c4
é = \u00e9
è = \u00e8
ê = \u00ea
ê = \u00ea
É = \u00c9
È = \u00c8
Ê = \u00ca
Ë = \u00cb
í = \u00ed
ì = \u00ec
î = \u00ee
ï = \u00ef
Í = \u00cd
Ì = \u00cc
Î = \u00ce
Ï = \u00cf
ó = \u00f3
ò = \u00f2
ô = \u00f4
õ = \u00f5
ö = \u00f6
Ó = \u00d3
Ò = \u00d2
Ô = \u00d4
Õ = \u00d5
Ö = \u00d6
ú = \u00fa
ù = \u00f9
û = \u00fb
ü = \u00fc
Ú = \u00da
Ù = \u00d9
Û = \u00db
ç = \u00e7
Ç = \u00c7
ñ = \u00f1
Ñ = \u00d1
& = \u0026
' = \u0027
Para colocar o código Unicode em uma String basta substituir o caracter pelo código. Exemplo: String s = "Programando sem cafe\u00edna";

O código do método que usei para gerar essa tabela está abaixo:
public static String geraCodigoUnicode(char letra) {
String hexa = Integer.toHexString( (int)letra );

String prefix;
if( hexa.length() == 1 ) {
prefix = "\\u000";
} else if( hexa.length() == 2 ) {
prefix = "\\u00";
} else if( hexa.length() == 3 ) {
prefix = "\\u0";
} else {
prefix = "\\u";
}

return prefix + hexa;
}

segunda-feira, 16 de abril de 2007

Pra que serve e como funciona o ServletRequestAttributeListener

O ServletRequestAttributeListener é uma interface que serve para monitorar os atributos adicionados, alterados e removidos de uma requisição. Sempre que se adiciona um atributo no objeto ServletRequest, o método attributeAdded do listener que implementa essa interface é chamado. Sempre que se substitui um atributo do objeto ServletRequest, o método attributeReplaced do listener que implementa essa interface é chamado. E finalmente, sempre que se remove um atributo do objeto ServletRequest o método attributeRemoved é chamado.

Veja abaixo o exemplo de uma classe que implementa esse listener para exibir o nome e o valor do parâmetro que foi adicionado, alterado e removido:
import javax.servlet.ServletRequestAttributeListener;
import javax.servlet.ServletRequestAttributeEvent;
import javax.servlet.http.HttpServletRequest;

public class MeuServletRequestAttributeListener implements ServletRequestAttributeListener
{
public void attributeAdded(ServletRequestAttributeEvent e)
{
System.out.println("Atributo adicionado -> nome: "
+ e.getName() + ", valor: " + e.getValue() );
}

public void attributeRemoved(ServletRequestAttributeEvent e)
{
System.out.println("Atributo removido -> nome: "
+ e.getName() + ", valor: " + e.getValue() );
}

public void attributeReplaced(ServletRequestAttributeEvent e)
{
System.out.println("Atributo alterado -> nome: "
+ e.getName() + ", valorAntigo: " + e.getValue() );
}
}

Para testar esse listener criei ainda um Servlet mapeado com o endereço /TesteAtributoRequest.do. Este Servlet faz forward para si mesmo decrementando o atributo "numero" que é guardado na requisição. Veja como fica o código:
public void doGet(HttpServletRequest request, HttpServletResponse response) 
throws IOException, ServletException
{
int numero;

if( request.getAttribute("numero") != null )
{
numero = ( (Integer)request.getAttribute("numero") ).intValue();
numero--;
}
else
{
try
{
numero = Integer.parseInt( request.getParameter("numero") );
}
catch(NumberFormatException e)
{
numero = 3;
}
}

if( numero <= 0 )
{
request.removeAttribute("numero");
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.print("Acaboooou");
}
else
{
request.setAttribute("numero", new Integer(numero));
request.getRequestDispatcher( "/TesteAtributoRequest.do" ).forward(request,response);
}
}

Ao executá-lo mandando o parâmetro "numero" com valor igual a 5 por exemplo, o listener configurado irá exibir a seguinte saída:

Atributo adicionado -> nome: numero, valor: 5
Atributo alterado -> nome: numero, valorAntigo: 5
Atributo alterado -> nome: numero, valorAntigo: 4
Atributo alterado -> nome: numero, valorAntigo: 3
Atributo alterado -> nome: numero, valorAntigo: 2
Atributo removido -> nome: numero, valor: 1

Pra que serve e como funciona o ServletRequestListener

O ServletRequestListener serve para ser notificado quando uma aplicação recebe uma requisição e quando esta requisição é destruída. Nomea-se no caso uma requisição como um objeto ServletRequest. Assim que o objeto ServletRequest é inicializado o método requestInitialized do listener que implementa a interface ServletRequestListener é chamado. Quando o objeto ServletRequest é destruído, o método requestDestroyed é chamado.

Veja um exemplo de um listener que implementa ServletRequestListener abaixo:
import javax.servlet.ServletRequestListener;
import javax.servlet.ServletRequestEvent;
import javax.servlet.http.HttpServletRequest;

public class MeuServletRequestListener implements ServletRequestListener
{
public void requestInitialized(ServletRequestEvent e)
{
HttpServletRequest r = (HttpServletRequest)e.getServletRequest();
System.out.println("Requisicao inicializada: " +
r.getRequestURI() );
}

public void requestDestroyed(ServletRequestEvent e)
{
HttpServletRequest r = (HttpServletRequest)e.getServletRequest();
System.out.println("Requisicao finalizada: " +
r.getRequestURI() );
}
}

No caso, o que o listener faz é escrever na saída padrão a URI requisitada quando uma nova requisição chega à aplicação e quando a requisição é finalizada. É importante notar que mesmo quando é feito uma requisição cujo resultado é um 404, o objeto de requisição é criado e em consequência os métodos do listener são chamados.