Alfresco 3.4d : corriger l’exécution d’un behaviour Javascript pour l’évènement « onUpdateProperties »

Le problème du jour : sur la mise à jour des propriétés d’un noeud possédant un aspect particulier, je souhaite exécuter un JavaScript. « Facile ! Les behaviours en JavaScript sont nativement prévus par Alfresco », me suis-je dit d’expérience (je les ai déjà utilisés par le passé).
Sauf que… c’était pas si simple que ça avec Alfresco 3.4d.

Et pour expliquer tout ça, on reprend du début.

Un behaviour en JavaScript, comment ça marche ?

Il suffit de définir un bean Spring (dans un fichier -context.xml, donc) comme suit :

<bean id="on<mon aspect>UpdateProperties" class="org.alfresco.repo.policy.registration.ClassPolicyRegistration" parent="policyRegistration">
    <property  name="policyName">
        <value>{http://www.alfresco.org}onUpdateProperties</value>
    </property>
    <property  name="className">
        <value>{http://www.amexio.fr/projet/test/model/content/1.0}mon  aspect</value>
    </property>
    <property  name="behaviour">
        <bean  class="org.alfresco.repo.jscript.ScriptBehaviour"  parent="scriptBehaviour">
            <property  name="location">
                <bean  class="org.alfresco.repo.jscript.ClasspathScriptLocation">
                    <constructor-arg>
                        <value>alfresco/extension/behaviour/onmon  aspectUpdateProperties.js</value>
                    </constructor-arg>
                </bean>
            </property>
        </bean>
    </property>
</bean>

Dans le JavaScript, on dispose alors d’un objet behaviour porteur des arguments prévus par la « policy ».
Dans le cas de onUpdateProperties, les arguments prévus sont :

  • args[0] : le noeud déclencheur de l’évènement
  • args[1] : les propriétés du noeud avant la mise à jour
  • args[2] : les propriétés du noeud après la mise à jour

Tout cela est très simple et fonctionne bien.

J’ai pas parlé de problème plus haut ?

Ou plutôt, fonctionnait bien !
En effet, en version 3.4d, il m’est impossible d’obtenir les propriétés avant ou après mise à jour. Les arguments sont désespérément null, alors que le noeud (args[0]) est bien présent, lui.
Une rapide recherche me montre d’ailleurs que je ne suis pas le seul à souffrir du problème : on trouve des questions sur les forums Alfresco et une entrée dans Jira (https://issues.alfresco.com/jira/browse/ALF-7506).

Face à ce problème, deux solutions :

  1. je laisse tomber JavaScript et je passe en Java pour réaliser mes traitements
  2. je tente de corriger le problème

Comme j’aime vraiment l’approche « script » car elle permet de tester plein d’idées très rapidement, j’écarte la première solution. Ne reste plus que la seconde ;-)

La traque du problème

Maintenant qu’un axe de travail est choisi, il faut le mettre en oeuvre. Le premier travail consiste à trouver d’où vient le problème. Mes armes : Tomcat en mode JPDA, Eclipse et le debugger JavaScript intégré à Alfresco.
Après avoir exploré tous les mécanismes mis en œuvre pour fournir le tableau d’arguments, j’en arrive à la conclusion qu’il y a un problème dans la conversion Java / JavaScript des HashMap (les propriétés sont en effet des HashMap<QName, Serializable>).

La classe responsable de fournir les arguments au moteur JavaScript est org.alfresco.repo.jscript.Behaviour. Cette classe dispose de la méthode getArgs, qui, pour chaque argument va demander la conversion dans un format « scriptement acceptable » par un appel à ValueConverter.convertValueForScript.
convertValueForScript va s’arranger pour convertir les éléments qui lui sont passés en objets JavaScript; sauf que rien n’est prévu pour les HashMap ! C’est un premier point à corriger. La correction semble en outre assez facile car Alfresco propose une classe ScriptableHashMap dont le nom évoque la conversion d’une HashMap en un objet utilisable dans les scripts.

Le second élément qui a attiré mon attention, c’est que getArgs retourne un Serializable[]. Les autres méthodes que j’ai consultées lors de cette traque retourne plutôt des Scriptable.

Une correction possible

Donc si je récapitule, il me faut :

  1. Améliorer le principe du ValueConverter utilisé par la classe Behaviour
  2. Revoir la signature de la méthode getArds de la classe Behaviour
  3. Trouver un point d’extension pour prendre en compte mes modifications

Commençons par ce dernier point car il est essentiel !
Si point d’extension il y a, ça doit forcément se trouver dans mon XML… et quand en plus j’ai écrit quelque chose du genre <bean class= »org.alfresco.repo.jscript.ScriptBehaviour », tout devient évident.
Ca tombe d’autant mieux que c’est ScriptBehaviour qui va injecter mon « behaviour » (classe Behaviour) dans le modèle JavaScript :

model.put("behaviour", new org.repo.jscript.Behaviour(this.behaviour.serviceRegistry, method.getName(), args));

Bon, il va falloir pas mal recopier la classe originale pour créer mon CustomScriptBehaviour, mais ce n’est pas bien grave. La seule ligne qui va changer est celle mentionnée ci-dessus. Elle va se transformer en :

model.put("behaviour", new fr.amexio.alfresco.repo.jscript.CustomBehaviour(this.behaviour.serviceRegistry, method.getName(), args));

Mon CustomBehaviour va lui aussi consister en une recopie (pas le choix, on ne dispose pas des accesseurs indispensables pour l’extension) de Behaviour, avec la modification de la méthode getArgs suivante :

public Scriptable getArgs()
{
  if (this.jsArgs == null)
  {
    CustomValueConverter valueConverter = new CustomValueConverter();
    ArrayList<Serializable> tmpArgs = new ArrayList<Serializable>();
    for (Object arg : this.args) 
  {
   tmpArgs.add(valueConverter.convertValueForScript(services, this.scope, null, (Serializable)arg));
  }
  this.jsArgs = (Scriptable)valueConverter.convertValueForScript(services, this.scope, null, tmpArgs);
 }
 return this.jsArgs;
}

Où l’on peut voir apparaître le CustomValueConverter qui va faire le gros du travail !
Sa logique, je vous la livre ici, brute :

public class CustomValueConverter extends ValueConverter {  
  public Serializable convertValueForScript(ServiceRegistry services, Scriptable scope, QName qname, Serializable value) 
  {
    value = super.convertValueForScript(services, scope, qname, value);
    if (value instanceof HashMap) 
    {
      ScriptableHashMap<Serializable,Serializable> elements;
      elements = new ScriptableHashMap<Serializable,Serializable>();
      HashMap<Serializable,Serializable> valueHashMap = (HashMap<Serializable,Serializable>)value;
      for (Object key : valueHashMap.keySet())
      {
        Serializable propValue = (Serializable) valueHashMap.get(key);		               
        elements.put(key.toString(), convertValueForScript(services, scope, qname, propValue));
      }
      value = elements;
    }
    return value;			
  }
}

En clair, on va parcourir tous les éléments de la map, convertir la clé en chaîne et convertir la valeur en quelque chose de compréhensible par JavaScript.
C’est quand même pas sorcier !

Une fois ces trois classes injectées dans la webapp, mon fichier -context.xml modifié comme il se doit (pour référencer CustomScriptBehaviour) et Alfresco redémarré, tout fonctionne bien !
Attention, je n’ai pas poussé les tests très loin, mais le résultat me semble déjà très satisfaisant.

Comme d’habitude, j’ai poussé cette proposition de correction sur le jira Alfresco : https://issues.alfresco.com/jira/browse/ALF-7506.

Cette entrée a été publiée dans Alfresco, avec comme mot(s)-clef(s) , , , , . Vous pouvez la mettre en favoris avec ce permalien.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée.

Vous pouvez utiliser ces balises et attributs HTML : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Protected by WP Anti Spam