sexta-feira, 28 de dezembro de 2007

Problema com as bordas das janelas usando o Beryl

Esse sim foi um sufoco. O Beryl não queria funcionar de jeito nenhum no meu Ubuntu. Diversos probleminhas foram faceis após simples pesquisas no google. Mas o problema maior não tinha resposta alguma. Quando o beryl estava rodando as bordas das janelas não apareciam de jeito algum.

A mensagem de erro do Beryl que me fez acordar para a razão do problema foi a seguinte:
beryl: decoration: property ignored because version is 20070319 and decoration plugin version is 20061011

Percebi que devia haver alguma incompatibilidade entre a versão do beryl e a do emerald ou coisa parecida. E realmente havia. O jeito foi achar uma denominador comum entre a versão do emerald, beryl e demais libs. Para fazer isso precisei fazer um downgrade da parada toda:
sudo apt-get install beryl=0.1.9999.2~0beryl1 
beryl-core=0.1.9999.2~0beryl1
beryl-manager=0.1.9999.2~0beryl1
beryl-plugins-data=0.1.9999.2~0beryl1
beryl-settings=0.1.9999.2~0beryl1
beryl-settings-bindings=0.1.9999.2~0beryl1
emerald=0.1.9999.2~0beryl1
libberyldecoration0=0.1.9999.2~0beryl1
libberylsettings0=0.1.9999.2~0beryl1
libemeraldengine0=0.1.9999.2~0beryl1
emerald-themes=0.1.9999.2~0beryl1 -V

O engraçado é que ontem de madrugada, depois que fiz o downgrade, não funcionou. Mas isso porque eu tinha configurado meu Beryl Manager para utilizar o Window Manager do Compiz, pois este funcionava bem. Contudo, com essa versão mais antiga do beryl, o Compiz parou de funcionar. Mas isso não tentarei consertar, ninguém precisa desse Compiz. Bastou então setar no Beryl Manager o Beryl como Windows Manager para tudo funcionar.

Olha a foto de como ficou meu cubo :)

Cada dia que passa me convenço mais da facilidade do Windows. Mas até que é divertido brincar com esse Ubuntu.

quarta-feira, 19 de dezembro de 2007

Globo Vídeos agora em Flash!

É com muito orgulho que anuncio que finalmente o Globo Vídeos entrou na era do Flash Vídeo. Os vídeos ao vivo, como da Globo News e as cameras do BBB continuarão em Windows Media Player, mas apartir de hoje a grande maioria dos vídeos on demand e íntegras ficarão disponíveis em flash.

Veja alguns exemplos:
Dominó é mania nacional em Cuba
Romulo e Reno: Lenda ou Realidade?
Festival de gelo na Bélgica

terça-feira, 18 de dezembro de 2007

Como obter a largura e a altura de Divs liquidos

Obter os atributos width e height de Divs absolutos é muito simples. A coisa começa a ficar complicada quando seu Div não possui uma altura e uma largura fixa, se comportando de acordo com o restante da página. Encontrei em alguns blogs diversas dicas de como fazer isso. Alguns só funcionavam no Firefox, outros só no Internet Explorer, nenhum em ambos.

Com essas informações então criei as funções getWidth e getHeight que retornam a largura e altura de um div, mesmo que está seja fluída:

function getHeight(d) {
if( document.all ) {
return eval( "document.all." + d.id + ".offsetHeight" );
} else {
return parseInt( getStyle(d,"height") );
}
}

function getWidth(d) {
if( document.all ) {
return eval( "document.all." + d.id + ".offsetWidth" );
} else {
return parseInt( getStyle(d,"width") );
}
}

function getStyle(el, style) {
if(!document.getElementById) return;
if( window.getComputedStyle ) {
value = document.defaultView.getComputedStyle(el, "").getPropertyValue(style);
} else if(document.defaultView) {
value = document.defaultView.getComputedStyle(el, "").getPropertyValue(style);
} else if(el.currentStyle) {
value = el.currentStyle[style];
}
return value;
}


Para usá-las é só chamar enviando por parâmetro um objeto div. Veja o exemplo:

var d = document.getElementById("meuDiv");
alert( getWidth( d ) );
alert( getHeight( d ) );


Testei essas funções com o Firefox 2, IE 7 e o Safari 3.

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.

segunda-feira, 3 de setembro de 2007

Script bash para encontrar classes em um diretório de jars

Scripts bash é uma das ferramentas mais uteis para desenvolvedores. Hoje por exemplo, para enfrentar um problema de classe duplicada no classpath da aplicação, fiz um rápido script para identificar todas as classes que seguem determinado padrão em um determinado diretório de jars. No caso, no WEB-INF/lib. Segue o script:
for i in $( ls | grep jar )
do
echo "$i:"
jar -tvf $i | grep "javascript.Context"
done;
No caso, estava procurando pela classe Context do Rhino, que logo descobri estava mesmo duplicada. Não no WEB-INF/lib da minha aplicação, mas dentro do weblogic.jar. Sim, por incrível que pareça o weblogic empacotou o Rhino inteiro em seu jar, com caminho de pacote e tudo, e ainda por cima numa versão bem antiga. Cada dia que passa mais odeio o weblogic.

domingo, 2 de setembro de 2007

JspException: Non-matching extension tags

Essa semana obtive a referida mensagem de erro ao colocar uma aplicação testada no tomcat para rodar no weblogic. A mensagem era mais ou menos a seguinte:
weblogic.servlet.jsp.JspException: (line 60): Non-matching extension tags //[ null; Line: 60]
Percebi que o erro ocorria na chamada a uma tag customizada de meu sistema que recebia em um atributo um parâmetro da requisição. O código era mais ou menos como o mostrado a seguir:
<minha:tagCustomizada nomeAtributo="<%=request.getParameter("valorFornecido")%>"/>
Ao pesquisar no google sobre o assunto, encontrei outras pessoas com o mesmo problema e uma sugestão para resolução dele. Trocar as aspas duplas por aspas simples no valor do atributo. Isso funcionou:
<minha:tagCustomizada nomeAtributo='<%=request.getParameter("valorFornecido")%>'/>
Parece até aceitável que o erro ocorra, pois pensando no padrão XML, a abertura do valor de atributos terminaria na próxima aspas dupla. Contudo, o JSP em questão não é um documento XML, de forma que teoricamente os scriptlets deveriam ser executados antes das tags customizadas. Tanto que no Tomcat não houve qualquer problema. Ao que tudo indica isso é um bug do Weblogic.

quarta-feira, 22 de agosto de 2007

Selenium IDE aplicado a interfaces com Ajax

Fazer testes automatizados com o Selenium IDE numa interface web que possui chamadas assíncronas pode se tornar um problemão. Um exemplo simples é de um link que buscaria através de HttpRequest um recurso no servidor:

<a href="javascript:void(buscarDados())">Clique aqui</a>
Neste caso para testar o clique no link usando o selenium temos dois problemas. Se usarmos a função click o teste seguinte poderá falhar caso o servidor demore alguns segundos para responder. E se usarmos a função clickAndWait ocorrerá um timeout pois o selenium espera que a página inteira mude.

Para resolver esse tipo de situação é que existem as funções iniciadas por wait. Elas aguardam um bom período até que determinado elemento ou texto sejam impressos na página. Dessa forma podemos utilizar a função click e posteriormente fazer aguardar por determinado recurso, o que indicaria que o servidor respondeu.

No caso do nosso exemplo a função de callback do ajax, ao receber os dados do servidor imprime uma tag H1 do html com o texto "Dados do servidor". Dessa forma o teste ficaria na seguinte ordem:

open/teste.html
clicklink=Clique aqui
waitForElementPresent//h1
verifyText//h1Dados do servidor

Repare que após o clique no link, utilizamos a função waitForElementPresent para aguardar que o html tenha inserido uma tag h1 em qualquer parte dele. Depois que este é impresso eu utilizo a função verifyText para verificar se há alguma tag h1 inserida no html que possua o texto "Dados do servidor".

A versão do Selenium IDE utilizada é 0.8.7 e a versão do Firefox 2.0.0.1

sexta-feira, 17 de agosto de 2007

O atributo length de objetos e arrays em javascript

Eu não resisti e usei Javascript essa ultima semana inteira - É a minha confissão de hoje na reunião de javascriptólatras anônimos - Passei um perrengue danado por causa de suas peculiaridades. Tudo causado por falta de atenção a uma das pequenas regrinhas que essa linguagem viciante tem. Para evitar que outros passem pelo mesmo problema, compartilho aqui minha dor.

Em primeiro lugar é preciso verificar que somente arrays têm o atributo length. Em segundo que arrays podem se comportar como objetos, e objetos podem se comportar como arrays, e que arrays são objetos. Entendeu? Bom, vamos aos exemplos pra ficar mais claro.
 var eu = { nome:"Tiago",idade:25 }; //Criando um objeto dinâmico com {}
alert( eu["nome"] ) //Acessando como se fosse um mapa
alert( eu.idade ) //Acessando como um atributo
eu.sexo = "Sempre"; //Criando um novo atributo
alert( eu["sexo"] );
eu["altura"] = 1.75; //Criando um novo atributo como mapa
alert( eu.altura );
No exemplo acima eu crio um objeto dinamicamente utilizando chaves além de acessar e criar novos atributos, de duas formas possíveis: Como se fosse um mapa, utilizando o operado colchetes, e da maneira mais comum, que é utilizando o operador ponto. Vamos seguir então para o próximo exemplo:
 var eu = []; //Criando um array com []
eu.nome = "Tiago"; //Setando atributo em um array? Que coisa estranha
eu["idade"] = 25 //Não era um array, por que está aceitando uma chave como se fosse um mapa?
alert( eu["nome"] ) //Acessando como se fosse um mapa
alert( eu.idade ) //Acessando como um atributo
eu.sexo = "Sempre"; //Criando um novo atributo
alert( eu["sexo"] );
eu["altura"] = 1.75; //Criando um novo atributo como mapa
alert( eu.altura );
Neste exemplo criamos um array ao invés de um objeto, e qual não é nossa surpresa ao perceber que, exceto pela incialização, o comportamento dele fica exatamente igual ao de um objeto. Isso ocorre porque um array é um objeto. Até aí tudo parece maravilhoso. A coisa desanta quando você resolve usar o comportamento de array, num array tratado como objeto. Vejamos:
 var eu = []; 
alert(eu.length); //Tamanho 0 oras, não coloquei nada aqui ainda
eu["nome"] = "Tiago"; //Inserindo um atributo
alert(eu.length); //Tamanho 0?? E o atributo que botei?
eu.idade = 25; //Inserindo outro atributo
alert(eu.length); //Tamanho 0 de novo????
eu[0] = "primeiro indice"; //Adicionando elementos ao array
eu[1] = "segundo indice";
alert(eu.length); //Ahhh agora sim
eu.push("terceiro indice");
eu.push("quarto indice");
alert(eu.length); //Ahhh agora sim de novo
No exemplo acima podemos perceber o quão confuso começa a ficar. Basicamente o atributo length passa a só funcionar quando inserimos elementos no array. Ou seja, os atributos inseridos no array ficam fora do array. Isso fica mais evidente fazendo as iterações como as do exemplo abaixo:
 //agora vamos iterar só pelos elementos do array
for(i=0; i {
alert("elemento do array: " + eu[i]);
}
//agora vamos iterar sobre tudo que tiver aí
for(i in eu)
{
alert("atributo do objeto: " + eu[i]);
}
//De novo somente elementos do array
while(eu.length>0)
{
alert("Remove elemento: " + eu.pop());
}
Na primeira eu itero sobre números, logo somente sobre os elementos do array. No segundo eu itero sobre as chaves, sejam elas numéricas, sejam de texto. E finalmente no terceiro eu itero removendo os elementos do array. Mas ainda sim podem ocorrer algumas curiosidades que podem nos confundir, veja abaixo:
 var eu = []; 
eu[0] = "primeiro indice";
eu[1] = "segundo indice";
eu["1"] = "substitui segundo indice"; //Isso aqui é o mesmo que eu[1]
alert(eu[1]); //Vai alertar a mesma coisa que o alerta de baixo
alert(eu["1"]);
eu["length"] = 0; //Vou sacanear o array agora
alert(eu.length); //Zerei o tamanho
alert(eu[4]); //Ih!!! Apagou tudo
Perceba que adicionar um elemento ao indice 1 como número é o mesmo que adicionar ao 1 como string. Além disso, veja que podemos alterar o valor do tamanho, atributo length, do array somente atribuindo um valor a ele. Isso pode causar grandes problemas, ainda mais se você estiver guardando dados digitados pelo usuário como se o array fosse um mapa.

Um exemplo simples é um usuário preenchendo um formulário com campo de nome e valor, ao clicar em um botão esses dados são inseridos no array como se fosse um mapa. Digamos que ele preencha da seguinte maneira:
Nome     | Valor: 
---------------------
"país" | "brasil"
10 | "isso"
7 | "hoje"
"olha" | "caso"
"length" | 0
Neste caso quando ele digitar o nome 10, o array passará a ter 10 posições, mas no momento que ele digitar "length" com o valor 0, os dados que ele inseriu com nome 10 e 7 desaparecerão. Para debugar isso você terá que arrancar vários tufos de seu cabelo.

E é aí finalmente que chegamos ao ponto crucial. Para evitar esse tipo de problema o ideal é deixar bem separado o que é array, inicializando com [] e o que é objeto inicializando com {}. Isso porque para objetos, um atributo com nome length não tem poder algum, exceto se você implementá-lo para que tenha.

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:

quinta-feira, 31 de maio de 2007

Gerar imagem PNG com o GnuPlot

Ao instalar o GnuPlot no servidor, verifiquei que não conseguia gerar graficos no formato png com este programa. O erro que eu obtinha é o seguinte:
gnuplot> set terminal png
^
unknown or ambiguous terminal type; type just 'set terminal' for a list

Observando o log da configuração, config.log, que é gerado assim que o comando ./configure é executado descobri qual o problema, faltava-me a libgd. Veja a linha do log mostrando o problema:
configure:16121: result:   png terminal: no (requires libgd with png support)

Sabendo disso, desinstalei o gnuplot, instalei a libgd e todas suas dependencias para finalmente reinstalar o gnuplot. Com isso, tudo funcionou perfeitamente.

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.

quinta-feira, 10 de maio de 2007

Como utilizar o ForEach Controller do JMeter

A estrutura de repetição, ForEach Controller do JMeter exige que existam váriaveis definidas com um padrão contendo um prefixo seguido de um número sequencial (Exemplo: x1, x2, x3, ou , n_1, n_2, n_3). Para definir um grande número de váriaveis sem grande esforço, pode-se utilizar um CSV Data Set Config que define váriaveis apartir de um arquivo texto.

No campo "Filename" do CSV Data Set Config indica-se o path de um arquivo de texto contendo os valores separados por vírgula (Exemplo: 10,56,345,12). A seguir no campo "Variable names" define-se um nome de váriavel para cada valor definido no arquivo texto. Assim, se no arquivo texto tiver cinco valores, devem ser definidos cinco nomes de váriaveis (Exemplo: n_1,n_2,n_3,n_4,n_5).

No ForEach Controller deve-se indicar o prefixo da váriavel em "Input variable prefix". Se o padrão de váriaveis suas possuir um _ antes do número, pode-se marcar a opção "Add _ before number". O campo "Output variable name" recebe o valor da variável que receberá o valor de uma das variáveis a cada iteração. Ou seja, a primeira vez que executar o valor será de n_1, na segunda vez será de n_2 e assim por diante.

Assim, os itens que estiverem dentro do ForEach Controller passam a poder utilizar a váriavel definida em "Output variable name" utilizando um padrão parecido com de Expression Language do JSP 2.0: ${nomeVariavel}.

quarta-feira, 9 de maio de 2007

Número de threads no Tomcat

Para aumentar o número de threads que o seu Tomcat utiliza para atender requisições é só alterar o server.xml, localizado em TOMCAT_HOME/conf/server.xml. É necessário informar alguns novos atributos (ou alterar) na tag Connector que é utilizada para receber as requisições, ou seja aquela que possui o atributo "port" com valor igual a porta http. Veja abaixo:
<Connector 
className="org.apache.coyote.tomcat4.CoyoteConnector" port="80"
...
maxThreads="150"
minSpareThreads="25"
maxSpareThreads="75">
...
</Connector>

O atributo "maxThreads" indica o máximo de threads que o tomcat irá abrir, o "minSpareThread" e "maxSpareThreads" são respectivamente o mínimo e o máximo de Threads que o Tomcat disponibilizará para servirem de steps.

Essa configuração funcionou para a versão 4.1.31 do Tomcat, mas creio não deve ter mudado para novas versões.

segunda-feira, 7 de maio de 2007

Busca com OpenSearch no Firefox e IE7

Recentemente fiz texto explicando como disponibilizar a busca de seu site no Firefox. Semana retrasada aqui no trabalho utilizamos uma nova forma de disponibilizar a nossa busca nos navegadores dos usuário. Dessa vez, funciona tanto para Internet Explorer 7, quanto para o Firefox.

Isso foi feito utilizando o padrão OpenSearch. E é bem similar ao padrão do artigo anterior, veja abaixo o código do XML que é preciso disponibilizar:
<?xml version="1.0" encoding="UTF-8"?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
<ShortName>Programando sem cafeina</ShortName>
<Description>Blog sobre tecnologia principalmente para a area de desenvolvimento de sistemas</Description>
<Url type="text/html" template="http://programandosemcafeina.blogspot.com/search/label/{searchTerms}"/>
<LongName>Programando sem cafeina - Blog sobre tecnologia principalmente para a area de desenvolvimento de sistemas</LongName>
<Image height="64" width="64" type="image/png">http://programandosemcafeina.blogspot.com/imagems/busca-openserach.png</Image>
<Image height="16" width="16" type="image/png">http://programandosemcafeina.blogspot.com/imagems/busca-openserach.png</Image>
<Language>pt-br</Language>
<OutputEncoding>UTF-8</OutputEncoding>
<InputEncoding>UTF-8</InputEncoding>
</OpenSearchDescription>

Esse XML tem apenas as opções básicas, para saber o restante das opções visite a página: Descrição do documento OpenSearch.

Continuando então, para disponibilizar a busca não basta apenas colocar o XML lá no seu servidor, é preciso oferecer um link com um código javascript que adicione a busca na barra de ferramentas do navegador do usuário. Veja o exemplo abaixo de como fazer isso:
<a href="javascript:void(window.external.AddSearchProvider('http://programandosemcafeina.blogspot.com/minhabusca.xml'));">
Adicionar busca do meu blog
</a>

Viu como é fácil? Quem quizer testar para ver como ficou a busca para o Globo Vídeos basta adicioná-la clicando em Adicionar busca do Globo Vídeos em seu navegador. É bom lembrar que em navegadores muito antigos isso não funciona. No Internet Explorer 6 mesmo não funciona.

quinta-feira, 3 de maio de 2007

Problemas com Post do XMLHttpRequest

Recentemente tive problemas ao tentar utilizar chamadas assíncronas no javascript utilizando o método post do protocolo http. Na verdade, o que ocorria é que os parâmetros enviados por post não eram recebidos em meu servlet. Descobri então como contornar isso, mas não entendi o porque desse problema ter ocorrido.

No javascript eu fazia a seguinte chamada, onde a variavel r era um objeto XMLHttpRequest:
r.open("POST", "http://localhost/MeuServlet", true);
r.send("nome=tiago");

No servlet MeuServlet eu apenas pegava o parâmetro nome e o imprimia na saída padrão:
String nome = request.getParameter("nome");
System.out.println("nome enviado = " + nome);

A saída era sempre "nome enviado = null". Olhando no debugger de chamadas assíncronas do firebug dava pra ver que os parâmetros estavam sendo enviados com sucesso. O problemas estava mesmo na recepção. Eis então que achei um exemplo na internet com o que faltava para meu código javascript:
r.open("POST", "http://localhost/MeuServlet", true);
r.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;");
r.send("nome=tiago");

Colocando essa linha indicando o Content-Type no header da requisição a chamada por post passou a funcionar. Se alguém tiver uma explicação do porque disso será de grande ajuda.

quarta-feira, 2 de maio de 2007

Rolar um DIV via javascript

Segue um exemplo de como rolar um div via javascript:
function praBaixo()
{
var c = document.getElementById("meuId");
c.scrollTop = c.scrollHeight;
}

No exemplo, a função "praBaixo" colocaria a barra de rolagem de um div para baixo de forma a mostrar o ultimo conteudo do div. É bom lembrar que para um div ter barra de rolagem é preciso indicar seu tamanho, width para rolagem horizontal e height para rolagem vertical, e a propriedade "overflow: auto" no css.

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.

segunda-feira, 23 de abril de 2007

Resumo do Sun Tech Days de 2007

Segue o resumão de todos os dias do Sun Tech Days de 2007. As fotos e vídeos irei postar durante a semana.

Resumo do Sun Tech Days do dia 19/04/2007
Resumo do Sun Tech Days do dia 20/04/2007

Resumo do Sun Tech Days do dia 20/04/2007

Aqui estão as minhas impressões e o que aprendi no segundo dia do Sun Tech Days de São Paulo:
Demorei um pouco pra postar mais foi :)

WEB 2.0 com Ajax no Sun Tech Days

Essa palestra não trouxe muitas informações novas, era mais para iniciantes na área de ajax, explicando como funcionam as chamadas assincronas utilizando o objeto XMLHttpRequest e dando alguns exemplos bem manjados. Uma coisa apresentada que eu não conhecia é o JMaki Widget. Com essa ferramento no NetBeans, rapidamente a palestrante fez uma página com um menu de imagens de cidades, utilizando aquele efeito que o menu do OS-X tem, que ao clicar em uma delas era exibida a localização da cidade, utilizando Yahoo Maps. Só com Widgets. É uma boa dar uma pesquisada nessa ferramenta.

Aplicações BPEL e SOA com Oracle no Sun Tech Days

Com essa palestra finalmente aprendi o que é BPEL e para que serve. Relutei muito em ler sobre o assunto, mas não teve jeito :) Segundo a palestra utilizamos o BPEL quando precisamos chamar vários webservices em sequências.

Achei muito interessante, mas a idéia de programar via XML não me agrada. Estruturas condicionais e loops num XML são coisas que nunca me agradarão. Contudo, o fato de ser XML torna a customização de um workflow mais fácil de ser manejado por um programa, como o da Oracle que foi exibido na palestra.

Algumas tags de atividades que foram mostradas são <receive> que recebe a requisição de um cliente, o <reply> que retorna a requisição para o cliente e o <invoke> que chama um webservice. E algumas tags de estrutura que foram mostradas são <sequence> que faz com que as atividades em seu interior sejam executadas em sequencia, <flow> que faz com que as atividades em seu interior sejam executadas em paralelo e <switch> que tem a mesma responsabilidade de um switch programático.

Outra coisa interessante mostrada nessa palestra foi como é formado um arquivo WSDL. Primeiro os tipos de dados, depois as mensagens, os ports referenciando as mensagens, os bindings e finalmente o service.

Linguagens de script no Java no Sun Tech Days

Palestra muito interessante mostrando a capacidade do Java 6 de executar linguagens de script. Basicamente existem duas formas de se executar uma liguagem de script no Java, por dentro de uma classe ou utilizando o executavel jrunscript.

Dentro de uma classe é preciso pegar o ScriptEngine utilizando o ScriptEngineManager. O ScriptEngine possui um método eval(String ou Reader) que executa um script apartir de uma String ou apartir de um arquivo utilizando um Reader. Para invocar funções utiliza-se o método invokeFunction(String), e nessa chamada pode-se indicar os parâmetros da função pois é um método que utiliza varargs.

Outra coisa legal é o método put que serve para colocar objetos Java dentro do script. As alterações ocorridas neste objeto dentro do script são refletidas fora do script. Também foi mostrado um exemplo de javascript importanto classes Java, com a função importPackage.

Muitos se perguntam se esse suporte a linguagens de script é mesmo necessário e o palestrante deu uma boa idéia do porque é sim importante. Primeiro porque a idéia é que o Java se separe de sua VM, de forma que esta VM possa rodar não só em diversas plataformas como também diversas linguagens. Segundo porque poder executar script apartir de classes Java é muito bom para customização de sistemas, principalmente na área financeira.

Sobre JSF no Sun Tech Days

Nessa palestra falou-se sobre as qualidades e defeitos do JSF, motivos para utilizá-lo, e sobre o futuro deste framework. Ao final foram feitos alguns rápidos exemplos utilizando o Visual Web Pack em conjunto com o Sun Java Creator.

Interessante foi saber que no futuro o JSTL e o JSF estarão no mesmo pacote. Ou seja, a Sun está fazendo de tudo para que o JSF enfim ganhe mercado. Hoje o JSTL é utilizado em conjunto com outros frameworks, mas se o JSF vier junto com o JSTL, será estranho utilizar outro que não o JSF.

Outra ponto citado que eu já havia lido a respeito é que com o JSF é possivel que uma mesma página gere resultado em vários formatos, não só HTML. Basta utilizar algum RenderKit diferente do HTMLRenderKit. Pena que não foi mostrado nenhum exemplo, esse é mais um assunto que merece que eu faça uma boa pesquisa.

Ao final a palestrante mostrou dois exemplos, um parecido com o da palestra do ADF da Oracle, com a diferença de que o Sun Java Creator com o Visual Web Pack funcionou muito melhor que o JDeveloper em termos de desempenho (é bom lembrar que o JDeveloper exibido era uma versão pré-alfa).

O outro era uma integração com o PayPal que gerou um erro na aplicação. Quando isso ocorreu a palestrante ficou tão constrangida que tivemos dois minutos de silêncio absoluto. Felizmente o erro foi corrigido e ela enfim conseguiu exibir o exemplo pra nós.

sexta-feira, 20 de abril de 2007

Ferramentas Oracle no Sun Tech Days

Nessa palestra falou-se sobre algumas ferramentas da Oracle para desenvolvimento web como o ADF, framework da Oracle baseado em JSF e EJB3. O exemplo que o palestrante fez com o JDeveloper ficou muito bonito, um grid utilizando um componente JSF deste IDE, que ao ser renderizado é exibido com opções de redimensionar colunas e ordenar por elas. Mão na roda não ter que procurar e adaptar esses scripts. Quem já fez um grid desses, cheios de firulas web 2.0, sabe a chatice que é. Contudo, a apresentação deixou uma má impressão, pois o JDeveloper demorou muito para carregar. Sem graça o palestrante explicou que isso ocorria porque era versão pré-alfa. Amador?

Abertura do segundo dia do Sun Tech Days

Novamente escrevo sem acentos pois no connection point do Sun Tech Days nao tem como utilizar caracteres especiais.

A abertura desse segundo dia foi muito macante. O palestrante parecia mais um pastor de igreja evangelica do que alguem da area de tecnologia. Objetividade zero. Foi mais de meia hora repetindo o mesmo refrao de comunidade. Esse tipo de discurso serve mais para pessoas da area de humanas.

Depois tivemos uma palestra sobre algumas ferramentas da Oracle, que descreverei mais tarde.

O bom mesmo, foi ao final da abertura, que teve um divertido concurso habilidades no palco. Desenvolvedores e arquitetos subiam para cantar, dancar e especialmente pagar mico. Destaque para os vencedores do concurso, uma menina que veio preparadissima para dancar danca do ventre e um rapaz, mais troncho nunca vi, que tambem dancou danca do ventre, sem camisa, com as banhas balancando. Eu ri muito.

Essas palhacadas eu filmei. Logo logo colocarei os videos no Youtube e entao eu posto aqui no Programando sem cafeina.

Resumo do Sun Tech Days do dia 19/04/2007

Aqui estão as minhas impressões e o que aprendi no primeiro dia do Sun Tech Days de São Paulo:

Agora vou dormir porque amanhã tem mais, se eu conseguir vaga no connection point eu posto mais durante o dia.

quinta-feira, 19 de abril de 2007

EJB 3 e JPA no Sun Tech Days

A palestrante explicou como funciona o mapeamento objeto-relacional do JPA por anotações de relacoes ManyToOne e ManyToMany, e como mapear herança utilizando uma só tabela. Para diferenciar se um objeto é de uma subclasse ou de outra utiliza-se a anotação @DiscriminatorColumnName e @DiscriminatorValue. Essa foi a melhor palestra do dia pois explicou do ínicio ao fim o mapeamento, como funciona o EntityUnit, como funciona o EntityManager, mostrou o ciclo de vida de uma entidade e deu ótimas dicas para não termos problemas no desenvolvimento utilizando JPA. Mais tarde quando eu baixar os power points repassarei essas dicas. Agora eu vou aproveitar o cocktail :)

Palestra de JAX-WS e WSIT no Sun Tech Days

Não consegui entender muito sobre esta palestra porque o palestrante falava rápido demais e a tradução simultânea, como mencionei antes, é um desastre, e além disso, nessa palestra a tradução estava especialmente ruim.

O que achei interessante, mas não entendi como funciona é o método de webservice assíncrono, assim que puder pesquisarei sobre o assunto. O palestrante fez também um WebService simples utilizando o JavaSE 6, com a anotacao @WebService e gerando os stubs com o comando wsimport. É incrivel como WebService fica mais facil com o JavaSE 6.

VMWare no Sun Tech Days

Nao gostei muito da palestra sobre Virtual Desktop da VMWare, isso porque infra-estrutura nao eh a minha praia, e as coisas que aprendi nao me serao uteis no trabalho, apenas como curiosidade. Gostei da ideia de economia de energia que a utilizacao de maquinas virtuais pode proporcionar. E a ideia de Remotion, no qual um servidor virtual pode ser transferido de um servidor fisico para outro. Isso eh muito util. Uma sugestao que o palestrante deu eh utilizar isso para transferir ambientes totalmente testados para producao, sem nova instalacao.

Palestra sobre JavaSE 6 no Sun Tech Days

Tivemos uma palestra mostrando as novas funcionalidades do JavaSE 6. O que foi mais interessante foram as alterações no Swing e no AWT. Dentre essas novas funções temos a nova classe TrayIcon que permite colocar um programa na bandeja do Sistema Operacional (Ali, ao lado do relógio), uma forma exibir uma tela de splash antes mesmo de carregar a JVM, e a classe Desktop que permite abrir sistemas nativos.

Outra nova funcionalidade que foi citada e que parece muito boa é a SwingWorker, que serviria para facilitar o trabalho multi-thread em aplicações Swing, pena que não mostraram nem mesmo um exemplo dela. Qualquer dia desses eu dou uma pesquisada sobre ela para colocar aqui.

No JavaSE6 também foram introduzidos novos executáveis para monitoração da aplicação, como é o caso do jconsole. Outro destaque é pela nova forma de criação de WebServices que dispensa a criacao do WSDL, bastando anotar a classe que irá prover o servico com @WebService. O proprio JavaSE levantaria um mini-servidor http para oferecer este servico, bastanto publicar o endpoint com Endpoint.publish.

Segundo o palestrante a migracao de JavaSE 5 para JavaSE 6 nao é custo algum pois nao há alteracao de sintaxe. Portanto é extremamente recomendável a migração devido às melhorias de performance.

Demos do Sun Tech Days

Foram exibidas duas demonstracões de desenvolvimento rápido com o NetBeans. Nao sou fã dessa IDE, mas tenho que tirar o chapéu, foram ótimas demonstrações. Primeiro fez-se um menu SVG para JavaME e depois uma demonstração de uma aplicacao com o Matise. Ambos mostraram como é rápido o desenvolvimento no NetBeans. Acredito que no dia em que a Sun investir no aperfeiçoamento do editor desta IDE, o NetBeans finalmente poderá barrar o Eclipse. Enquanto isso continuo fiel ao programa que esconde o sol.

Outra coisa legal foi a exibição do SunSpot, uma espécie de luva que faz com que tenhamos um efeito similar ao do filme Minority Report misturado com o LookInGlass, o destop 3D feito em Java. Não funcionou perfeitamente, teve um momento em que a menina que apresentava teve que repetir o movimento umas duas vezes para girar umas janelas, mesmo assim foi lindo de se ver. Sinto que o futuro dos desktops será emocionante.

Abertura do Sun Tech Days

Estou aqui no Sun Tech Days em São Paulo, os próximos textos que eu postar aqui não terão acento. Estou numa máquina do connecting point que aparentemente não possui suporte a caracteres especiais (Atualizado: Agora no hotel estou corrigindo os acentos). Já adiantando, a coisa mais engraçada do evento é o serviço de tradução simultânea. Os tradutores não tem nenhuma familiaridade com os termos de informática, soltando perólas engraçadíssimas.

Logo na primeira palestra tivemos uma bela parábola sobre ciclo de vida de software. Uma comparação mostrando que assim como é mais produtivo para os cientistas fazerem esperiências em animais menores, como ratos e insetos, do que com animais maiores, como um elefante por exemplo, também é melhor para programadores trabalharem no desenvolvimento de softwares com ciclos de vidas mais rápidos e de contínuo feedback.

Falou-se muito sobre a separação entre VM e a linguagem Java, mostrando qual o objetivo da Sun no suporte a linguagens de script. O que ocorre hoje é que essas linguagens possuem cada uma a sua própria VM. A idéia da Sun é que no futuro todas rodem em uma mesma VM, tornado-as portáveis para diversas plataformas. Inicialmente isso se dará com uma camada de biblioteca java, mas no futuro a idéia é tirar essa camada. O Java 6 já possui suporte a javascript.

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.

quarta-feira, 11 de abril de 2007

Pra que serve e como funciona o ServletContextAttributeListener?

O ServletContextAttributeListener é um listener que recebe sinais quando um atributo é adicionado, removido ou alterado no contexto. Para quem não sabe, um atributo de contexto é válido para todos os servlets, threads e sessões de uma aplicação. Para testar seu funcionamento criei uma classe implementando a interface ServletContextAttributeListener e um servlet para adicionar, remover e alterar atributos do contexto.

Segue o código do listener:
package meupacote.listeners;

import javax.servlet.ServletContextAttributeListener;
import javax.servlet.ServletContextAttributeEvent;

public class MeuServletContextAttributeListener implements ServletContextAttributeListener
{
public void attributeAdded(ServletContextAttributeEvent e)
{
System.out.println("Atributo adicionado: nome=" +
e.getName() + ", valor=" + e.getValue()
);
}

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

public void attributeReplaced(ServletContextAttributeEvent e)
{
System.out.println("Atributo alterado: nome=" +
e.getName() + ", valorAntigo=" + e.getValue()
);
}
}
Veja que para implementar esse listener é preciso sobrescrever os métodos attributeAdded, que é chamado quando um atributo é adicionado, attributeRemoved, que é chamado quando um atributo é removido e attributeReplaced que é chamado quando um atributo é alterado.

O evento que esses métodos recebem possue dois métodos: getName e getValue, eles servem para pegar o nome e o valor do atributo manejado na ação. É preciso observar que no attributeReplaced, o getValue do evento retorna o valor anterior ao que foi alterado.

Agora veja o código do servlet que inclui, altera e remove atributos do contexto:
public class MudaAtributoContexto extends HttpServlet
{
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
{
String acao = request.getParameter("acao");
String nome = request.getParameter("nome");
String valor = request.getParameter("valor");

response.setContentType("text/html");
PrintWriter out = response.getWriter();

if( nome!=null )
{
if( "remover".equals(acao) )
{
getServletContext().removeAttribute(nome);
out.print("Removido: " + nome);
}
else
{
getServletContext().setAttribute(nome, valor);
out.print("Setado: " + nome + " = " + valor);
}
}

StringBuilder s = new StringBuilder("<form><b>Incluir: </b>")
.append(" nome: <input name=\"nome\"/>")
.append(" valor: <input name=\"valor\"/>")
.append(" <input type=\"submit\"/>")
.append("</form><hr />");

Enumeration e = getServletContext().getAttributeNames();
while( e.hasMoreElements() )
{
String n = (String)e.nextElement();
Object v = getServletContext().getAttribute(n);

s.append("<form>")
.append(" nome: <input name=\"nome\" value=\"" + n + "\">")
.append(" valor: <input name=\"valor\" value=\"" + v + "\">")
.append(" Remover? <input name=\"acao\" value=\"remover\" type=\"checkbox\">")
.append(" <input value=\"Alterar\" type=\"submit\">")
.append("</form>");
}

out.print(s);
}
}

Depois de registrado o servlet e o listener no web.xml é só testar. Se por exemplo adicionarmos um atributo de nome pais e valor igual a Brasil, depois alterarmos esse mesmo atributo para Guatemala e ao final removê-lo. Teremos as seguintes linhas escritas no output:

Atributo adicionado: nome=pais, valor=Brasil
Atributo alterado: nome=pais, valorAntigo=Brasil
Atributo removido: nome=pais, valor=Guatemala

Pra que serve e como funciona o ServletContextListener?

A interface ServletContextListener é um listener utilizado para verificar quando uma aplicação é iniciada e finalizada. A classe que implementa essa interface deve sobrescrever os métodos contextInitialized e contextDestroyed que serão chamado assim que a aplicação é iniciada e finalizada respectivamente.

Veja um exemplo de classe implementando este listener:
package meupacote.listeners;

import javax.servlet.ServletContextListener;
import javax.servlet.ServletContextEvent;

public class MeuServletContextListener implements ServletContextListener
{
public void contextInitialized(ServletContextEvent e)
{
System.out.println("MeuServletContextListener - iniciado: " +
e.getServletContext().getServerInfo()
);
}

public void contextDestroyed(ServletContextEvent e)
{
System.out.println("MeuServletContextListener - finalizado");
}
}

Assim que registrármos esse listener no web.xml da aplicação, sempre que o contexto for recriado o método contextInitialized será chamado, escrevendo no output "MeuServletContextListener - iniciado: " seguido das informações do container utilizado. Como eu utilizo o tomcat 5, é apresentada a seguinte: "Apache Tomcat/5.5.17". Já o método contextDestroyed será chamado sempre que a aplicação for finalizada.

terça-feira, 10 de abril de 2007

Verificando o funcionamento de SingleThreadModel

SingleThreadModel é do mau. Não é recomendável que se use de maneira alguma, é até uma interface marcada como deprecated. Mas mesmo assim, por algum motivo obscuro ela cai na prova do SCWCD. Para verificar o seu funcionamento criei dois servlets, um sem implementar a famigerada interface e o outro implementando.

Servlet não implementando SingleThreadModel:
public class TesteSemSingleThreadModel extends HttpServlet
{
public static int instancias = 0;

public void init()
{
instancias++;
}

public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
response.setContentType("text/html");
response.getWriter().print("Instancias: " + instancias);
}
}

Servlet implementando SingleThreadModel:
public class TesteComSingleThreadModel extends HttpServlet implements SingleThreadModel
{
public static int instancias = 0;

public void init()
{
instancias++;
}

public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
response.setContentType("text/html");
response.getWriter().print("Instancias: " + instancias);
}
}

Como resultado pude perceber que o primeiro servlet, que não implementa a interface discutida, quando chamado exibe o texto "Instancias: 1" indicando que o método init() foi chamado apenas uma vez. Já o segundo servlet que implementa a SingleThreadModel, quando chamado exibe o texto "Instancias: 2". Ou seja, duas instancias do servlet foram criadas, uma para cada thread do Tomcat, container que estou utilizando.

Extensões do Firefox para desenvolvedores web

Compartilho aqui com vocês os pluggins, agora chamados de addons, que utilizo no meu Firefox 2. São extensions para auxiliar a depuração de sistemas e que não podem faltar a um desenvolvedor web.
  • Firebug - Melhor pluggin para debugar javascript: Exibe exatamente quando e onde um erro ocorre. Rastrea as chamadas ajax e permite analise do html e css da página.
  • GreaseMonkey - Permite definir códigos javascript sempre que determinadas páginas forem abertas. Muito útil para exibir funcionalidades beta em um determinado site somente para os usuários que você escolher, ou para reconfigurar páginas que você costuma visitar.
  • WebDeveloper - Diversas ferramentas para desenvolvedores web. A que mais utilizo é a manutenção de cache e de cookies. Mas também é interessante a de visualizador de campos de formulários.
  • JSView - Cria um atalho para exibir todos os arquivos js e css que uma página carrega, inclusive aqueles lidos dinamicamente. Depois de instalar ele é preciso habilitar a opção de exibir seu icone na barra de status do firefox.
  • Live HTTP Headers - Exibe todas as requisições HTTP que o Firefox faz. Permite filtrar para exibir arquivos de imagem, flash, css entre outros ou não;
  • Aardvark - Permite analisar o código html gerado na página, inclusive o que foi criado apartir de javascript. Basta apertar com botão direito, e no menu selecionar "Start Aardvark", que os elementos html serão destacados. Apertando a tecla h verifica-se todas as opções do addon.
  • IE Tab - Veja paginas no Firefox como se estivesse no Internet Explorer. Ideal para pessoas como eu que preferem testar em outra aba da raposa ao invés de abrir uma janela do IE.
  • Resizeable Form Fields - Faz com que alguns campos de formulários possam ser redimensionados.
  • del.icio.us - Cria dois botõezinhos do Delicious, um para acessar diretamente seus favoritos e outro para adicionar a página que você está visitando.
  • XPather - Adiciona um item no menu do botão direito que indica o XPath para acessar determinado elemento no html.
Alguns desses pluggins possuem macetes para funcionar. Não postei todos eles aqui por falta de tempo. Portanto se você tiver algum problema comente aqui o problema que, se caso eu tenha tido o mesmo, eu respondo com a solução.

segunda-feira, 9 de abril de 2007

No forward QueryString pode ser capturada de maneira diferente

Quando um servlet chama repassa a requisição para outro, utilizando o método forward do RequestDispatcher, este outro servlet pode capturar a QueryString do primeiro tanto utilizando o método getQueryString() de HttpServletRequest como utilizando o attributo "javax.servlet.forward.query_string" da requisição.

Segue o servlet que repassa a requisição para o próximo
public class MeuServlet1 extends HttpServlet
{
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
request.getRequestDispatcher("/MeuServlet2").forward(request,response);
}
}

Agora o servlet que recebe a requisição:
public class MeuServlet2 extends HttpServlet
{
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
response.setContentType("text/html");
PrintWriter p = response.getWriter();
p.print("Query String1: " + request.getQueryString() );
p.print("Query String2: " + request.getAttribute("javax.servlet.forward.query_string") );
}
}

Informação inutil para quem está desenvolvendo, mas importante para quem assim como eu estuda para tirar a SCWCD.

A busca de seu site no firefox

ATENÇÃO: O seguinte artigo mostra como adicionar uma busca somente no Firefox. Para ver como adicionar em diversos navegadores, como o próprio Firefox e o Internet Explorer, utilizando o padrão OpenSearch abra o artigo Busca com OpenSearch no Firefox e IE7




Quer disponiblizar a busca de seu site ou blog direto no Firefox? Isso é fácil fácil, basta primeiro criar um arquivo src contendo um xml nos moldes do seguinte exemplo:
<search 
name="Titulo do site"
description="Descricao do site"
action="http://endereco.do.site/busca"
searchform="http://endereco.do.site/busca"
method="GET"
version="1.0">
<input name="q" user="">
</search>
<browser
update="http://endereco.do.site/busca-firefox.src"
updateicon="http://endereco.do.site/busca-firefox.png"
updatecheckdays="7">
</browser>


Calma calma, parece um descritor complicado mas não é. Primeiro na tag search devemos preencher o titulo do site e depois a descrição. O atributo action indica o executor da busca: um servlet, cgi, o que for, que receberá um determinado parâmetro de busca. O nome desse parâmetro é indicado na tag input.

A tag browser indica quando esses descritor deve ser atualizado e onde deve ser buscado. Veja que tem o endereço de um icone, atributo updateicon. É importante que o tamanho desse icone seja 16x16, caso contrário fica esquisitão.

Depois de criado seu e disponibilizado o descritor, basta colocar o link para que os usuários adicionem sua busca no firefox. Segue o exemplo do javascript que faz isso:
<a href="javascript:window.sidebar.addSearchEngine('http://endereco.do.site/busca-firefox.src','http://endereco.do.site/busca-firefox.png','Busca no meu site','png');">
Adiciona busca do meu site
</a>


Recentemente disponibilizamos aqui na globo.com a busca do nosso portal de vídeos, o Globo Vídeos, para ser colocado no firefox. Veja como fica:

Adiciona busca do Globo Vídeos

quinta-feira, 5 de abril de 2007

Windows Media Player embed só com a barra de status

No post ClassID do Windows Media Player embed eu descrevi um problema que estava tendo para exibir um player embed no internet explorer com somente a barra de status. Utilizando o classid 6BF52A52-394A-11d3-B153-00C04F79FAA6 isto se tornava impossivel.

Eis então que tive uma idéia simples, que poderia ser considerada uma técnica POG. A solução encontrada foi redimensionar o player para o tamanho dos controles somado ao tamanho da barra de status, e encapsular isso num div com overflow hidden, do tamanho da barra de status. Veja o código:

<div style="overflow: hidden; width: 300px; height: 19px;">
<object classid="CLSID:6BF52A52-394A-11d3-B153-00C04F79FAA6"
id="MeuPlayer" name="MeuPlayer"
height="64" width="300"
type="application/x-mplayer2"
>
<param name="uiMode" value="full"/>
<param name="URL" value="nao_se_afobe_nao.wma"/>
</object>
</div>



Isso pode ser considerado uma técnica POG porque não é garantido que em versões futuras do Internet Explorer e do Windows Media Player o tamanho da barra de controles e de status seja 64 pixels, e que o tamanho da barra de status sozinha seja de 19 pixels.

quarta-feira, 4 de abril de 2007

IllegalStateException quando utilizado o forward de RequestDispatcher

Quando uma resposta já tiver sido enviada do servidor de aplicações para o cliente, o método forward da interface RequestDispatcher não pode ser utilizado, pois ele não funciona e gera um IllegalStateException.

Criei um código para testar em quais momentos a resposta é passada ao usuário para descobrir quando é que poderíamos obter essa exceção. Veja:
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
String param = request.getParameter("tipo");
if( param==null || param.length() == 0 )
{
response.getWriter().print("Informe o parametro tipo com os valores: ");
response.getWriter().print(" print, flush, stream, close, header ou cookie");
}
else
{
if( "print".equals(param) )
{
response.getWriter().print("alguma coisa");
}
else if( "flush".equals(param) )
{
response.getWriter().print("alguma coisa");
response.getWriter().flush();
}
else if( "stream".equals(param) )
{
ServletOutputStream output = response.getOutputStream();
}
else if( "close".equals(param) )
{
response.getWriter().print("alguma coisa");
response.getWriter().close();
}
else if( "header".equals(param) )
{
response.addHeader("Pragma","no-cache");
}
else if( "cookie".equals(param) )
{
Cookie c = new Cookie("nome","valor");
response.addCookie(c);
}

try
{
request.getRequestDispatcher("/meu.jsp").forward(request,response);
}
catch(IllegalStateException e)
{
e.printStackTrace();
}
}
}
Com isso descobri que a exceção é gerada quando os métodos flush e close do PrintWriter, que é criado pelo HttpServletResponse, são chamados, e quando o método getOutputStream do HttpServletResponse é invocado. Isso é claro, se após chamá-los for executado um forward.

Verifiquei também que se o forward for executado antes, somente a chamada a getOutputStream gera a exception. No tomcat ele dá a seguinte mensagem: getWriter() has already been called for this response.

Método forward de RequestDispatcher prevê algumas exceções

Sempre que utilizar o método forward da interface RequestDispacther é preciso tratar exceções ServletException e IOException. Utilizando uma IDE como o eclipse a gente nunca se preocupa com isso, mas para quem está estudando pro SCWCD, isso é importantíssimo.

Primeiro é preciso importar as classes:
import javax.servlet.ServletException;
import java.io.IOException;
Depois tratar ou capturando a exceção:

try
{
request.getRequestDispatcher("/pagina.jsp").forward(request,response);
}
catch(IOException e)
{
log(e.getMessage());
}

catch(ServletException e)
{
log(e.getMessage());
}
Ou reenviando ela:

public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
request.getRequestDispatcher("/pagina.jsp").forward(request,response);
}

terça-feira, 3 de abril de 2007

Política de privacidade

Este site pode utilizar cookies e/ou web beacons quando um usuário
tem acesso às páginas. Os cookies que podem ser utilizados associam-se
(se for o caso) unicamente com o navegador de um determinado
computador.

Os cookies que são utilizados neste site podem ser instalados pelo
mesmo, os quais são originados dos distintos servidores operados por
este, ou a partir dos servidores de terceiros que prestam serviços e
instalam cookies e/ou web beacons (por exemplo, os cookies que são
empregados para prover serviços de publicidade ou certos conteúdos
através dos quais o usuário visualiza a publicidade ou conteúdos em
tempo pré determinados). O usuário poderá pesquisar o disco rígido de
seu computador conforme instruções do próprio navegador.

Usuário tem a possibilidade de configurar seu navegador para ser
avisado, na tela do computador, sobre a recepção dos cookies e para
impedir a sua instalação no disco rígido. As informações pertinentes a
esta configuração estão disponíveis em instruções e manuais do próprio
navegador

* O Google, como fornecedor de terceiros, utiliza cookies para exibir anúncios no seu site.
* Com o cookie DART, o Google pode exibir anúncios para seus usuários com base nas visitas feitas aos seus e a outros sites na Internet.
* Os usuários podem desativar o cookie DART visitando a Política de privacidade da rede de conteúdo e dos anúncios do Google.