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.