Jak instalator portletów w LIferay wyruchał classloader Tomcata
Kilka dni mi to zajęło, ale dopiero lekkie zapalenie gardła połączone z finałem leczenia za pomocą dwóch napojów piwopochodnych Somersby spowodowało, że mam rozwiązanie.
W czym problem leżał
Mamy sobie task polegający na dopisaniu do istniejącego portletu dodatkowego modułu. W dużym skrócie portlet pobiera dane z formularza www, zamienia je na PDFa i wysyła mailem. My mamy dodać webservice, który będzie niejako dodatkowym kanałem komunikacji. Inaczej mówiąc zamiast pieprzyć się z klikaniem formularza na www wyślemy sobie go z appki po WSie.
Samo implementowanie WS jako elementu istniejącego portletu w liferay zasługuje na osobny wpis, bo tak pojebanej technologii to tylko wśród phpowców szukać, a i tu wygląda to logiczniej. Dla nas istotne jest to, że w zależnościach naszego rozwiązania pojawia się cglib-2.1\_3 i commons-logging-1.0.4.
Objawy problemu
Dewelopujemy, paczkujemy, deployujemy. Na localhoście (goły tomcat) śmiga wyśmienicie. Deploy lokalny wykonujemy jako „rm -rf ; cp” deploy, czyli za pomocą czarnego ekranu, ale wała… deploy na test, odpalamy SoapUI i mamy… InvocationTargetException. Ok… bywa… generalnie ten błąd jest zwracany przez AXISa gdy coś się grubi wyjebie. Niestety w konstruktorze klasy tego błędu mamy:
Listing 1. konstruktor ITE
protected InvocationTargetException() {
super((Throwable)null); // Disallow initCause
}
Wot, bolszoj’ technika. Dla niekumatych… po chuj nam przyczyna błędu. Taki kod podobny jest w swej istocie do pustego bloku catch i powinno się autora na spalić na stosie… hm… stosie… hm… somersbiak się skończył… szit…
Ok, debbuger podpinamy się na wywołanie metody WS i metodą eliminacji dochodzimy do momentu gdzie się wywala. Okazuje się, że przyczyną błędu jest NoClassDefFoundError, który oznacza, że klasa została załadowana, ale w trakcie pierwszego jej wywołania, kiedy następuje jej weryfikacja, niezależenie metody statycznej czy konstruktora, coś nie bangla. I to solidnie nie bangla…
Klasą, która powodowała syf była… springowa ValidationUtils. Chwila zastanowienia… i okazuje się, że rzeczywistą przyczyną był problem z klasami ze wspomnianego wcześniej commons-logging. Tak błąd oznacza najczęściej, że w classpathie leża dwie klasy o identycznej nazwie. Oczywiście szybko się okazało, że rzeczywiście w WEB-INF/lib mam dwie paczki commons-logging, ale tylko na serwerze testowym. Przy okazji okazało się, że zdublowany jest jeszcze cglib tzn. jest jar „pełen” i wersja „nodep”… to nasunęło mi pewną myśl…
Przyczyna problemu
Po kilku krótkich testach znalazłem przyczynę problemu. Mechanizm instalujący portlety w liferayu wykonuje operację kopiowania. Problem pojawia się w momencie gdy z paczki usuwamy jakieś pliki. W trakcie instalacji stara wersja jest nadpisywana przez ekwiwalent bashowej operacji cp, ale nie przez ciąg operacji kasowania i kopiowania. W efekcie jeżeli podbijemy numer wersji jakiejś biblioteki to stara wersja pozostanie na swoim miejscu i będzie bruździć. Zazwyczaj problem ten nie występuje, bo „zabezpieczeniem”, jest „naturalne” działanie użytkowników, którzy ręcznie usuwają stary portlet, a nie zdają się na mechanizmy instalatora. W efekcie nawet gdy wystąpi tego typu awaria następuje paniczne usuwanie portletu z katalogu roboczego i nieświadome gaszenie pożaru. Ja nie zachowałem się jak typowy user, chyba dlatego, że ręczne usunięcie starej wersji gdy instaluje się nową jest dla mnie czymś głupim, tym samym spowodowałem problem, a ze względu na dodatkowo dziwne zachowanie się serwera testowego, ale nie localhosta zacząłem drążyć temat…