Posts

  • Ceylon + jOOQ = <3

    So you want to try Ceylon on a new side project, but don’t feel like writing yet another boring command line application? In this article, I will show you how to set up a new Ceylon application that uses jOOQ seamlessly. We will see how easy it is to use Ceylon’s Java interoperability, and how easy it is to customize jOOQ and adapt it to Ceylon’s conventions. The sample application will use an existing database.

    (read more)

  • Monitorer ses logs avec Logstash

    Lundi matin, aux environs de 9 h 30. Vous arrivez un peu en retard, mais bon c’était pour la bonne cause, il fallait impérativement finir de merger ces pull requests sur votre projet perso et vous en avez eu jusqu’à 3h du mat’. Même pas le temps d’aller prendre une boisson chaude pour se réveiller, le téléphone sonne : une anomalie ultra-bloquante-qui-va-mettre-en-péril-le-projet-c’est-pas-possible vient d’être ouverte, c’est la catastrophe bla bla bla… Classique, me direz-vous, mais comment trouver facilement l’origine du problème ?

    (lire la suite)

  • Un switch annoté en Java

    Un billet sur le blog de l’Agilitateur répondant à un billet sur le blog d’Excilys concernant les switchs en java me donne envie de parler un peu de codage en Java. Cet article est une variante du code C# proposé par Oaz en Java.

    Je vais vous épargner toute la discussion sur le switch c'est bien / c'est nul, il y a déjà les commentaires enflammés sur le blog d'Excilys pour ça.

    Passons directement au code : de la même manière qu'Oaz l'a fait, je dispose d'une interface Function qui définit le contrat que vont suivre les différents cas du switch. Pour que l'on sache que les différentes sous-classes sont bien des cases, on les annote avec un @Case créé pour l'occasion :

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface Case {
        String name();
    }
    On demande au compilateur de garder cette annotation au runtime (pour faire du scanning dynamique), et on précise qu'elle peut s'appliquer à un type (classe ou interface).

    On peut alors utiliser l'annotation de cette façon :

    public interface Function {
        void doYourJob();
    }
    
    @Case(name="bar")
    public class BarFunction implements Function {
    
        public void doYourJob() {
            System.out.println("On va manger... des chips ! T'entends ? DES CHIPS !!");
        }
    }
    
    @Case(name="foo")
    public class FooFunction implements Function {
    
        public void doYourJob() {
            System.out.println("Hi, my name is foo!");
        }
    }
    
    @Case(name="othercase")
    public class OtherCase {
    
        public void doYourJob() {
            System.out.println("I'm not gonna be called :-(");
        }
    }

    Reste maintenant le plus dur à faire : scanner un package à la recherche de classes annotées @Case et qui héritent de notre classe de base (ici Function, mais on va essayer de rester génériques).

    public class Switch<T> {
    
        private String packageName;
        private Map<String, T> cases;
        private Class<T> parentType;
        
        public Switch(String packageName, Class<T> parentType) {
            this.packageName = packageName;
            this.parentType = parentType;
            scanCases();
        }
    
        private void scanCases() {
            cases = new HashMap<String, T>();
    
            try {
                for (Class clazz : getClasses(packageName)) {
                    if (clazz.isAnnotationPresent(Case.class) && isA(clazz, parentType)) {
                        Case theCase = (Case) clazz.getAnnotation(Case.class);
                        cases.put(theCase.name(), (T) clazz.newInstance());
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            System.out.println("Scanned " + cases.size() + " cases.");
        }
    
        private boolean isA(Class clazz, Class<T> class1) {
            if (clazz.getSuperclass() == class1) {
                return true;
            }
            for (Class intf : clazz.getInterfaces()) {
                if (intf == class1) {
                    return true;
                }
            }
            return false;
        }
    
        public T on(String caseName) throws IllegalArgumentException {
            if (cases.containsKey(caseName)) {
                return cases.get(caseName);
            } else {
                throw new IllegalArgumentException("The case " + caseName + " does not exist");
            }
        }
    
        private List<Class> getClasses(String pckgname) { ... }
    J'ai fait appel au generic type introduit en Java5, cela permet d'avoir un switch acceptant n'importe quelle classe de base qu'implémentent les différents cas. J'ai été obligé de repasser cette information dans le constructeur car en Java il n'est pas possible de faire un T.class dans la phase de scan des classes (pour savoir si la classe scannée étend ou implémente T). À la place, on a donc un Class en second paramètre.

    La méthode scanCases va parcourir toutes les classes d'un package (grâce à la méthode getClasses() honteusement trouvée sur le forum de Sun Oracle). Pour chaque classe, on regarde si elle est annotée @Case et si elle étend ou implémente T. Si c'est le cas, on en ajoute une instance dans une Map avec le nom du cas comme clé.

    Dernière étape : la méthode on() qui renvoie l'instance associée à un cas ou jette une exception s'il n'existe pas.

    La glue entre tout ça, c'est un main assez simple :

    public class Main {
    
        public static void main(String[] args) {
            Switch<Function> swi = new Switch<Function>("com.jansen.annotatedswitch.demo", Function.class);
            
            swi.on("foo").doYourJob();
            swi.on("bar").doYourJob();
            swi.on("othercase").doYourJob();
        }
    
    }
    Notez le dernier appel qui va planter : j'ai bien une classe annotée @Case(name="othercase") mais elle n'implémente pas Function (une ruse de sioux pour égarer le lecteur peu attentif ;-) ).

    Conclusion : c'est aussi possible en Java, avec un peu de gymnastique intellectuelle pour arriver à faire ce qu'on souhaite avec les generics :P

    (lire la suite)

  • Le logger de la mort

    Parfois, au cours d'une mission chez un client, on tombe sur du code pas testé, et pas toujours prévu pour être testé non plus. Je vais présenter un cas sur lequel je suis tombé récemment, issu d'un assez gros projet Java EE en cours de développement.

    Une classe de services X effectue une certain nombre de traitements. En cas d’erreur, au lieu de jeter une exception métier, un code d’erreur est loggué. Impossible d’utiliser un @Test(expected=...), donc. Fort heureusement, le champ logger dans X est une interface, potentiellement mockable :

    interface Ilogger {
      public abstract void logError(final Key key, final Object... messages);
      // blabla
    }

    Dans tous les cas où nous sommes intéressés, la méthode logError est appelée avec un paramètre Key représentant le code d’erreur. Malheureusement, le logger n’est pas injecté dans un setter, et il est privé. On aimerait tout de même vérifier que l’erreur est bien logguée sous certaines conditions. Solution, un peu de réflexion pour forcer le champ logger à une instance que l’on contrôle :

    public class MockLogger implements ILogger {
      private List<Key> loggedKeys;
    
      public MockLogger() {
        super();
        loggedKeys = new ArrayList&lt;Key&gt;();
      }
    
      public void logError(Key key, Object... messages) {
        loggedKeys.add(key);
      }
    
      public boolean wasLogged(Key key) {
        return loggedKeys.contains(key);
      }
    
      public void resetLogCalls() {
        loggedKeys.clear();
      }
    }

    La méthode resetLogCalls permet d’utiliser la même instance du logger sur plusieurs tests, il faut l’appeler dans une méthode annotée @After.

    Il faut maintenant injecter notre propre logger dans le service testé :

    Field f = serviceUnderTest.getClass().getDeclaredField("logger");
    f.setAccessible(true);
    f.set(serviceUnderTest, myLoggerInstance);
    f.setAccessible(false);

    Et voila, nous pouvons maintenant faire des assertTrue utilisant la méthode wasLogged et la Key nous intéressant ! Notez l’appel à Field#setAccessible(boolean) pour contourner le caractère private du champ logger (que l’on restaure par la suite).

    Note : si le logger n’avait pas utilisé d’interface, nous aurions pu nous en sortir en créant une classe fille redéfinissant toutes les méthodes sans appels à super().

    (lire la suite)

  • Contrôler la bande passante de sa machine linux

    Le haut débit c'est bien, mais des fois on a quand même un côté nostalgique du 56k, soit parce qu'on a envie d'embêter les visiteurs de son serveur, soit parce que pour débugger une application (par exemple) on a envie que les données arrivent lentement. Sous linux, il existe la commande tc, pour traffic control, qui permet de contrôler ses connexions réseaux, notamment leur débit...

    J'ai donc trouvé un script tout fait permettant de limiter la bande passante d'une interface réseau, en upload ou en download.

    #!/bin/bash
    #
    #  tc uses the following units when passed as a parameter.
    #  kbps: Kilobytes per second 
    #  mbps: Megabytes per second
    #  kbit: Kilobits per second
    #  mbit: Megabits per second
    #  bps: Bytes per second 
    #       Amounts of data can be specified in:
    #       kb or k: Kilobytes
    #       mb or m: Megabytes
    #       mbit: Megabits
    #       kbit: Kilobits
    #  To get the byte figure from bits, divide the number by 8 bit
    #
    
    #
    # Name of the traffic control command.
    TC=/sbin/tc
    
    # The network interface we're planning on limiting bandwidth.
    IF=eth0             # Interface
    
    # Download limit (in mega bits)
    DNLD=1mbit          # DOWNLOAD Limit
    
    # Upload limit (in mega bits)
    UPLD=1mbit          # UPLOAD Limit
    
    # IP address of the machine we are controlling
    IP=216.3.128.12     # Host IP
    
    # Filter options for limiting the intended interface.
    U32="$TC filter add dev $IF protocol ip parent 1:0 prio 1 u32"
    
    start() {
    
    # We'll use Hierarchical Token Bucket (HTB) to shape bandwidth.
    # For detailed configuration options, please consult Linux man
    # page.
    
        $TC qdisc add dev $IF root handle 1: htb default 30
        $TC class add dev $IF parent 1: classid 1:1 htb rate $DNLD
        $TC class add dev $IF parent 1: classid 1:2 htb rate $UPLD
        $U32 match ip dst $IP/32 flowid 1:1
        $U32 match ip src $IP/32 flowid 1:2
    
    # The first line creates the root qdisc, and the next two lines
    # create two child qdisc that are to be used to shape download 
    # and upload bandwidth.
    #
    # The 4th and 5th line creates the filter to match the interface.
    # The 'dst' IP address is used to limit download speed, and the 
    # 'src' IP address is used to limit upload speed.
    
    }
    
    stop() {
    
    # Stop the bandwidth shaping.
        $TC qdisc del dev $IF root
    
    }
    
    restart() {
    
    # Self-explanatory.
        stop
        sleep 1
        start
    
    }
    
    show() {
    
    # Display status of traffic control status.
        $TC -s qdisc ls dev $IF
    
    }
    
    case "$1" in
    
      start)
    
        echo -n "Starting bandwidth shaping: "
        start
        echo "done"
        ;;
    
      stop)
    
        echo -n "Stopping bandwidth shaping: "
        stop
        echo "done"
        ;;
    
      restart)
    
        echo -n "Restarting bandwidth shaping: "
        restart
        echo "done"
        ;;
    
      show)
    
        echo "Bandwidth shaping status for $IF:"
        show
        echo ""
        ;;
    
      *)
    
        pwd=$(pwd)
        echo "Usage: tc.bash {start|stop|restart|show}"
        ;;
    
    esac
    
    exit 0
    (lire la suite)

  • Pourquoi Netbeans c'est bien

    C'est un fait avéré, Netbeans c'est mieux qu'Eclipse. (comment ça, un troll tout poilu ?). Pas plus tard qu'il y a une minute, j'en ai refait l'heureuse expérience. Agacé de voir une série de getters/setters dans ma classe, qui sont triviaux à comprendre mais qui prennent de la place, j'ai voulu utiliser l'editor folding, propre à Netbeans (je crois, du moins).

    (lire la suite)

  • Récupérer de la musique depuis Deezer

    Même si je ne suis pas trop fan de Deezer pour diverses raisons (interface Flash trop lourde, beaucoup trop de pub, ...), je suis quand même bien content de pouvoir y rechercher une musique que j'ai en tête et que je peux récupérer pour la réécouter par la suite...

    En effet, il est de notoriété publique que ce système met en cache la chason en cours d'écoute et la stocke dans un fichier flv nommé /tmp/Flash* sous linux.

    À partir de là, il est facile de créer un script shell qui va se charger de récupérer ce ficher, puis d'en extraire la musique en MP3 et de la sauvegarder dans notre dossier de musique favori. Top du top, le script que je vous propose par la suite permet également d'éditer les tags ID3 et de renommer le fichier en conséquence, puis ajoute le morceau à la playlist Amarok en cours :D

    Voici la bête :

    #!/bin/bash
    
    DEST=/home/user/Musique
    
    cd $DEST
    
    files=`ls /tmp/Flash* | cut -d '/' -f3`
    
    for file in $files
    do
            ffmpeg -i "/tmp/$file" -acodec copy -f mp3 "$DEST/$file.mp3"
            id3ren -quick -notrack -template "%a - %s.mp3" "$DEST/$file.mp3"
            dcop amarok playlist addMedia "$DEST/$file"
    done
    (lire la suite)

  • Security holes in symfony apps

    symfony 1.2 was just released, and came along with a brand new advent calendar. The first day mentions two ways to set up your web server to host a symfony project. The ugly way seems to be very... ugly and crazy, putting your app directly in your webserver's root folder allows anyone to access all the files. But this security hole can be easily found in 'real' apps, as well as a few others...

    This article will try to enumerate some of the mistakes that are often done when a symfony application is put in production environment.

    Development environment is still present

    These files, ending with _dev.php, are very useful when you are coding: they provide a lot of information on the configuration, the SQL queries that were executed, your PHP version etc. But on the other hand, they should never be accessible to your visitors (I mean, bad ones, you know, those who type in the darkness). They can show your kernel version, the symfony plugins which are installed, the escaping method you are using (or not using...), etc., which can lead to other vulnerabilities.

    The project root is the webserver's root folder

    This can also give a lot of information to an attacker, especially your database connection settings. Just search for 'web/frontend.php' in your favorite search engine, and you will have a bunch of apps which are potentially in this case. Then you just have to browse config/databases.yml and you get what you want. Just consider that if the application is unsecure, the webserver which is running it is also unsecure, and you can have a remote access to MySQL on port 3306, which will show you a lot of interesting things...

    The backend is not protected

    Harder to find, but not impossible. Even if this page is not indexed by search engines, it's quite easy to see if a web app is powered by symfony, and the majority of these web apps have a backend which is called 'backend' (if not, just try 'admin' :)). So it's very important to restrict access to this backend, as it can contain sensitive information (user accounts, etc.). Imagine that you didn't make a recent backup of your database, and that someone deletes all its content...

    How to fix it?

    It's not difficult to avoid these problems. In fact, the solutions are very well explained in the official symfony book, but nonetheless they are often not applied.

    • Check that *_dev.php files are not uploaded (see the rsync_excludes.txt file for example)
    • If you can, put the whole project (except the web/ folder) outside the web document root
    • Protect your backend with sfGuard, or at least a htaccess/htpasswd file :) (and do not user admin/admin or something like this)

    (read more)

subscribe via RSS