Artisan Numérique

/développement/java/dbus/ Mise en oeuvre de DBUS sous Java

Mon besoin de base était de pouvoir utiliser l'API Java TuxDroid à partir de n'importe quelle application Linux. Par exemple dans le système de notification de KDE. Et en creusant un peu, les possibilités ne manquent pas. Il y a tout d'abord la très supportée mais aussi relativement lourde solution Corba utilisée par les applications Gnome. Nous avons aussi le très bon DCOP issue du monde KDE mais le portage Java semble quelque peu poussiéreux. Reste la relativement récente solution DBUS proposée par freedesktop.org et disposant d'un très bon "binding" java. Et force est d'avouer que cela fonctionne très bien.

DBUS en grosses mailles

DBus est un excellent système qui comme beaucoup dans le monde du libre ne brille pas par sa documentation et sa profusion d'exemples simples. Fort heureusement le portage Java de DBus même si lui-aussi sous documenté est livré avec une foule de tests unitaires qui fournissent une bonne base de départ. Et finalement, un petit serveur DBus fonctionnel en java m'a pris tout au plus 1/4 d'heure à mettre en œuvre.

DBUS, comme Corba, DCop, RMI, WebServices ou encore XmlRPC, est un système d'invocation et de notification à distance orienté objet. L'idée sous-jacente est celle d'un serveur qui va "publier" un certain nombre d'objets, qui exposent un certain nombre d'interfaces, qui contiennent un certain nombre de méthode. Il est alors possible, à distance, d'explorer ces objets, et d'en invoquer, à distance, les méthodes.

DBUS est conçu pour faire office de super bus de communication inter-processus standardisé par freeDesktop.org pour, à terme, être utilisé par toutes les applications Linux. Il peut s'agir d'un démon système (ex. BlueTooth qui notifie les arrivées de nouveaux périphériques) ou d'une application bureau (ex. l'activation de l'économiseur d'écran de Gnome). Chacune de ces applications publie leurs interfaces et peuvent ainsi communiquer entre elles. Techniquement rien n'empêche Gnome ou Kde d'utiliser DBUS en lieu et place de Corba/DCOP mais je doute que le travaild e ré-écriture que cela implique soit envisagé. Au mieux nous disposerons sûrement de points entre les trois architectures.

Installation

L'écriture du binding DBus/Java a été réalisée par Matthew Johnson.

Pour l'installer, le plus simple reste d'utiliser le paquet qui va bien, maintenant inclus dans toutes les distributions modernes.

urpmi dbus-java

Ensuite, il ne vous reste plus qu'à utiliser la librairie qui se trouve en /usr/share/java/dbus.jar.

Mise en œuvre

L'objectif de ce petit exemple est de créer un simplicime serveur DBus qui contient un seul objet (Main) et une seule méthode (Devinez... helloWorld ;-). Tout d'abord, nous devons définir notre interface (fichier DBusHelloWorldInterface.java):

import org.freedesktop.DBus;
import org.freedesktop.dbus.DBusInterface;
public interface DBusHelloWorldInterface extends DBusInterface
{
  @DBus.Description("Petit message de HelloWorld avec un nom en paramètre")
  public String helloWorld(String nom);
}

Rien de bien sorcier pour qui connais les annotations introduites en Java 1.5.

Nous allons maintenant écrire le code du serveur (fichier DbusHelloWorldServer.java):

import org.freedesktop.dbus.DBusConnection;
import org.freedesktop.dbus.exceptions.DBusException;
public class DBusHelloWorldServer implements DBusHelloWorldInterface
{
  private DBusConnection dbusConnection;
  public boolean isRemote()
  {
    return false;
  }
  private boolean stop=false;
  public String helloWorld(String name)
  {
    stop=true;
    return "Hello World : "+name;
  }
  public void start()
  {
    try
    {
      dbusConnection = DBusConnection.getConnection(DBusConnection.SESSION);
      dbusConnection.requestBusName("mon.premier.bus");
      dbusConnection.exportObject("/Main", this);
      while (!stop)
      {
         try {
         Thread.sleep(1000);
         } catch (Exception e) {}
      }
      dbusConnection.disconnect();
    }
    catch (DBusException e)
    {
      e.printStackTrace();
    }
  }
  public static void main(String[] args)
  {
    new DBusHelloWorldServer().start();
  }
}

Le programme est très simple. La méthode main() fabrique le serveur et exécute sa méthode start. Le serveur lui, ouvre une connexion de type SESSION sur le système DBUS, demande un droit sur le nom de bus mon.premier.bus et y publie son objet Main. Lorsque la méthode helloWorld est invoquée à distance, l'appel se termine dans son implémentation côté serveur qui a comme seuls traitement la mise à true de la variable (la boucle du serveur dans le main-thread va donc s'arrêter) et le renvoi d'un classique HelloWorld suivi du nom passé en paramètre.

Il nous faut maintenant de compiler et lancer le tout.

export CLASSPATH=/usr/share/java/dbus.jar:/usr/share/java/dbus-viewer.jar:/usr/share/java/dbus-bin.jar:/usr/share/java/hexdump.jar:.
javac DBusHelloWorldInterface.java
javac DBusHelloWorldServer.java
java DBusHelloWorldServer.java & // serveur lancé en tâche de fond...

Introspection

Une fois notre serveur lancé, rien ne se passe et c'est normal, nous allons vérifier maintenant que, pourtant, tout fonctionne. Une des fonctionnalités classique des systèmes comme DBUS (la même chose est faisable en RMI, DCOP, etc...) est de pouvoir introspecter un serveur pour en lister les objets, les interfaces et les méthodes. Cette opération est rendue réalisable ici par une application Viewer fournit avec le binding java. Il suffit donc de la lancer :

java org.freedesktop.dbus.viewer.DBusViewer

Une petite application Swing apparaît alors affichant tous les bus DBus publiés. Vous devez donc en trouver un qui se nomme mon.premier.bus qui publie un objet /Main. Sélectionnez cette ligne et cliquez sur Introspect. Là apparaît une autre fenêtre contenant dans un onglet la déclaration XML de l'interface contenant votre méthode helloWorld. L'autre onglet contient une version Java de cette interface. Tout est bien en place, nous pouvons donc maintenant quitter cette application pour lancer notre première commande DBUS.

Commande à distance du serveur

Pour cela, nous allons utiliser la commande dbus-send fournie en standard dans le paquet DBUS en lui passant les paramètres complets de notre commande :

dbus-send --print-reply --dest='mon.premier.bus' /Main mon.premier.bus.helloWorld string:'gaston'

Le premier paramètre indique à la commande qu'il lui faut afficher les résultats. Le second donne le nom de notre bus (mon.premier.bus) tel que définit dans DBusHelloWorldServer.Java. Le troisième indique le chemin relatif de l'objet que l'on chercher à attaquer (/Main). Le quatrième donne le chemin complet de la commande (nom du bus + "." + nom de la commande).

Enfin les derniers arguments sont les paramètres à passer à la commande. Ils se composent chacun (ici nous n'en avons qu'un) du type de donnée (ici String, mais ce pourrait être variant, int32, uint16, boolean en fonction de ce qui a été définit dans l'interface) suivi de la valeur du paramètre (entre quotes car il s'agit d'une chaîne).

Une fois validée, la commande devrait alors vous renvoyer la valeur :

string "Hello World : gaston"

Et notre serveur, c'est très logiquement, arrêté. Voilà, ça marche.

Conclusion

Je n'ai pas la prétention de vous donner ici autre chose qu'un avant goût de ce que permet DBUS et son binding Java. Mais j'espère que cela vous a donné des idées des possibles offerts par cette technologie. C'est une pièce importante à l'heure de l'intégration fine de Java et du bureau Unix.

A noter, un très bon outil pour explorer les services dbus : lsdbus.