Présentations
RCProject

Informations

Années 2014 - 2015
Catégorie Projet personnel
Langage Java

Présentation du projet

Le projet RCProject (Rolland Cyrille Project) est un projet personnel qui a pour objectif de simplifier le developpement de projets Java. Pour ce faire, RCProject propose :

  • Une configuration des classes et de leurs attributs par annotation
  • Une mise en oeuvre simplifiée des traces techniques
  • La conversion de chaines de caractères (String) vers n'importe quelle classe via l'implémentation d'une interface prévue à cet effet
  • La possibilité de mettre en place rapidement un proxy sur n'importe quelle classe Java du projet

L'idée d'origine de ce projet est de rassembler dans framework les éléments récurrents des projets Java.

Travail réalisé

  • Conception et developpement d'un module de conversion de chaine de caracèteres
  • Conception et developpement d'un module de configration
  • Conception et developpement d'un module de logs
  • Conception et developpement d'un module de gestion des proxy
  • Conception et developpement d'une classe "Tools" regroupant un ensemble de fonctionnalités fréquemment utilisées dans les projets Java

Exemples

Initialisation

Afin de pouvoir utiliser les fonctionnalités proposées par RCProject, il faut initialiser le framework. Pour cela, il suffit d'appeler une des méthodes statiques suivantes :

  • RCProject.init()
  • RCProject.init(String filePath)

La première méthode utilise le fichier de configuration par défaut  "configuration.properties" qui doit être placé dans le repertoire "config" du projet. Le fichier de configuration par défaut des loggers "log4j.properties" doit se situer dans le repertoire "config/log".

Remarque : Les fichiers "configuration.properties" et "log4j.properties" sont fournis avec RCProject. Dans chaque fichier l'ensemble des propriétés configurables est détailé. Pour utiliser l'une de ces dernières, il suffit de décommenter la propriété concernée et de la configurer comme expliqué dans les commentaires qui lui sont attachés.

La seconde méthode permet de spécifier l'emplacement du fichier de configuration.

Remarque : Depuis le fichier configuration il est possible de modifier l'emplacement du fichier de configuration des loggers.

Par exemple : initialisation dans la méthode "main" du porjet :

public static void main(String[] args) {
    try{
        RCProject.init();
    }catch(ConfigurationException e){
        e.printStackTrace();
    }
    ...

Configuration d'une classe

RCProject est conçu pour simplifier la configuration des attributs des classes.

En guise d'exemple, nous allons utiliser la classe "HelloWorld" qui permet l'affichage d'un message après avoir attendu un temps déterminé. Cette classe a été conçue de la manière suivante ;

  • Deux attributs :
    • "message" : Le message à afficher
    • "timeToWait" : Temps à attendre en millisecondes avant l'affichage du message
  • Deux constructeurs :
    • Par défaut : Le message afficher sera "Hello world" et le temps d'attente sera de 0 milliseconde
    • Avec en argument les valeurs associées aux attributs "message" et "timeToWait"
  • La méthode "writeMessage" qui affiche le message "message" après avoir attendu "timeToWait" millisecondes 

L'implémentation de la classe "HelloWorld" est la suivante :

public class HelloWorld {
    /*---------- Attributes ---------*/
    private Integer timeToWait;
    private String message;

    /*-------- Constructors ---------*/
    /**
     * Default constructor
     */
    public HelloWorld() {
        this(0,"Hello world");
    }

    /**
     * Constructor
     * @param timeToWait : time to wait before writing the message (ms)
     * @param message : The message
     */
    public HelloWorld(int timeToWait, String message) {
        super();
        this.timeToWait = (timeToWait>0) ? timeToWait : 0;
        this.message = (message==null) ? "" : message;
    }

    /*----------- Methods -----------*/
    /**
     * Method which displays 'message' after waiting 'timeToWait' milliseconds
     * @throws InterruptedException
     */
    public void writeMessage() throws InterruptedException{
        Thread.sleep(timeToWait);
        System.out.println(message);
    }
}

Nous allons maintenant permettre la configuration des attributs de cette classe. Pour cela, nous allons utiliser les annotations suivantes :

  • "ConfigurableClass" : Permet de rendre la classe configurable et de spécifier le préfix à utiliser pour chaque configuration
  • "ConfigurableAttribute" : Permet de rendre un attribut configurable et de spécifier :
    • Le nom de l'attribut dans la configuration
    • Si l'attribut est obligatoire (par défaut non)
    • Une valeur par défaut si l'attribut n'est pas obligatoire et n'est pas présent dans les fichiers de configurations

Remarque : Si un attribut est obligatoire mais n'est pas présent dans la configuration alors une "ConfigurationException" est levée.

Dans le cas de la classe "HelloWorld", nous allons mettre en place une configuration respectant les contraintes suivantes :

  • Les attributs configurables devront être préfixés par "helloworld"
  • L'attribut "timeToWait" sera nommé "timeToWait" dans la configuration, ne sera pas obligatoire et aura comme valeur par défaut "0"
  • L'attribut "message" sera nommé "message" dans la configuration et sera obligatoire

La mise en place de cette configuration pour la classe "HelloWorld" se fait ainsi :

@ConfigurableClass(prefix="helloworld")
public class HelloWorld {
    /*---------- Attributes ---------*/
    @ConfigurableAttribute( configName="timeToWait", defaultValue="0")
    private Integer timeToWait;

    @ConfigurableAttribute( configName="message", mandatory=true)
    private String message;
    ...

Une fois la configuration mise en place, il est possible de configurer les attributs de la classe "HelloWorld" dans le fichier de configuration du projet.

Par exemple :

# Time to wait
helloworld.timeToWait = 5000

# Message
helloworld.message = Configured hello world

Remarque : Par configuration il est possible d'importer d'autres fichiers de configuration. L'ensemble des configurations d'un projet peuvent donc être réparties dans plusieurs fichiers

Maintenant que notre configuration est en place, il suffit de créer une instance configurée. Pour cela il y a deux façons de proceder :

  • Création d'une instance configurée : Cette méthode consiste à créer une nouvelle instance préconfigurée via RCProject en utilisant la méthode statique "ClassFactory.getInstance(class)".
  • Configuration d'une instance existante : Cette méthode consiste à configurer une instance existante en utilisant la méthode statique "ClassFactory.configureAttribute(intance)". Cela signifie que les valeurs des champs configurables de l'instance seront écrasées par les valeurs de la configuration. 

Dans notre exemple nous allons créer une instance configurée :

public static void main(String[] args) {
    try{
        RCProject.init();
        
        HelloWorld configuredHelloWorld = ClassFactory.getInstance( HelloWorld.class );
        configuredHelloWorld.writeMessage();
        
    }catch(Exception e){
        e.printStackTrace();
    }
    ...

Remarque : Dans l'exemple précédent la méthode "ClassFactory.getInstance" est utilisée pour appeler le constructeur par défaut de la classe "HelloWorld". Cependant, il est possible d'appeler un autre constructeur en passant la liste de ses arguments en second argument de la méthode.

Gestion des logs

RCProject propose une méthode simple et rapide pour mettre en place des traces techniques.

Pour ajouter un logger sur une classe il suffit :

  • D'utiliser l'annotation "Logger" qui permet de spécifier le nom du logger dans le fichier de configuration.
  • Appeler une des méthodes statiques suivantes :
    • Log.debug
    • Log.error
    • Log.info
    • Log.trace
    • Log.warn

Par exemple, sur la méthode "writeMessage" de la classe "HelloWorld" :

@ConfigurableClass(prefix="helloworld")
@Logger(name="helloworld")
public class HelloWorld {
    ...

    /**
     * Method which displays 'message' after waiting 'timeToWait' milliseconds
     * @throws InterruptedException
     */
    public void writeMessage() throws InterruptedException{
        if(Log.isDebugEnabled())
            Log.debug("Writing message '"+ Tools.toString( message ) +"' after waiting ["+ Tools.toString( timeToWait ) +"] milliseconds");
        
        Thread.sleep(timeToWait);
        System.out.println(message);
    }

    ...

Remarque : la méthode "Tools.toString" permet de l'affichage d'une instance via sa méthode "toString()" tout en gérant le cas où cette dernière est "null". Si l'instance passée en argument est de type "Collection", "Map"ou tableau alors cette méthode affichera chaque élément contenu dans l'instance.

Dans l'exemple précédent nous avons spécifié que le nom du logger dans la configuration est "helloworld". Par défaut Log4J est utilisé pour gérer les traces. La configuration du nouveau logger aura donc la forme suivante :

## Helloworld logger
log4j.logger.helloworld = DEBUG, STDOUT

STDOUT étant un "appender" de type "console" fourni avec RCProject.

Remarque : Si l'annotation "Logger" n'est pas présente alors nom par défaut du logger est le nom complet de la classe (package + classe). Dans notre exemple, la classe "HelloWorld" se situe dans le package "rc.helloworld" le nom  par défaut du logger serait donc : "rc.helloworld.HelloWorld"

Conversion

L'objet StringConverter

Lors de la configuration de la classe "HelloWorld", on remarque que le RCProject est capable de convertir les configurations sous forme de chaines de caractères ("String") en d'autre "classe". Pour cela, RCProject utilise une classe nommée "StringConverter".

Pour créer une instance de "StringConverter" il suffit d'utiliser la méthode statique "StringConverter.getInstance()". Pour utiliser cette fonctionnaliter dans le code il faut utiliser la méthode "convert" sur une instance de "StringConverter ".

Remarque : l'objet "StringConverter" récupéré via la méthode "StringConverter.getInstance()" est en rélité un singleton.

Par exemple, pour convertir une chaine de caractères vers un entier :

String integerStr = "1";
StringConverter converter = StringConverter.getInstance();
Integer myInt = converter.convert( integerStr, Integer.class );

Remarque : Si une erreur se produit durant la conversion, une "ConversionException" est levée.

Les converters

Pour convertir les chaines de caractères en d'autre "classe", l'instance de "StringConverter" utilise des "Converters" chargés de faire la conversion du format "String" vers le format demandé. RCProject fournit par défaut les "Converters" permettant gérer les formats suivants :

  • Boolean
  • Byte
  • Char
  • Class
  • Class[]
  • Double
  • Float
  • Integer
  • List
  • Long
  • Map
  • Object
  • Set
  • Short
  • String
  • String[]

Afin de gérer d'autres formats, il est également possible de créer de nouveaux "Converters".

En guise d'exemple, nous allons ajouter un attributs "author" à notre classe "HelloWorld". Ce dernier permettra d'attribuer un auteur au message affiché. Pour cela, nous allons utiliser la classe "Author" suivante qui décrit un auteur par son nom et son prénom : 

public class Author {
    /*---------- Attributes ---------*/
    private String lastName;
    private String firstName;

    /*-------- Constructor ----------*/
    public Author(String lastName, String firstName) {
        super();
        this.lastName = lastName;
        this.firstName = firstName;
    }

    /*----------- Getters -----------*/
    public String getLastName() {
        return lastName;
    }
    public String getFirstName() {
        return firstName;
    }
}

Comme vu précédemment, nous ajoutons l'attribut "author" à la classe "HelloWorld" et nous le rendons configurable via le nom "author" :

/*---------- Attributes ---------*/
@ConfigurableAttribute( configName="author", mandatory=true)
private Author author;
...

Dans le fichier de configuration du projet, nous ajoutons la propriété "helloworld.author". Pour notre exemple, nous supposerons que l'auteur sera configuré sous le format : "NOM, Prénom" (on utilise la virgule comme séparateur)

Par exemple :

#Author
helloworld.author = ROLLAND, Cyrille

Maintenant que notre attribut est configuré, il faut ajouter à notre projet un "Converter" permettant de convertir une chaine de caractères vers la classe "Author". Pour cela, nous allons implémenter la classe "AuthorConverter" qui doit implémenter l'interface "Converter".

Un "Converter" doit être composé des méthodes :

  • getTargetClass : Retourne la classe vers laquelle sera convertie la chaine de caractères
  • convert : Convertit une chaine de caracètres en la classe cible

Voici donc une implémentation possible de la classe "AuthorConverter" : 

public class AuthorConverter  implements Converter{
    @Override
    public Object convert(String value) throws ConversionException {
        Author result = null;
        
        //If value is not null
        if(Tools.isset(value)){
            String[] data = value.split(",");
            
            //2 elements : lastName and firstName
            if(data.length==2){
                result = new Author( data[0].trim(), data[1].trim() );
            }else{
                throw new ConversionException("Unable to convert '"+ value +"' into 'Author' instance");
            }
        }
        
        return result;
    }

    @Override
    public Class getTargetClass() {
        return Author.class;
    }
}

Maintenant que le nouveau "Converter" a été implémenté, il suffit de l'ajouter à la liste des "Converters" utilisée par le "StringConverter".

Cela ce fait via le fichier de configuration du projet de la manière suivante (remarque : la classe "AuthorConverter" est dans le package 'rc.helloworld') :

#Converters
StringConverter.converters = rc.helloworld.AuthorConverter

Une fois que tout est en place, nous pouvons gérer l'affichage de l'auteur après l'affichage du message :

public void writeMessage() throws InterruptedException{
    ...
    if(Tools.isset(author))
        System.out.println("Author : "+ author.getFirstName() +" "+ author.getLastName() );
}

Remarque : "Tools.isset(author)" permet de vérifier que l'attribut author est bien défini (non null).

Proxy

Dans cette partie nous allons mettre en place un proxy sur la classe "HelloWorld" via RCProject.

Un proxy RCProject doit implémenter l'interface "rc.proxy.interfaces.Proxy".

Pour simplifier le développement, RCProject propose d'étendre la classe abstraite "AbstractProxy". Cela signifie qu'il faut implémenter la méthode "invokeMethod" qui est appelée lors de l'invocation d'une méthode proxifiée.

Voici un exemple de proxy RCProject que nous utiliserons sur la classe HelloWorld : Ce dernier affiche le nom des méthodes appelées sur l'instance proxifiée avant et après l'invocation de ces dernières.

@Logger(name="helloworldproxy")
public class HelloWorldProxy extends AbstractProxy{
    @Override
    public Object invokeMethod(Object instance, Method method, Object[] args)
            throws ProxyException {
        Object result=null;
        
        System.out.println("Before calling method : '"+ Tools.toString(method) +"'");

        //Method invocation
        try {
            result = method.invoke(instance, args);
        } catch (Exception e) {
            Log.error("'Invoke' exception", e);
            throw new ProxyException(e);
        }

        System.out.println("After calling method : '"+ Tools.toString(method) +"'");
        
        return result;
    }
}

Afin de pouvoir proxifier les instances d'une classe, il est necessaire que la classe en question implémente une ou plusieurs interfaces qui représentent les méthodes à proxifier.

Pour la classe "HelloWorld" nous allons mettre en place une interface "MessageWriter" composée de la méthode "writeMessage". Cette interface prendra donc la forme suivante :

public interface MessageWriter {
    /*----------- Methods -----------*/
    /**
     * Method which displays a message
     * @throws Exception
     */
    public void writeMessage() throws Exception;
}

 

Une fois que la classe "HelloWorld" implémente l'interface "MessageWriter", il suffit de configurer le proxy. Pour cela il y a deux solutions ;

  • Par annotation : Cette solution consiste en l'ajout de l'attribut "proxy" dans l'annotation "ConfigurableClass". Cet attribut est lui même une annotataion de type "ProxyConfiguration" qui permet de spécifier la configuration du proxy à utiliser.
@ConfigurableClass(
        prefix="helloworld",
        proxy=@ProxyConfiguration(
                proxy="rc.helloworld.HelloWorldProxy"
                )
        )
@Logger(name="helloworld")
public class HelloWorld implements MessageWriter {
    ...
  • Via le fichier de configuration du projet : 
#Proxy
helloworld.proxy = rc.helloworld.HelloWorldProxy

Remarques :

  • La configuration via le fichier de configuration est prioritaire sur la configuration par annotation
  • Il est possible de désactiver un proxy par configuration en plaçant la valeur "none"
Copyright © 2011 - 2017 ROLLAND Cyrille Tous droits réservés