Bug OSWorkflow :
un NullPointerException causé
par AbstractWorkflow.canInitialize
Développant avec OSWorkflow dans plusieurs projets, j’ai affronté, un jour, une exception étrange. Ce n’était pas le genre d’exceptions dont on peut deviner la cause dès le premier coup d’œil.

This article is also available in English.
java.lang.NullPointerException at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:261) at java.lang.ClassLoader.loadClass(ClassLoader.java:299) at com.sun.appserv.server.util.ASURLClassLoader.loadClass(ASURLClassLoader.java:100) at java.lang.ClassLoader.loadClass(ClassLoader.java:299) at java.lang.ClassLoader.loadClass(ClassLoader.java:251) at com.sun.enterprise.util.ConnectorClassLoader.loadClass(ConnectorClassLoader.java:176) at java.lang.ClassLoader.loadClass(ClassLoader.java:251) at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1405) at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1292) at com.opensymphony.module.propertyset.PropertySetManager.getInstance(PropertySetManager.java:51) at com.opensymphony.module.propertyset.PropertySetManager.getInstance(PropertySetManager.java:31) at com.opensymphony.workflow.AbstractWorkflow.canInitialize(AbstractWorkflow.java:395) at com.opensymphony.workflow.AbstractWorkflow.canInitialize(AbstractWorkflow.java:364)
Mon premier réflexe était de la googler. Suivre les quelques liens de résultats que j’ai trouvé (comme ici, là et là) n’a pas résolu le problème. Des questions sont restées sans réponses, d’autres, ont eu le conseil de vérifier les jars et le packaging. Cela m’a poussé à aller de l’avant, et à analyser le code source pour le déboguer et retracer l’origine du problème.
Ce que j’ai découvert, c’est que le problème réside dans la méthode canInitialize de la classe AbstractWorkflow. Comme tout Workflow (classe qui implémente l’interface Worklflow) hérite de AbstractWorkflow (même si vous développez vos propres implémentations, il est fortement recommandé d’hériter de AbstractWorkflow), vous pouvez avoir ce genre d’exceptions si vous appelez la méthode canInitialize. Il est évident que le fait d’appeler canInitialize existe dans toute application (ou presque) qui se base sur OSWorkflow, tout simplement parce que cette méthode indique si l’utilisateur courant est autorisé à démarrer une nouvelle instance d’un workflow donné.
Les lignes 394 et 395 sont les plus intéressantes (de la méthode canInitialize) :
// since no state change happens here, a memory instance is just fine
PropertySet ps = PropertySetManager.getInstance("memory", null);
Ce que ce bout de code signifie exactement c’est : demander au PropertySetManager d’instancier l’implémentation PropertySet qui a été configuré avec le nom “memory” dans le fichier de configuration de PropertySet. Cela fonctionne à merveille avec la configuration par défaut de PropertySet. Mais qu’en est-il si le développeur choisis d’avoir une configuration PropertySet propre à lui (en ayant son propre propertyset.xml) qui ne définit pas une implémentation PropertySet avec le nom “memory” ?
C’était exactement le cas de tous ceux qui ont fait face à cette exception. A mon avis, une telle implémentation soumet le développeur à une forte contrainte qui n’est mentionné nulle part dans la documentation.
Première solution : Le contournement
La première solution qui peut venir à l’esprit de chacun est celle de définir, tout simplement, une implémentation PropertySet avec le nom “memory”, en ajoutant :
<propertyset name="memory" class="com.opensymphony.module.propertyset.memory.MemoryPropertySet" />
dans le fichier de configuration.
Cette solution est rapide et facile à mettre en œuvre, mais fait introduire quelque chose que, personnellement, je déteste : du code et/ou de la configuration qui ne sert à rien, qui est juste là “pour que ça fonctionne” et que “de toute façon, ça ne mange pas de pain”.
Deuxième solution : Redéfinir la classe AbstractyWorkflow
Une solution plus “clean”, serait de réécrire la classe AbstractWorkflow (i.e. : corriger le bug). Ce qu’on devrait modifier est évidemment la 395ème ligne.
Commençons par comprendre l’utilité de cette instance de PropertySet (stocké dans la variable ps). Si nous essayons d’examiner les lignes de code suivantes, nous nous apercevrons facilement que cette instance est utilisée pour…rien du tout !
Elle est là, juste parce que la méthode de l’interface Condition prend une instance de PropertySet comme l’un de ses paramètres. Et c’est pour cela qu’on peut lire dans la ligne de commentaire “a memory instance is just fine”, qui veut dire : “une instance mémoire est amplement suffisante”. Puisque une instance PropertySet est toujours associé à une instance de workflow, et qu’il ne s’agit ici d’aucune instance de workflow (rappelez-vous, on essaie juste de savoir si l’utilisateur courant a le droit d’en instancier), cela n’a pas vraiment beaucoup de sens de parler ici d’une instance PropertySet.
C’est une raison pour laquelle affecter ‘null’ à la variable ps peut être suffisant. Mais, si votre (propre) condition dépend du PropertySet passé en paramètre comme ci-dessous :
public boolean passesCondition(Map transientVars, Map args, PropertySet ps) throws StoreException {
String some = ps.getString("param");
Ce qui est, d’ailleurs, bizarre pour une condition d’initialisation de workflow (sauf si c’est une classe réutilisée pour d’autres conditions), vous aurez surement un NullPointerException (à moins que vous ne testiez sur la variable ps).
Bref, je vous suggère un substitut plus sure à notre 395ème ligne :
//directly instantiate the memory propertyset implementation //no longer rely on the way the developer configuration PropertySet ps = new MemoryPropertySet();
Ou aussi, vous pouvez affectez une instance de classe anonyme :
PropertySet ps = new PropertySet(){
public boolean exists(String arg0) throws PropertyException {
return false;
}
public Object getAsActualType(String arg0) throws PropertyException {
return null;
}
public boolean getBoolean(String arg0) throws PropertyException {
return false;
}
...
...
...
public int getInt(String arg0) throws PropertyException {
return 0;
}
public Collection getKeys() throws PropertyException {
return Collections.EMPTY_LIST;
}
...
...
public boolean supportsTypes() {
return false;
}
};
J’espère que ce billet aidera certaines personnes. Je voulais, juste, ajouter que la deuxième solution proposée peut causer des problèmes ou des comportements inattendus, si vous mettez à jour votre version de OSWorkflow et que vous oubliez de réajustez vos modifications.
Tags: bug, canInitialize, exception, NullPointerException, OSWorkflow, PropertySet, PropertySetManager.getInstance
