Comme moi, tu as hérité d’un projet Java bâti sur Spring ET Struts. Oui plutôt qu’utiliser Spring MVC, quelqu’un s’est enquiquiné à utiliser Struts. Et un jour, tu décides d’un changement majeur…
Le changement: virer ce serveur Windows
Fatigué des performances ridicules, usé par des mises à jour sans fin de Windows, tu prends ton courage à deux mains, la carte de crédit du Boss et tu achètes un bon gros serveur pour héberger ton application Tomcat / MySql. Et là, chassez le naturel il revient au galop, tu installes une belle distribution Debian (parce que c’est la meilleure). Ajouter Tomcat et MySql est une formalité.
Passage à Linux et coup de stress
Bien en confiance, tu configures ton outil CD/CI pour déployer sur Tomcat/Linux et tu déploies. Le démarrage se passe sans souci et quand tu te connectes à l’application tu admires cette superbe mire de login. Joie. Ivre de ce succès, tu t’enhardis et tu t’identifies. Et ça fonctionne. Là c’est bon, toute l’équipe est joyeuse, la migration ne sera bientôt qu’un lointain souvenir. Mais avant cela, tu décides d’ouvrir une liste un peu conséquente afin de profiter de la vitesse de ce serveur. Aucune donnée n’est affichée et l’application se borne à afficher une message d’erreur bien couillon.\
Le NullPointerException de l’espace
Donc la lecture du log d’erreur commence, la cause de l’erreur est vite trouvée, il s’agit d’un NullPointerException. Là tu te dis “mais quel est le naze qui ne catche pas ses exceptions?”. Tu vas donc voir le code concerné et tu réalises que c’est le service de base (père de tous les services d’entities) qui déconne. Mais euh? C’est juste pas possible que ce service ne fonctionne pas, sans lui l’application ne tient pas debout. Tu continues d’analyser et tu arrives à la conclusion que ce service ne reçoit pas les paramètres attendus alors que le client les envoies bien. Mais c’est quoi ce binz? (pour rester poli).
Solution de facilité: accuser les autres
C’est bien connu, toi tu sais programmer, c’est les autres qui sont cons. Fort de ce constat, tu décides de remplacer Tomcat v8.5.x par la version suivante dans la même série (que tu as testé en local, ton application fonctionne donc tu as confiance). Tu relances et même échec. Quelques coups de StackOverflow plus tard, tu es convaincu que c’est bien Tomcat qui déconne et tu passes directement à la dernière version de cette série 8. Là c’est certain, le problème est résolu. Et bien non, ce NPE persiste. Tu décides donc de sauter à Tomcat 9 pour voir si c’est Tomcat ou toi. Bim! Le problème persiste.\
Tomcat étant hors de cause, il reste la version de Java qui pourrait jouer des tours. Aussi tôt, aussi tôt fait, tu installes un autre JDK (adoptOpenJdk sous Windows donc) en te disant que hop! C’est dans la poche. Et bien non. Là, une certaine fatigue t’envahit mais elle est vite compensée par un bouillonnement intérieur, celui qui dit “c’est pas un NullPointerException qui va me résister”.
Solution de pro: le remote debug
Petit résumé de la situation: ton application fonctionne parfaitement sous Windows mais pas sous Linux. Tomcat est hors de cause, le JDK aussi. Tu voudrais bien faire un coup de debug afin de comprendre pourquoi les paramètres sont perdus mais bien sûr tu ne vas pas livrer en production une version truffée de “logger.info” ou “System.out.println”. Alors tu laisses ton cerveau vagabonder et tu finis par te demander si IntelliJ qui sait tout faire n’aurait pas un petit joker en stock. Bien sûr que si! Tu peux configurer un serveur remote et faire du debug, comme dans ta version locale. C’est magique. Donc tu configures ça, tu retapes un peu la configuration du tomcat sur le serveur pour le passer en mode debug et avec la magie des breakpoints, tu commences à comprendre…
Struts, pourquoi est-il aussi méchant?
Après avoir arrêté le code juste avant cette fichue NPE, tu peux analyser la pile d’appels incluant les 25 (oui oui) intercepteurs Struts (soit environ 75 appels à analyser). Et de ligne en ligne, tu finis par trouver l’erreur! Lors de l’appel d’une JSP, un des dispatchers n’a pas le droit d’écrire son fichier temporaire sur disque (1). Mais? Que? Enfin? Bien sûr, impossible de savoir dans quel dossier il veut écrire. L’analyse continue et oh! Surprise! Ce dispatcher possède une configuration dans Struts.xml qui renvoie vers ApplicationContext.xml. Curieux, tu lis cela et tu découvres le pot aux roses: la propriété multipartSaveDir contient le nom d’un dossier. Petit contrôle des droits, effectivement le user qui lance le serveur Tomcat n’a pas droit d’écrire dans ce dossier. Avant de changer cela, tu lis quand même la documentation Struts, juste pour éviter de faire une boulette. La pertinence de la documentation est sans égale, il n’y a aucune information entre ce dossier et un fichier temporaire qui serait généré lors de l’appel. Tu ajoutes donc le droit d’écrire à l’utilisateur Tomcat et tout fonctionne. Ouf! Il ne s’est écoulé que 4 heures…
(1): pourquoi donc un dispatcher devrait écrire un fichier temporaire pour servir une page JSP? Parce que Tiles fait sa magie en incluant ce qui est configuré pour cette page.
Conclusion
Bah parfois, même un coup de RTFM n’aide pas (faut dire que faire interagir 3 frameworks, ça complique forcément). C’est moche. Et surtout, quand on ajoute un dossier dans une configuration, on se demande à quoi il va servir, on le note en commentaire et on gère les droits utilisateurs en conséquence.