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.

2 comentários:

  1. show de bola Tiago, este post ficou muito bom.
    Acho muito importante dividir estas experiencias com outros desenvolvedores.
    abs.

    ResponderExcluir
  2. http://blog.phpbuero.de/?p=17
    Sorry I couldn´t find your eMail, this is in reply to your Ajaxian comment about type-aware method overloading.
    Have Fun!
    Frank Thürigen
    frank . thuerigen @ phpbuero . de

    ResponderExcluir