Contrôler vos jobs Alfresco avec JMX (partie 2)

Dans la première partie, nous avons vu comment exposer simplement un job (plus précisément, le trigger d’un job) dans une console d’administration JMX.
Nous avons également vu que cette exposition n’est pas très ergonomique, et qu’il vaut mieux savoir où cliquer.

Dans cette seconde partie, nous allons nous intéresser aux moyens de rendre le fonctionnement plus « user friendly », quoique je ne saurais vous recommander de laisser vos utilisateurs jouer avec la console JMX :-).

Définition du besoin

Mon besoin est de pouvoir réaliser les opérations suivantes sur mon job :

  • Configurer l’expression cron ;
  • Arrêter le job ;
  • Redémarrer le job ;
  • Connaître le statut du job.

Un besoin très simple en somme ! Encore une fois, Spring et sa merveilleuse magie va bien nous être utile.

Mise en oeuvre de la magie Spring

Le concept

Lorsque l’on expose un bean en JMX comme nous l’avons fait dans la première partie, Spring va inspecter le bean pour en extraire tous les attributs et méthodes publics pour les exposer.
Plutôt que de laisser Spring tout faire, il est également possible de spécifier dans la configuration une interface qui va nous fournir les méthodes à exposer. Les attributs seront alors définis en fonction des setters/getters disponibles dans les méthodes exposées. On gagne ainsi un contrôle sur les actions qui vont être possibles depuis la console JMX.

Définition de l’interface de gestion

Notre interface est plutôt triviale : il s’agit de définir uniquement ce que nous avons définit comme besoin. Rien de très spécial ici.

package net.torda.alfresco.util;

/**
 * Interface définissant les méthodes qui seront exposées sur la console JMX
 * @author Charles Le Seac'h
 */
public interface CronTriggerBeanManagerInterface {

	/**
	 * Etat actuel du job.
	 * @return booléen vrai si le job est démarré
	 */
	public boolean isEnabled();

	/**
	 * Démarre le job
	 * @throws Exception
	 */
	public void enable() throws Exception;

	/**
	 * Arrête le job
	 * @throws Exception
	 */
	public void disable() throws Exception;

	/**
	 * Récupération de l'expression cron actuellement définie
	 * @return chaîne de caractère au format cron
	 */
	public String getCronExpression();

	/**
	 * Définition de l'expression cron à utiliser
	 * @param cronExpression expression cron
	 */
	public void setCronExpression(String cronExpression);
}

Implantation de l’interface

Deux solutions sont possibles:

  1. Définir notre propre CronTriggerBean de la sorte :
    public class JMXCronTriggerBean extends CronTriggerBean implements CronTriggerBeanManagerInterface

    puis implanter le code de nos méthodes spécifiques.
    Cette solution est simple, mais elle va être assez intrusive dans la façon de définir nos triggers de jobs. Ce que nous écrivions

    <bean id="jobDemonstrationTrigger" class="org.alfresco.util.CronTriggerBean">

    devra s’écrire

    <bean id="jobDemonstrationTrigger" class="net.torda.alfresco.util.JMXCronTriggerBean">

    Ce n’est pas le bout du monde, mais ceci nécessite de revoir la définition de tous les jobs pour lesquels nous voulons une gestion en JMX. Une telle modification est trop intrusive pour assurer une bonne maintenance de notre extension.

  2. La seconde solution consiste en l’écriture d’un manager pour notre job. Cette solution offre la possibilité de gérer n’importe quel (cron triggerd) job depuis la console JMX et bénéficie en outre d’une plus grande simplicité dans l’écriture du code. C’est donc celle-ci que j’ai retenu et qui va être présentée ci-dessous.

Le manager de jobs

Pour plonger tout de suite dans le bain, voici le code épuré des détails triviaux :

package net.torda.alfresco.util;

public class CronTriggerBeanManager implements CronTriggerBeanManagerInterface {
  private static Log logger = LogFactory.getLog(CronTriggerBeanManager.class);

  private CronTriggerBean triggerBean;

  @Override
  public boolean isEnabled() {
    return triggerBean.isEnabled();  
  }
  
  @Override
  public void enable() throws Exception {
    triggerBean.setEnabled(true);
    triggerBean.afterPropertiesSet();
    logger.info("Job " + triggerBean.getBeanName() + " enabled");
  }
  
  @Override
  public void disable() throws Exception {
    triggerBean.setEnabled(false);
    triggerBean.destroy();
    logger.info("Job " + triggerBean.getBeanName() + " disabled");
  }
  
  @Override
  public String getCronExpression() {
    return triggerBean.getCronExpression();
  }
  
  @Override
  public void setCronExpression(String cronExpression) {
    triggerBean.setCronExpression(cronExpression);
    logger.info("Job " + triggerBean.getBeanName() + " scheduled with expression "+cronExpression);
    try {
      triggerBean.afterPropertiesSet();
    } catch (Exception e) {
      logger.error("Setting cron expression "+ cronExpression + " for job " + triggerBean.getBeanName() + " throws error : " + e.getMessage());
      logger.error(e.getStackTrace());
    }
  }  
}

Notre manager à besoin de savoir quel job va être géré; on lui donne donc cette information avec :

private CronTriggerBean triggerBean;

(on n’oubliera donc pas le setter associé).

Comme vu dans la première partie, l’activation d’un job passe par la définition de la propriété enabled et l’appel de la méthode afterPropertiesSet :

public void enable() throws Exception {
  triggerBean.setEnabled(true);
  triggerBean.afterPropertiesSet();
  logger.info("Job " + triggerBean.getBeanName() + " enabled");
}

La désactivation d’un job passe par l’exécution de la méthode destroy :

public void disable() throws Exception {
  triggerBean.setEnabled(false);
  triggerBean.destroy();
  logger.info("Job " + triggerBean.getBeanName() + " disabled");
}

Et pour finir, après avoir saisi la valeur de l’expresison cron, il ne faut pas oublier d’appeler afterPropertiesSet :

public void setCronExpression(String cronExpression) {
  triggerBean.setCronExpression(cronExpression);
  logger.info("Job " + triggerBean.getBeanName() + " scheduled with expression "+cronExpression);
  try {
    triggerBean.afterPropertiesSet();
  [...]

Et voila ! C’est tout pour la partie Java !

Et le Spring dans tout ça ?

Il ne faut pas l’oublier, en effet. C’est Spring qui va donner vie à tout ce petit monde.
Pour commencer, un point important : la description du job vue en première partie ne change absolument pas.

Nous allons simplement modifier la définition de la partie « exposition JMX » de la manière suivante :

<bean id="TordaAdvancedJMXJobs" class="org.springframework.jmx.export.MBeanExporter">
  <property name="server" ref="alfrescoMBeanServer"/>
  <property name="beans">
    <map>
      <entry key="Torda:type=Advanced Jobs,name=Job Demonstration Trigger" value-ref="jobDemonstrationTriggerManager"/>
    </map>
  </property>
  <property name="assembler">
    <bean class="org.springframework.jmx.export.assembler.InterfaceBasedMBeanInfoAssembler">
      <property name="managedInterfaces">
        <value>net.torda.alfresco.util.CronTriggerMbeanManagerInterface</value>
      </property>
    </bean>
  </property>
</bean>
<bean id="jobDemonstrationTriggerManager" class="net.torda.alfresco.util.CronTriggerBeanManager">
  <property name="triggerBean" ref="jobDemonstrationTrigger"/>
</bean>

La nouveauté est ici :

<property name="assembler">
  <bean class="org.springframework.jmx.export.assembler.InterfaceBasedMBeanInfoAssembler">
    <property name="managedInterfaces">
      <value>net.torda.alfresco.util.CronTriggerMbeanManagerInterface</value>
    </property>
  </bean>
</property>

C’est cette section qui permet de spécifier que le MBean JMX va être défini au moyen d’une interface.

Il ne reste plus qu’à redémarrer Alfresco pour voir le résultat.

Le résultat

L’interface graphique

L’onglet « Attributes » n’affiche plus que 2 propriétés.

  • CronExpression : la valeur est en bleue, elle peut donc être modifiée. Cette propriété est disponible car l’interface dispose des méthodes « getCronExpression » et « setCronExpression ».
  • Enabled : la propriété est en noir, donc en lecture seule. Notre interface ne dispose en effet pas de la méthode setEnabled qui permettrait de l’avoir en lecture/écriture.

L’onglet « Operations » liste uniquement les méthodes que nous avons définies dans l’interface :

C’est quand même beaucoup plus lisible comme cela !

Modification de la fréquence du job

La fréquence du job peut être modifiée :

  • Soit directement depuis l’onglet « Attributes » en modifiant la valeur de « CronExpression » (c’est ma méthode préférées)
  • Soit en exécutant la méthode « setCronExpression » en passant l’expression souhaitée en paramètre.

Que l’on choisisse l’une ou l’autre des manière, le résultat de la demande est traité immédiatement, contrairement à ce que nous avions avec l’exposition basique du bean :

Arrêt / relance du job

Les méthodes « enable » et « disable » peuvent être exécutées à tout moment, et leur nom est bien plus parlant qu’avant :

Informations

Comme vous pouvez le constater sur les captures d’écran précédentes, chaque opération de modification du job donne lieu à la production d’une entrée de log. Pratique pour vérifier les opérations qui ont eu lieu !.

Application à d’autres jobs

Jusqu’à présent, ce billet se fondait sur un job tout simple de démonstration.
Tous les principes mentionnés plus haut peuvent s’appliquer aux autres jobs définis dans le système. Par exemple, il va être possible de gérer les jobs Alfresco par ce biais.

La première étape consistera en la déclaration des managers via Spring :

<bean id="tempFileCleanerTriggerManager" class="net.torda.alfresco.util.CronTriggerBeanManager">
  <property name="triggerBean" ref="tempFileCleanerTrigger"/>
</bean>
<bean id="contentStoreCleanerTriggerManager" class="net.torda.alfresco.util.CronTriggerBeanManager">
  <property name="triggerBean" ref="contentStoreCleanerTrigger"/>
</bean>
<bean id="nodeServiceCleanupTriggerManager" class="net.torda.alfresco.util.CronTriggerBeanManager">
  <property name="triggerBean" ref="nodeServiceCleanupTrigger"/>
</bean>

La seconde concerne l’exposition de ces managers :

<bean id="TordaAdvancedJMXJobs" class="org.springframework.jmx.export.MBeanExporter">
  <property name="server" ref="alfrescoMBeanServer"/>
  <property name="beans">
    <map>
      <entry key="Torda:type=Advanced Jobs,class=Alfresco,name=Temp File Cleaner" value-ref="tempFileCleanerTriggerManager"/>
      <entry key="Torda:type=Advanced Jobs,class=Alfresco,name=Content Store Cleaner" value-ref="contentStoreCleanerTriggerManager"/>
      <entry key="Torda:type=Advanced Jobs,class=Alfresco,name=Node Service Cleanup" value-ref="nodeServiceCleanupTriggerManager"/>
    </map>
  </property>
  <property name="assembler">
    <bean class="org.springframework.jmx.export.assembler.InterfaceBasedMBeanInfoAssembler">
      <property name="managedInterfaces">
        <value>net.torda.alfresco.util.CronTriggerMbeanManagerInterface</value>
      </property>
    </bean>
  </property>
</bean>

Après le redémarrage d’Alfresco, on dispose d’une console JMX listant les différents jobs configurés :

Conclusion

Nous disposons maintenant d’un système extrêmement simple pour la gestion de nos jobs en JMX, sans pour autant dénaturer Alfresco. C’est un point essentiel de l’approche décrite ci-dessus : nous pouvons modifier l’exécution des jobs Alfresco sans jamais avoir altéré le moindre code ou la moindre configuration standard.
Mais on peut encore faire mieux ;-)

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