Blog Zenika

#CodeTheWorld

Architecture

HawtIO, écrire un plugin

Après avoir découvert dans un premier article les fonctionnalités de HawtIO, nous allons à présent voir comment développer son propre plugin.

L’exemple qui illustre cet article a pour objectif d’afficher dans un écran dédié de la console HawtIO, le contenu du registre JNDI d’un serveur d’application. Le code source de l’application est disponible dans le GitHub Zenika

Côté backend

Créer un MXBean

Pour créer un MBean JMX, il n’est nul besoin d’utiliser HawtIO, mais l’outil propose une classe de base MBeanSupport assez pratique. Elle se charge de l’enregistrement dans le serveur JMX. On écrira donc quelque chose du genre:

/** Interface */
@MXBean
public interface JndiFacadeMXBean {
	...
}
/** Implémentation */
public class JndiFacade extends MBeanSupport implements JndiFacadeMXBean {
	...
}

Pour rappel, apparus avec JMX 2/Java 6, les MXBeans sont une extension et une simplification des MBeans JMX. Ils limitent les types autorisés dans la valeurs de retour et les paramètres mais permettent tout de même d’échanger des objets Java simples et augmentent les chances de compatibilité des applications clientes.
Pour en savoir plus:

Apache Aries Blueprint

Blueprint est une spécification d’un framework d’injection de dépendances adaptée aux conteneurs OSGi. Apache Aries est une implémentation de Blueprint ainsi qu’un ensemble de composants de base (JTA, JPA, JNDI…) pour OSGI et Blueprint. En clair, c’est une sorte de Spring, en différent; Blueprint est par exemple utilisé dans Apache ServiceMix. HawtIO peut s’appuyer sur Blueprint, même si ce n’est pas une obligation.
Pour découvrir Blueprint:

Comme Spring, Blueprint se configure avec des fichiers XML (la syntaxe est quasi identique) et gère le cycle de vie des objets:

<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
		xmlns:ext="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.2.0">
	<bean id="javaJndiFacade" class="io.hawt.jndi.JndiFacade"
			init-method="init" destroy-method="destroy" scope="singleton">
		<property name="contextName" value="java:"/>
	</bean>
</blueprint>

La méthode init se charge d’enregistrer le MBean dans la registre JMX, et inversement avec la méthode destroy

La découverte de plugins

Côté backend, BluePrint permet la modularité d’HawtIO. Au démarrage de l’application HawtIO, BluePrint parcourt les Jars présents dans le classpath à la recherche de fichiers OSGI-INF/blueprint/blueprint.xml, puis instancie et configure les beans (des MXBean par exemple) comme tout conteneur d’injection de dépendances qui se respecte. En ajoutant un Jar sur le classpath d’HawtIO, on peut donc faire apparaître des MBeans dans le registre JMX.
Coté frontend, pour découvrir dynamiquement les plugins additionnels, HawtIO recherche dans le registre JMX, les MBeans de type plugin dans le domaine hawtio. Pour cette raison, on déclare un second MBean dans Blueprint comme précédemment, à ceci près que l’implémentation est fournie par HawtIO cette fois :

<bean id="jndiPlugin" class="io.hawt.web.plugin.HawtioPlugin"
			init-method="init" destroy-method="destroy">
		<property name="name" value="jndi"/>
		<property name="context" value="/hawtio"/>
		<property name="scripts" value="app/jndi/js/jndiPlugin.js,app/jndi/js/jndiController.js"/>
	</bean>

On obtient ainsi un MBean nommé hawtio:type=plugin,name=jndi. La propriété scripts donne la liste de fichiers JavaScript que devra charger dynamiquement le navigateur.

Côté frontend

C’est de l’AngularJS pur jus: je ne détaillerai guère l’utilisation de ce framework.

Initialisation du plugin

On créé un module AngularJS dans lequel on déclare des routes. Lorsque le module démarre (fonction run), on inscrit dans des registres:
– la documentation (addUserDoc),
– les onglets de premier niveau (topLevelTabs),
– les onglets dans la vue JMX (subLevelTabs
– et le module lui même (addModule).
User doc, Top level tabs, Layout
Sub level tabs
Au niveau du code, ça donne:

angular.module('Jndi', ['hawtioCore'])
		.config(function($routeProvider) {
			$routeProvider
					.when('/jndi', {templateUrl: 'app/jndi/html/jndi.html'})
					.when('/jndi/:name', {templateUrl: 'app/jndi/html/jndi.html'});
		})
		.run(function(workspace, viewRegistry, helpRegistry) {
			viewRegistry["jndi"] = "app/jndi/html/layoutJndiTabs.html";
			// Documentation
			helpRegistry.addUserDoc("jndi", 'app/jndi/doc/help.md', function() {
				return workspace.treeContainsDomainAndProperties('hawtio', {type: 'JndiFacade'});
			});
			// Onglet premier niveau
			workspace.topLevelTabs.push({
				id: "jndi",
				content: "JNDI",
				title: "Browse JNDI registry",
				isValid: function(workspace) {
					return workspace.treeContainsDomainAndProperties('hawtio', {type: 'JndiFacade'});
				},
				href: function() {
					return "#/jndi";
				}
			});
			// Onglet niveau JMX
			workspace.subLevelTabs.push({
				content: '<i class="icon-list-alt"></i> Java:',
				title: "Java: Context",
				isValid: function(workspace) {
					return workspace.hasDomainAndProperties('hawtio', {type: 'JndiFacade', name: "java"});
				},
				href: function() {
					return "#/jndi/java";
				}
			});
		});
hawtioPluginLoader.addModule('Jndi');

Vous remarquerez au passage que les onglets apparaissent ou pas en fonction de la présence ou pas d’un MBean donné: workspace.hasDomainAndProperties. HawtIO va assez loin dans la dynamicité: par exemple, l’arrêt d’une application provoque le dés-enregistrement de MBeans donnés côté backend, aussitôt les onglets associés vont disparaître dans le frontend.

Layout

Dans l’initialisation ci-dessus, on a déclaré dans le viewRegistry un layout, celui-ci donne la mise en page globale de l’écran.

<ul class="nav nav-tabs" ng-controller="Core.NavBarController">
	<!-- Onglet niveau layout -->
	<li ng-class='{active : isActive("#/jndi/java")}'>
		<a ng-href="{{link('#/jndi/java')}}">Java:</a>
	</li>
</ul>
<div class="row-fluid">
	<div ng-view></div>
</div>

Contrairement aux autres, les onglets du niveau layout sont décrits en HTML.

Vue, Contrôleur et Documentation

Ici aussi c’est de l’AngularJS classique, je vous épargnerai donc les détails:

Jndi.JndiController = function($scope, $routeParams, jolokia) {
	$scope.context = {id: $routeParams.name};
}
<div ng-controller="Jndi.JndiController">
	{{context.id}}
</div>

Dans le contrôleur, on utilise l’API JavaScript Jolokia pour aller chercher des informations sur le backend.

jolokia.getAttribute("hawtio:type=JndiFacade,name=java","ContextName",{
		method:"POST",
		success:function(response) {
			$scope.context.name=response;
			$scope.$apply();
		}
	});

Vous noterez, que comme Jolokia JS ne s’appuie pas sur le $http d’Angular mais sur JQuery, on est contraint d’invoquer $apply dans les callbacks pour que la vue soit mise à jour.
En ce qui concerne l’aide en ligne du plugin, c’est un simple fichier écrit en MarkDown.

Conclusion

Le développement de plugins HawtIO n’est pas sorcier, l’outil est vraiment conçu avec l’extensibilité en ligne de mire. On regrettera cependant que la documentation soit pauvre (pas JSDoc pour l’instant), il faudra donc disséquer un peu le code source.
Quelques pointeurs:

Auteur/Autrice

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.