JBoss AS 7 et MBeanRegistration : attention au preRegister/postRegister

Lors du déploiement d’un service JMX sous JBoss AS 7.1.1.Final si la classe déployée implémente MBeanRegistration (JEE 5) alors cette dernière pourra effectuer une tâche avant son enregistrement JMX (preRegister) et après son enregistrement (postRegister).

Oui mais …

Dans le cas de la migration d’un service JMX Quartz 1.x et de l’installation de Quartz en tant que module dans JBoss AS 7 je me suis rendu compte que dans une méthode preRegister ou postRegister le démarrage du Scheduler Quartz qui a été configuré via un fichier XML il y a une exception ClassNotFoundException pour le chargement des classes des Jobs : Quartz , en tant que module et au moment de preRegister() ou postRegister(), n’a pas encore accès aux classes qui viennent d’être déployées.

Il faut dire que Quartz utilise un classloader spécifique (CascadedClassLoaderHelper exploitant une cascade de ClassLoader) qui n’est peut-être pas compatible avec le class loader spécifique de « JBoss Modules », du moins au déploiement du service JMX. Oui parce qu’une fois le service JMX déployé un nouveau rechargement du fichier XML de Quartz ne provoque plus d’exception ClassNotFoundException …

A noter mon message sur le forum de JBoss AS 7 concernant ce sujet :

https://community.jboss.org/message/752132#752132

Autre point concernant le preRegister/postRegister suite au problème rencontré avec le service JMX de Quartz : si un service JMX appelle un EJB il y aura probablement une erreur du fait que l’EJB n’existe pas encore. Et cela même si le déploiement du service précise qu’il dépend du EAR contenant l’EJB à exploiter. Cela est dû au fait de la parallélisation des déploiements effectuée par JBoss AS 7. Effectivement le service JMX est assuré d’avoir les classes nécessaires à son fonctionnement (par exemple les classes de l’EAR contenant les EJBs) mais il n’est pas assuré que l’EJB ait été initialisé (ce qui n’a jamais été mon cas du moins en déployant le service JMX contenu dans un jar contenu lui même dans un EAR contenant aussi un autre JAR ayant les EJBs).

A noter qu’il est peut être possible de faire dépendre le mbean d’un service JMX lié à l’EJB comme pour exemple :

	<mbean ...>
		<depend>deployment=myapp.ear,singleton-bean=Scheduler,subdeployment=myapp-ejb.jar,subsystem=ejb3</depend>
	<mbean>

Je n’ai pas testé cela mais est-ce que le service JMX ne va pas échouer en précisant qu’il ne trouve pas la dépendance ou bien est-ce qu’il va attendre un peu avant que celle-ci soit disponible (ce qui m’étonnerait).

Publicités

JBoss AS 7 : Quartz 1.x et JMX

Lors d’une migration de JBoss 5.1.0.GA vers JBoss 7.1.1.Final le scheduler Quartz était en version 1.6.6 et se chargeait via le plugin Quartz JBoss/JMX c’est à dire via un service JMX configuré dans un fichier jboss-service.xml packagé dans un .SAR

	<mbean code="org.quartz.ee.jmx.jboss.QuartzService"
		name="scheduler:service=QuartzService,name=QuartzService">

		<attribute name="StartScheduler">true</attribute>

		<attribute name="Properties">
			...
			org.quartz.plugin.jobInitializer.fileNames=quartz-jobs.xml
			...
		</attribute>
	</mbean>

La classe org.quartz.ee.jmx.jboss.QuartzService est livré par le scheduler Quartz (via une librairie quartz-jboss-1.6.6.jar).

Cependant ce mode de fonctionnement n’est plus compatible avec JBoss AS 7 car la classe QuartzService exploite des « services » JMX qui ne semblent plus disponible avec JBoss AS 7 comme par exemple des opérations JNDI (rebind() dans la classe QuartzService).

Pour migrer Quartz dans JBoss AS 7 la version a été mise à jour (tant qu’à faire) : Quartz 2.1.5. La configuration XML de Quartz via le fichier « quartz-jobs.xml » a été légèrement modifié (voir ci-dessous) et le scheduler est lancé via un EJB de type @Singleton marqué avec @Startup. Le singleton s’occupe d’enregistrer les différents MBean JMX par l’obtention d’un MBeanServer via le code suivant :

	MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();

Concernant la migration de Quartz 1.x vers Quartz 2.x voici les changements au sein du fichier de configuration des jobs (du moins pour l’application concernée) :

  • L’élément racine est modifié,
    <quartz xmlns="http://www.opensymphony.com/quartz/JobSchedulingData"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://www.quartzscheduler.org/ns/quartz
    	http://www.opensymphony.com/quartz/xml/job_scheduling_data_1_5.xsd"
    	version="1.5">

    devient

    <job-scheduling-data version="2.0" 
    	xmlns="http://www.quartz-scheduler.org/xml/JobSchedulingData" 
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://www.quartz-scheduler.org/xml/JobSchedulingData 
    	http://www.quartz-scheduler.org/xml/job_scheduling_data_2_0.xsd">
  • <job> est remplacé par <schedule> (et donc </job> par </schedule>)
  • <job-detail> est remplacé par <job> (et donc </job-detail> par </job>)
  • <volatility>…</volatility> est supprimé
  • l’attribut allows-transient-data de l’élément <job-data-map> est supprimé

JBoss AS 7 : cache Hibernate de second niveau avec une stratégie « read-write »

Par défaut JBoss AS 7 exploite Infinispan en tant que cache Hibernate de second niveau.

Cela se traduit, dans une entité JPA, par l’utilisation d’une annotation @org.hibernate.annotations.Cache. En migrant une application utilisant un cache applicatif de second niveau de type EhCache je suis tombé sur des entités utilisant une stratégie de cache de type « read-write », via l’annotation suivante :

@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)

Au déploiement de l’application, et sans utilisation de EhCache, il y a une erreur d’Infinispan précisant qu’il n’est pas possible d’avoir un cache de type « read-write ». Via la documentation d’Hibernate il est indiqué qu’Infinispan ne gère pas le mode « read-write ». Il est donc, dans ce cas, obligatoire de s’affranchir d’Infinispan.

Pour installer EhCache 2.5.3 en tant que cache de second niveau en tant que module au sein de JBoss AS 7 / Hibernate 4.x il faut exploiter la librairie « hibernate-ehcache » qui permet de faire un lien entre Hibernate 4.x et EhCache alors que pour Hibernate 3.x le lien avec EhCache était directement inclus au sein d’EhCache via la classe net.sf.ehcache.hibernate.EhCacheRegionFactory.

Dans le dossier ${JBOSS_HOME}/modules/net/sf/ehcache/main il faut déposer le fichier ehcache-core-2.5.3.jar et créer le fichier module.xml suivant :

<?xml version="1.0" encoding="UTF-8"?>

<module xmlns="urn:jboss:module:1.1" name="net.sf.ehcache" slot="main">
    <resources>
        <resource-root path="ehcache-core-2.5.3.jar"/>
    </resources>

    <dependencies>
        <module name="org.slf4j"/>
        <module name="javax.transaction.api"/>
        <module name="javax.api" />

        <system export="false">
            <paths>
                <path name="org/xml/sax" />
                <path name="org/xml/sax/ext"/>
                <path name="org/xml/sax/helpers" />
            </paths>
        </system>
    </dependencies>
</module>

Dans le dossier ${JBOSS_HOME}/modules/org/hibernate/cache/ehcache il faut déposer le fichier hibernate-ehcache-4.0.1.Final.jar (pour Hibernate 4.0.1 packagé avec JBoss AS 7.1.1.Final) et créer le fichier module.xml suivant :

<?xml version="1.0" encoding="UTF-8"?>

<module xmlns="urn:jboss:module:1.1" name="org.hibernate.cache.ehcache">
    <resources>
        <resource-root path="hibernate-ehcache-4.0.1.Final.jar"/>
    </resources>

    <dependencies>
        <!-- ce module effectue la liaison entre ehcache et hibernate -->
        <module name="net.sf.ehcache" />
        <module name="org.hibernate" />

        <!-- dépendances d'hibernate -->
        <module name="org.jboss.logging"/>
        <module name="javax.api"/>
        <module name="javax.transaction.api"/>
    </dependencies>
</module>

Enfin au niveau du déploiement d’un ear / d’un war / etc il faut indiquer l’utilisation des modules net.sf.ehcache et org.hibernate.cache.ehcache. Par exemple avec un fichier jboss-deployment-structure.xml d’un ear :

<?xml version="1.0" encoding="UTF-8"?>

<jboss-deployment-structure xmlns="urn:jboss:deployment-structure:1.1">
    <ear-subdeployments-isolated>false</ear-subdeployments-isolated>

    <deployment>
        <dependencies>
            <module name="net.sf.ehcache" />
            <module name="org.hibernate.cache.ehcache" />
        </dependencies>
    </deployment>
</jboss-deployment-structure>

JBoss AS 7, UserTransaction et new Thread()

Quand, dans JBoss AS 7, on utilise Hibernate 4 le UserTransaction est récupéré automatiquement depuis l’adresse JNDI java:jboss/TransactionManager.

Cependant si un EJB crée un nouveau Thread (new Thread()) et que ce dernier a besoin d’exploiter ce UserTransaction cela provoque une exception indiquant que le UserTransaction ne peut pas être trouvé. La faute en est à la classe org.hibernate.service.jta.platform.internalJBossAppServerJtaPlatform qui récupère le UserTransaction avec un nom JNDI qui correspond aux anciennes version de JBoss : java:comp/UserTransaction.

Ainsi pour résoudre ce problème il suffit de créer une nouvelle classe qui étend la classe JBossAppServerJtaPlatform qui, pour Hibernate 4, doit être configurée par le fichier persistence.xml et par la propriété hibernate.transaction.jta.platform .

public class MyJBossAppServerJtaPlatform extends JBossAppServerJtaPlatform {

	public static final String UT_NAME = "java:jboss/UserTransaction";

	@Override
	protected UserTransaction locateUserTransaction() {
		return (UserTransaction) jndiService().locate(UT_NAME);
	}
}

Cette nouvelle classe retrouve le UserTransaction avec le bon nom JNDI. Pour l’exploiter il suffit juste de change la propriété hibernate.transaction.manager_lookup_class (toujours dans le fichier persistence.xml) :

<property name= »hibernate.transaction.jta.platform » value= »my.package.MyJBossAppServerJtaPlatform » />