fireFooXXX

Il faut croire que les billets s’attirent entre eux :-).

Au détour d’une recherche sur Java, voilà-t-y po que je tombe sur une référence à mon navigateur préféré.

PS pour ceux qui ne suivent pas : / protected avec firefox. :D

Rajouter une interface à une classe pendant l'exécution

Je viens à l’instant de découvrir enfin les joies de l’ASM (j’ai bien dit ASM ! :-p). Je me suis basé pour ce faire sur un exemple d’un de mes profs cette année, Rémi Forax pour l’ASM et sur ce billet pour mieux comprendre le principe de l’agent java (cf. plus loin). Comme c’est encore frais, j’en profite pour en faire un petit article qui sera profitable, je pense, à d’autres et à moi pour plus tard ^^.

Cet outil permet en Java de rajouter du code à une classe pendant l’exécution. C’est très impressionnant. Imaginons qu’on veuille faire quelque chose que le compilateur ne permet pas. Je veux par exemple renvoyer une classe d’un type différent, mais la même cependant, selon des paramètres donnés, je n’ai pas d’autres choix que d’écrire les deux classes différentes.

Pas avec ASM, justement.

Je vais ici expliquer avec un exemple le plus simple possible comment rajouter une interface à une classe pendant l’exécution. Pour faire cela, nous aurons besoin de trois choses (en plus de javac et java, évidemment) :

Pensez aussi à vos petits doigts, ça peut servir.

Nous allons écrire une classe AddInterfaceClassTransformer qui hérite de ClassFileTransformer. C’est cette classe qui va s’occuper de modifier le bytecode de la classe qu’on veut changer au moment de son chargement.

Comment ça marche ?

  1. On utilise un pattern Visitor pour en quelque sorte tager les classes qu’on veut modifier ;
  2. On parcourt la classe pour réécrire le code qu’on souhaite en fonction des tags par exemple.

En fait, on va indiquer à la VM qu’elle doit utiliser un “java agent” avant de charger les classes. C’est ce principe qui va nous permettre de modifier le code si on le souhaite.

La classe de test

On écrit cette toute petite classe qui “implémente” par exemple EventListener, on lui fait implémenter quelque chose pour bien mettre ensuite en évidence qu’il y en a bien une de plus :

public class MyClassOfMe implements EventListener
{
}

Le code de test

La classe suivante va nous permettre d’afficher par reflection java les interfaces de MyClassOfMe :

public class MonTest
{
   public static void main(String[] args)
   {
       System.out.println("Liste des interfaces de "+MyClassOfMe.class+" : ");
       for (Class interfaze : MyClassOfMe.class.getInterfaces())
       {
           System.out.println(interfaze);
       }
       System.out.println("Fin");
   }
}

Si on lance cette classe après compilation, on obtient le résultat suivant :

$ java fr/umlv/bmathus/MonTest
Liste des interfaces de class fr.umlv.bmathus.MyClassOfMe :
interface java.util.EventListener
Fin

Rien d’extraordinaire, on a affiché là ce qui est actuellement compilé et qu’on a sous les yeux. Mais encore fallait-il vérifier que ça fonctionne :-).

Maintenant, nous voudrions donc comme je l’ai dit que cette classe affiche en fait une ligne de plus indiquant qu’elle implémente une interface supplémentaire. J’ai choisi d’ajouter Serializable.

Le transformer

Écrivons donc le ClassFileTransformer qui va nous permettre de faire ça :

public class AddInterfaceClassTransformer implements ClassFileTransformer
{
   public byte[] transform(ClassLoader loader, String name, Class clazz,
           ProtectionDomain domain, byte[] code)
           throws IllegalClassFormatException
   {
       // if it's not the first load or a bootstrap class
       if (loader == null || clazz != null)
           return null;

       System.out.println("[Loading " + name + "]");

       ClassReader reader = new ClassReader(code);

       // On "tag" le code à modifier
       final MyVisitor myVisitor = new MyVisitor();

       // On fournit le visitor au reader, on ne s'occupe pas des méthodes de
       // debug (skipDebug=true)
       reader.accept(myVisitor, true);

       ClassWriter writer = new ClassWriter(false);

       MyRewriteClassAdapter rewriteClassAdapter = new MyRewriteClassAdapter(
               writer, myVisitor.mustAddInterface);
       reader.accept(rewriteClassAdapter, true);

       byte[] remoteCode = writer.toByteArray();
       return remoteCode;
   }

Ici, on définit en tant que classe interne le visitor qui va s’occuper de marquer les classes qu’on souhaite modifier.

   private class MyVisitor implements ClassVisitor
   {
       public MyVisitor()
       {
       }

       public void visit(int version, int access, String name,
               String superName, String[] interfaces, String sourceFile)
       {
           System.out.println("Nom de la classe analysée = " + name);

Je n’arrive pas utiliser getInternalName(MyClassOfMe.class) et que ça fonctionne encore ici, donc je mets la chaîne en dur. Si quelqu’un a une explication, je suis preneur.

           if ("fr/umlv/bmathus/MyClassOfMe".equals(name))
           {
               mustAddInterface = true;
           }
       }

       public void visitInnerClass(String _arg0, String _arg1, String _arg2,
               int _arg3)
       {
       }

       public void visitField(int _arg0, String _arg1, String _arg2,
               Object _arg3, Attribute _arg4)
       {
       }

       public CodeVisitor visitMethod(int _arg0, String _arg1, String _arg2,
               String[] _arg3, Attribute _arg4)
       { 
           return null;
       }

       public void visitAttribute(Attribute _arg0)
       {
       }

       public void visitEnd()
       {
       }

       boolean mustAddInterface;
   }

   private static class MyRewriteClassAdapter extends ClassAdapter
   {

       private boolean mustAddInterface;

       public MyRewriteClassAdapter(ClassWriter writer,
               boolean mustAddInterface)
       {
           super(writer);
           this.mustAddInterface = mustAddInterface;
       }

Ici, assez simplement, on récupère la liste des interfaces déjà présentes. Et on y ajoute Serializable, l’utilisation d’un Set nous permet de ne pas nous occuper de savoir si l’implémentation est déjà présente. En effet, pour ceux qui l’ignorent, un Set est un ensemble. Or, au sens mathématique, ça veut dire qu’on ne peut pas avoir deux fois le même objet. Donc, ici, l’ajout ne se fera que si l’interface n’est pas encore présente.

       public void visit(int version, int access, String name,
               String superName, String[] interfaces, String sourceFile)
       {
           HashSet<String> set;
           if (interfaces == null)
               set = new HashSet<String>();
           else
               set = new HashSet<String>(Arrays.asList(interfaces));

           if (mustAddInterface)
           {
               set.add(I_SERIALIZABLE);
           }
           super.visit(version, access, name, superName, set
                   .toArray(new String[set.size()]), sourceFile);
       }

       private static String I_SERIALIZABLE = getInternalName(Serializable.class);
   }

Là, nous donnons le point d’entrée du java agent. De la même façon que la fonction main est le point d’entrée d’une classe normale, la méthode premain est celui recherché par la vm lorsqu’on lui indique qu’une classe est un agent java.

On lui passe ici une instance de notre transformer.

   public static void premain(String agentArgs, Instrumentation inst)
   {
       inst.addTransformer(new AddInterfaceClassTransformer());
   }

   private static String getInternalName(Class< ? > clazz)
   {
       return clazz.getName().replace('.', '/');
   }

   private static Method defineClass;
   static
   {
       try
       {
           defineClass = ClassLoader.class.getDeclaredMethod("defineClass",
                   String.class, byte[].class, int.class, int.class,
                   ProtectionDomain.class);
       }
       catch (NoSuchMethodException e)
       {
           throw new AssertionError(e);
       }
       defineClass.setAccessible(true);
   }

   private static HashSet<String> objectMethods = new HashSet<String>();
   static
   {
       objectMethods.add("equals" + "(Ljava/lang/Object;)Z");
       objectMethods.add("toString" + "()Ljava/lang/String;");
       objectMethods.add("hashCode" + "()I");
   }
}

Création du java agent

Comme je l’ai indiqué au début, cette classe doit être utilisée par la vm avant de charger normalement les classes. Nous allons donc packager notre classe dans un jar pour l’utiliser ensuite en tant qu’agent java.

Le manifest

Le manifest doit contenir une ligne indiquant la Premain-Class à utiliser, il aura donc la forme suivante :

Premain-Class: fr.umlv.bmathus.AddInterfaceClassTransformer

À présent, nous pouvons créer notre jar :

$ jar cvfm transformer.jar META-INF/MANIFEST.MF fr

C’est presque fini, il ne reste plus qu’à lancer la même classe qu’au début en précisant à la vm qu’elle doit utiliser notre java agent transformer.jar. Bien entendu, il faut rajouter au classpath le jar d’asm :

java -javaagent:transformer.jar -cp asm-1.5.3.jar:. fr/umlv/bmathus/MonTest

Et ça affiche :

[Loading fr/umlv/bmathus/MonTest]
Nom de la classe analysée = fr/umlv/bmathus/MonTest
[Loading fr/umlv/bmathus/MyClassOfMe]
Nom de la classe analysée = fr/umlv/bmathus/MyClassOfMe
Liste des interfaces de class fr.umlv.bmathus.MyClassOfMe :
interface java.io.Serializable
interface java.util.EventListener
Fin

Gagné ! Nous avons réussi à ajouter l’interface qu’on voulait à notre classe.

Conclusion

ASM est très puissant, je n’ai fait là qu’en découvrir un petit morceau. Ce type de traitement est applicable à n’importe quel attributs de votre classe, vous pouvez ainsi modifier méthodes, champs, etc. Je reviendrai certainement sur le sujet d’ici quelques temps si je trouve des compléments intéressants à ce billet.

Mon poing sur la gueule

Vous vous souvenez du billet où je parlais du fait qu’on essayait de faire passer les brevets logiciels lors d’un conseil de l’agriculture ?

Et bien, ils remettent ça ces connards ! Putain, mais dans quel monde je, nous vivons ? C’est ça qu’on appelle un processus démocratique ? Essayer de faire passer en force (et plusieurs fois!!!) des trucs aussi importants en les faisant valider par des gens qui ne connaissent pas les dossiers et qui n’en ont donc pas les compétences ?

Ahurissant, désarmant. Je ne comprends pas. Je suis dégoûté. Partagé entre l’envie de coller une grosse baffe aux instigateurs de cette manipulation malhonnête et celle de leur vomir dessus par dégoût.

Quelle honte.

Visual très basic

En direct de la partie [cartoons de
theserverside](http://www.theserverside.com/cartoons/TalesFromTheServerSide.tss)
The usual suspect.

Mon besoin du moment

Des journées plus longues… Une soixantaine d’heures par jour m’irait assez bien… Pas le temps de dormir… Pas le temps de sortir… Pas le temps de vous parler…

Quelle horreur…

Mapper une relation many-to-many avec Hibernate

Un peu de technique, soyons fou. Après cette nuit de coding party pour le projet de génie logiciel de mon école, je me dis que c’est sûrement un bon moment pour vous parler d’hibernate.

J’avais déjà un peu parlé d’hibernate ici pour ceux qui se demandent ce que c’est. En gros, c’est un outil qui permet de ne pas s’occuper de la sauvegarde de ses objets dans une base de données. On spécifie pour ce faire un mapping en XML indiquant les correspondances entre le modèle relationnel (les tables de la BD) et le modèle objet (les classes).

Je vais ici décrire comment effectuer un mapping many-to-many. Cet exemple est ciblé pour ce besoin, si vous ne connaissez pas ce dont je parle et que vous voulez en savoir plus, allez plutôt lire la documentation de référence. Je me suis initialement basé sur cet article pour me documenter et réaliser mes tests.

Mon objectif est de donner un exemple simplifié au maximum, mais complet. Par complet, j’entends donner le diagramme de classes et leurs relations, le mapping de toutes les classes et tester quelques trucs qui m’ont fait me poser des questions lorsque je cherchais.

J’ai prévu de faire ça sur différents sujets, je dirais que le prochain que je ferai sera conditionné à la fois par les demandes en ce sens et un peu par mon temps :-).

Les relations ?

On a une classe RCourse et une classe RStudent :

  • un cours peut contenir plusieurs étudiants ;
  • un étudiant peut assister à plusieurs cours.

Rappelons ici, qu’une relation de type n/n génère sur un schéma relationnel une table intermédiaire qui contient justement les associations RCourse/RStudent existantes. Nous décidons d’appeler cette table rstudent_rcourse.

Le code

Voilà les deux classes Java correspondantes :

public class RCourse
{
   int id;
   String name;
   Set<RStudent> students;
   //Écrivez ici tous les setters et getters 
    [...]
}
public class RStudent
{
    private int id;
    private String firstName;
    private String lastName;
   //Écrivez ici tous les setters et getters 
    [...]
}

Un conseil pour les getters et setters. Si vous utilisez un IDE comme eclipse, demandez lui de générer toutes les méthodes en protected. En effet, cette visibilité suffit à Hibernate pour accéder à l’objet persistant et vous permet de ne pas exposer forcément tous les setters publics sur votre objet. Un peu à la manière de la configuration d’un firewall : fermez tout, ne passez ensuite en public que les méthodes au fur et à mesure des besoins.

Le mapping

La partie un peu moins facile que le reste est la description du set , mais le fichier reste quand même assez simple :

Pour RCourse :

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN" 
                "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping>
  <class name="de.berlios.chronos.technique.hibernate.relations.RCourse" 
    table="rcourse">
    <id name="id">
      <generator class="native"/>
    </id>
    <property name="name" type="java.lang.String" />
    
    <set name="students" table="rstudent_rcourse" lazy="false">
      <key column="course_id"/>
      <many-to-many class="de.berlios.chronos.technique.hibernate.relations.RStudent" 
        column="student_id" outer-join="true"/>
    </set>
  </class>
</hibernate-mapping>

Pour RStudent :

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN" 
               "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping>
  <class name="de.berlios.chronos.technique.hibernate.relations.RStudent" 
    table="rstudent">
    <id name="id">
      <generator class="native"/>
    </id>
    <property name="firstName"/>
     <property name="lastName"/>
    
    <set name="courses" table="rstudent_rcourse" lazy="false">
      <key column="student_id"/>
      <many-to-many class="de.berlios.chronos.technique.hibernate.relations.RCourse" 
        column="course_id"/>
    </set>
  </class>
</hibernate-mapping>

Faisons remarquer l’attribut lazy (i.e. paresseux, oui, oui) de la balise set. Il conditionne le fait qu’Hibernate cherchera ou non à peupler directement les collections dépendantes à la récupération de l’objet (exemple : si on récupère un cours, si il doit aller chercher tous les étudiants de ce cours tout de suite), ou si il doit attendre l’utilisation de la collection en question pour le remplir.

Génération du SQL

Grâce à SchemaExport, il est possible de générer directement tout le SQL correctement au mapping ci-dessus. Vous pouvez vous inspirer de l’exemple de script donné ici si vous avez des soucis avec ça. Si vous insistez, je pourrais éventuellement envisager d’écrire quelques explications sur le sujet.

alter table rstudent_rcourse drop constraint FKDB6FCE57C276819F;
alter table rstudent_rcourse drop constraint FKDB6FCE57575ED7BF;
drop table rstudent_rcourse;
drop table rstudent;
drop table rcourse;
drop sequence hibernate_sequence;
create table rstudent_rcourse (
   course_id int4 not null,
   student_id int4 not null,
   primary key (student_id, course_id)
);
create table rstudent (
   id int4 not null,
   firstName varchar(255),
    lastName varchar(255),
   primary key (id)
);
create table rcourse (
   id int4 not null,
   name varchar(255),
   primary key (id)
);
alter table rstudent_rcourse add constraint FKDB6FCE57C276819F foreign key (student_id) references rstudent;
alter table rstudent_rcourse add constraint FKDB6FCE57575ED7BF foreign key (course_id) references rcourse;
create sequence hibernate_sequence;
Tester

Après avoir écrit le code, le mapping, généré le sql et créé les tables, on peut tester. Il est notamment intéressant de tester l’influence du paramètre lazy.

Conseil : sauvegardez des étudiants et un cours auquel ils assistent. Essayez ensuite de récupérer l’objet RCourse en mettant tour à tour le paramètre lazy à false ou true.

Positionnez l’affichage du code SQL (hibernate.show_sql=true) et faites quelques affichages entre la récupération de l’objet et le premier accès que vous faites à la collection de students. Selon le mode (fainéant ou non), vous remarquerez que le moment où la requête de jointure est effectuée diffère : au moment du Session.find() pour lazy=“false”, au premier accès à la collection pour lazy=“true”.

Conclusion

Bon, j’ai été un plus court que je n’aurais voulu, mais je suis très fatigué et je pense que des docs plus complètes existent déjà sur Internet. De toute façon, mon objectif résidait plus dans le fait de founir tout le code nécessaire aux tests au néophyte afin qu’il puisse constater simplement et rapidement le fonctionnement d’Hibernate.

Ça ne fera jamais qu'une de plus

Et hop, encore une faille de sécurité “extrêmement critique”… Via slashdot, on apprend en effet qu’une nouvelle faille IE6 sur un SP2 pourrait permettre à un programme, je cite :

  • d’écrire des fichiers sur votre disque ;
  • d’en supprimer.

Étonnant, non ?

Bon, j’ai failli arrêter là ce billet, mais je me dis que certains (il en reste encore ?) n’utilisent ou ne savent pas quelle autre solution existe. Pour surfer sur Internet, utilisez firefox.

Train pour Lille ou comment bien contrôler

Aujourd’hui, j’ai pris le train pour aller à Lille voir un copain à qui je l’ai promis depuis la terminale. 6 ans déjà :-/. Il était temps…

Bref, en arrivant gare du Nord, avant de monter dans le train, il y avait déjà une horde de contrôleur (une petite horde il est vrai, environ 6 ou 7, c’est à partir de combien une horde ? (o= ) qui attendait pour contrôler, donc, tous les voyageurs à l’entrée du quai.

Très bien, je montre mon billet, il le vérifie, me dit même que j’ai oublié de le composter (ah, la fatigue…), je corrige ceci et je passe.

Je m’installe donc dans le convoyeur raillique de viande humaine et je commence à travailler sur mon portable. Je découvre un réseau Wifi, je m’y connecte, ehehe un DHCP me donne une adresse et … trop tard, le train a démarré et je n’ai pas le temps de regarder si je pourrais avoir accès au net =).

Après quelques minutes, qui vois-je arriver ? LE CONTRÔLEUR…

Je réfrenne une envie sauvage de lui demander pourquoi on est contrôlé deux fois pour le même prix et s’il est payé au nombre de contrôles dans la journée, il REvérifie donc mon billet et passe son chemin.

Alors question à un éventuel connaisseur des mécaniques occultes internes à la SNCF : Est-ce que vous pouvez m’expliquer l’utilité de payer des gens à contrôler les voyageurs dans le train alors que ceux-ci n’ont déjà pu y rentrer que s’ils avaient été contrôlés ?

….. Et c’est nous qui payons …..

Ah ben ça va mieux maintenant

C’est mieux.

# free
            total       used       free     shared    buffers     cached
Mem:        775264     607272     167992          0      29268     303304
-/+ buffers/cache:     274700     500564
Swap:       491360          0     491360

Avec Gnome 2.8, Eclipse, OpenOffice.org, Firefox, Thunderbird, Apache, PostgreSQL et tous mes services habituels (serveur ssh, xinetd, etc.) lancés en même temps.

C’est un peu comme une bouffée d’air pur :-)

Mise à jour du 9 janvier : pour les gens comme Vincent qui ne comprennent pas ce qu’il y a au dessus. En gros, ma machine avait trop peu de RAM (cf. billet précédent), ce qui fait qu’elle devait beaucoup swapper (en gros aussi, utiliser le disque dur comme ram virtuelle, le problème c’est qu’un dd est beaucoup plus lent que de la vraie RAM). La commande free donne l’utilisation de la mémoire, ici on voit que j’ai maintenant 775Mo de RAM au lieu de 256 avant. On voit donc aussi en conséquence que la machine ne swappe plus (au lieu de 300Mo avant…), ce qui se ressent énormément au niveau de la réactivité du système lorsqu’il est chargé.

D’autres part, je vais rajouter ces quelques mots pour que google les référence au cas où des gens qui possèdent le même portable que moi chercheraient à savoir quelle RAM fonctionnerait avec leur ordinateur. Mon portable est un Presario X1000, plus précisément un X1005EA. C’est un centrino et le type de RAM qu’il utilise est de la DDR-SDRAM SoDimm PC2100. J’ai acheté une barette de 512Mo Corsair garantie à vie sur le site de LDLC, livré en quelques jours seulement, tout a fonctionné parfaitement.

Le navigateur obsolète

Bon, nous avons quelques jours pour que ce célèbre navigateur obsolète devienne premier sur google pour la requête “navigateur obsolète”.

Vincent et Denis ont commencé, je continue. Allez, faites passer le message pour que notre navigateur obsolète possède enfin la place qu’il mérite tant.

C’est vrai qu’un navigateur obsolète comme celui-ci, on n’en fait plus…

Mise à jour du 9 janvier : Ayè, c’est déjà gagné

Page 22 of 33 Older