lundi 26 août 2013

Fichier de propriétés Maven mal encodé. [2/2]

Pourquoi donc, lorsque le fichier est chargé l'encodage n'est pas bon ?

Si on reprend le code et que l'on va plus loin :
public static Properties loadPropertyFile( File propFile, Properties baseProps )
    throws IOException
{
    if ( !propFile.exists() )
    {
        throw new FileNotFoundException( propFile.toString() );
    }
    final Properties fileProps = new Properties();
    final FileInputStream inStream = new FileInputStream( propFile );
    try {
        fileProps.load(inStream) ;
    }
 ....

On constate que c'est la classe java.util.Properties qui charge les fichiers de propriétés.
Or dans la java doc (http://docs.oracle.com/javase/6/docs/api/java/util/Properties.html) on lit :
"except the input/output stream is encoded in ISO 8859-1 character encoding"

"The native2ascii tool can be used to convert property files to and from other character encodings."

Donc, la classe java.util.Properties ne gère que l'ISO 8859-1.
Pour palier ce problème, depuis java 1.6, il existe une méthode public synchronized void load(Reader reader) throws IOException qui prend un objet type java.io.BufferedReader qui couplé avec java.io.InputStreamReader permet le support d'encodage différent.
Cet encodage différent peut soit être configuré via le constructeur, soit via -Dfile.encoding=UTF8.

lundi 12 août 2013

Fichier de propriétés Maven mal encodé. [1/2]

Avec Maven, il est possible de spécifier un fichier de propriétés qui sera utilisé par la suite pour faire du filtrage de fichiers.
Ce fichier, contient la liste des variables à valoriser.

Par exemple, si dans le fichier local.properties, il y a :
titre=En cours de création

Dans le fichier à substituer label.properties, le contenu est le suivant :
monLabel=${titre}

Les deux fichiers sont encodés en UTF8.
Dans le pom.xml, l'encodage est spécifié par :
<properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 
</properties>

Une fois substitué, le résultat du fichier est :
En cours de création

Il y a donc un problème d'encodage mais où ?

Pour faire simple, le filtering jusqu'à Maven 3.0.x en tout cas se fait via la librairie "maven-filtering".
Et si on regarde le code, classe PropertyUtils méthode loadPropertyFile (c'est cette méthode qui est appelé pour le filtering), on a :
public static Properties loadPropertyFile( File propFile, Properties baseProps )
    throws IOException
{
    if ( !propFile.exists() )
    {
        throw new FileNotFoundException( propFile.toString() );
    }
    final Properties fileProps = new Properties();
    final FileInputStream inStream = new FileInputStream( propFile );
    ....

Il n'est pas prévu de pouvoir spécifier un encodage en particulier pour les fichiers contenant les variables qui devront être filtrés.
Le fichier est ouvert avec l'encodage par défaut de la JVM.

Le soucis, est que si lors du lancement de Maven, on ajoute l'option de la JVM -Dfile.encoding=UTF8 cela ne fonctionne pas.
Pourquoi ?
J'y reviendrais dans un autre billet.

Ticket sur le plugin maven ressource sur ce sujet : https://jira.codehaus.org/browse/MRESOURCES-175

lundi 29 juillet 2013

Utilisation de la balise <configuration> dans un plugin maven

Lorsque dans un pom, on active un plugin maven, il arrive de le configurer via une balise configuration.
Prenons l'exemple de maven war plugin :
<project>
  ...
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <version>2.3</version>
        <configuration>
          <webResources>
            <resource>
              <!-- this is relative to the pom.xml directory -->
              <directory>resource2</directory>
            </resource>
          </webResources>
        </configuration>
      </plugin>
    </plugins>
  </build>
  ...
</project>

Cette balise est très intéressante.
Imaginons que nous souhaitons créer un plugin qui exécute des commandes shell, et en plus dans un certain ordre, comment faire ?

Partons du postulat que notre plugin se configure comme suit :
<plugin>
 <groupId>org.test.plugins</groupId>
 <artifactId>maven-montest-plugin</artifactId>
 <version>1.0.0-SNAPSHOT</version>
 <executions>
  <execution>
   <phase>generate-sources</phase>
   <goals>
    <goal>shell-exec</goal>
   </goals>
   <configuration>
    <script name="toto">
     <run name="coucou1" exec="commande ${project.version}"></run>
     <run name="coucou2">nom du script ${project.version}</run>
    </script>
   </configuration>
  </execution>
 </executions>
</plugin> 

Pour pourvoir reproduire le comportement de la balise webResources. Il faut utiliser un objet du type org.codehaus.plexus.configuration.PlexusConfiguration.
Ici, nous aurons donc un attribut script :
/**
 * @goal shell-exec
 * @threadSafe true
 */
public class ShellExecutionMojo extends AbstractMojo {
    /**
     * The script to execute
     *
     * @parameter
     */
    private PlexusConfiguration script;
 
 ...
}

Il est important de noter, qu'il n'est pas possible d'avoir un tableau ou une liste à ce niveau.

La classe PlexusConfiguration contient une méthode getName() qui retourne le nom de la balise XML. Dans notre cas run.
Pour récupérer un attribut de la balise, il suffit d'appeler la méthode getAttribute(String). Cette méthode renvoit null si la balise n'est pas présente.
Pour récupérer le contenu de la balise (exemple coucou2), il faut utiliser la méthode getValue().
S'il y a des balises enfants, il faudra utiliser les méthodes getChild().
Si on souhaite récupérer la liste des enfants, il faudra utiliser la méthode getChildren(), qui renvoit un tableau et getChildCount() pour savoir le nombre d'enfants.

Voici l'exemple complet :
package org.test.plugins;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.codehaus.plexus.configuration.PlexusConfiguration;

import org.test.plugin.util.PlexusConfigurationComparator;

/**
 * @goal shell-exec
 * @threadSafe true
 */
public class ShellExecutionMojo extends AbstractMojo {
    /**
     * The script to execute
     *
     * @parameter
     */
    private PlexusConfiguration script;
 
    /**
     * List of script to run
     */
    private List listScriptToRun ;
    
 @Override
 public void execute() throws MojoExecutionException, MojoFailureException {
  PlexusConfiguration[] children = script.getChildren() ;
  
  getLog().info("Running ShellExecutorMojo") ;
  
  listScriptToRun = new ArrayList(children.length) ;
  
  for (PlexusConfiguration currentNode : children) {
   if ("run".equals(currentNode.getName())) {
    listScriptToRun.add(currentNode) ;
   }
  }
  
  Collections.sort(listScriptToRun, new PlexusConfigurationComparator(getLog())) ;
  
  for (PlexusConfiguration currentNode : listScriptToRun) {
   getLog().info("> " + currentNode.getAttribute("name")) ;
   
   execScript(currentNode) ;
  }  
  
  getLog().info("Fin") ;
 }
 
 private void execScript(final PlexusConfiguration currentNode) {
  String cmd = currentNode.getAttribute("exec") ;
  
  if (StringUtils.trimToNull(cmd) == null) {   
   cmd = currentNode.getValue() ;
   
   getLog().info("Balise = " + cmd) ;
  } else {
   getLog().info("Balise exec = " + cmd) ;
  }
 }
}

La classe de tri :
/**
 * 
 */
package org.test.plugin.util;

import java.util.Comparator;

import org.codehaus.plexus.configuration.PlexusConfiguration;
import org.apache.commons.lang.StringUtils;
import org.apache.maven.plugin.logging.Log ;

/**
 * Sort PlexusConfiguration object
 * @author Emeric MARTINEAU
 */
public class PlexusConfigurationComparator implements Comparator {
 /**
  * Logger
  */
 private Log logger ;
 
 public PlexusConfigurationComparator(final Log log) {
  this.logger = log ;
 }
 
 @Override
 public int compare(PlexusConfiguration object1, PlexusConfiguration object2) {
  final String orderString1 = object1.getAttribute("order") ;
  final String orderString2 = object2.getAttribute("order") ;
  
  int order1 = convertOrder(orderString1) ;
  int order2 = convertOrder(orderString2) ;
  
  
  return order1 - order2 ;
 }
 
 /**
  * Converti l'order
  * @param orderString
  * @return
  */
 private int convertOrder(final String orderString) {
  int order = 0 ;
  
  // If string is null, no order attribut found
  if (StringUtils.trimToNull(orderString) != null) {  
   try {  
    order = Integer.valueOf(orderString) ; 
   } catch(final NumberFormatException e) { 
    logger.warn(String.format(
         "Invalide 'order' tag value = '%s'", orderString)) ;
   } 
  }
  
  return order ;
 }

}

lundi 15 juillet 2013

Algorithme de gestion de dépendance [2/2]

Voic donc un exemple d'implémentation. A la différence de l'algorithme, l'implémentation ci-dessous prend la première entrée sans dépendance et la supprimer, puis recommence.
import java.util.ArrayList;
import java.util.List;

public class Graph {
    /**
     * List of file and dependency
     */
    private List vertexList = new ArrayList();

    /**
     * Dependancy matrix
     */
    private boolean matrix[][] ;

    public Graph() {
    }

    /**
     * Add artefact or dependency
     */
    public void addItem(String label) {
        vertexList.add(label);
    }

    /**
     * Add link of dependency
     */
    public void addDependency(final String artefact, final String dependency) {

        int artefactIndex = vertexList.indexOf(artefact);
        int dependencyIndex = vertexList.indexOf(dependency);

        if (matrix == null) {
            matrix = new boolean[vertexList.size()][vertexList.size()];
        }

        matrix[artefactIndex][dependencyIndex] = true ;
    }

    /**
     * Return list to include
     */
    public List getDependencyList() throws Exception
    {
        // List of dependency to be include
        final List dependencyList = new ArrayList(vertexList.size()) ;
        // Current item without dependency
        int currentItem ;
        
        // Check all item
        while (vertexList.size() > 0)
        {
            // Get a vertex with no successors, or -1
            currentItem= getIndexOfItemWithoutDependency() ;
            
            // must be a cycle
            if (currentItem == -1) 
            {
                throw new Exception("ERROR: Graph has cycles") ;
            }
            
            dependencyList.add(vertexList.get(currentItem)) ;

            // delete vertex
            removeItem(currentItem) ; 
        }

        return dependencyList ;
    }

    /**
     * Return first item without any dependency
     * 
     * @return -1 if error (cycles) or index of item
     */
    public int getIndexOfItemWithoutDependency() 
    {
        boolean hasDependency = false ;

        final int numVerts = vertexList.size();

        for (int row = 0; row < numVerts; row++) {
        
            // If no dependency, hasDependency = false
            for (int col = 0; col < numVerts && !hasDependency; col++) {
                hasDependency = matrix[row][col] ;
            }
            
            if (!hasDependency) {
                return row;
            }
        }
        
        // All item have dependency. Error !
        return -1 ;
    }

    /**
     * Remove item
     */
    public void removeItem(final int itemIndex) {
        final int numVerts = vertexList.size();

        vertexList.remove(itemIndex);

        // Because we are in array, don't move last cal/row
        if (itemIndex < numVerts - 1) 
        {
            for (int row = itemIndex; row < numVerts; row++) {
                moveRowUp(row, numVerts) ;
            }

            for (int col = itemIndex; col < numVerts; col++) {
                moveColLeft(col, numVerts) ;
            }
        }
    }

    /**
     * Remove row, and copy all in up
     */
    private void moveRowUp(int row, int length) {
        for (int col = 0; col < length; col++) {
            matrix[row][col] = matrix[row + 1][col] ;
        }
    }

    /**
     * Remove col and remove left
     */
    private void moveColLeft(int col, int length) {
        for (int row = 0; row < length; row++) {
            matrix[row][col] = matrix[row][col + 1] ;
        }
    }

    public static void main(String[] args) throws Exception {
        Graph g = new Graph();
        g.addItem("A"); // 0
        g.addItem("B"); // 1
        g.addItem("C"); // 2
        g.addItem("D"); // 3
        g.addItem("E"); // 4
        g.addItem("F"); // 5
        g.addItem("G"); // 6
        g.addItem("H"); // 7

        g.addDependency("A", "D"); // AD
        g.addDependency("A", "E"); // AE
        g.addDependency("B", "E"); // BE
        g.addDependency("C", "F"); // CF
        g.addDependency("D", "G"); // DG
        g.addDependency("E", "G"); // EG
        g.addDependency("F", "H"); // FH
        g.addDependency("G", "H"); // GH

        for (String s : g.getDependencyList()) {
            System.out.println(s);
        }
    }
}

lundi 1 juillet 2013

Algorithme de gestion de dépendance [1/2]

Si on doit gérer des dépendances entre des composants et qu'aucun outil n'existe, il peut être pratique d'avoir une idée d'un algoritme de dépendance.
En supposant le schéma suivant :


Comment faire pour réussi à trier par exemple les fichiers dans le bon ordre ? (c'est à dire H en premier)

Il va être nécessaire d'utiliser une matrice dans lequel, en ordonné, comme en absyse, il y aura les fichiers.
L'établissement à la main de cette matrice dans le résultat suivant :


Le principe va être de trouvé le/les fichiers qui n'ont pas de dépendances.
Le composant H n'a pas de dépendance.
Ensuite, le composant H est supprimé.
Voici le résultat :


Il apparait à présent que F et G n'ont pas de dépendances.
Continuons.


C, D, E sont alors sans dépendance.


Il ne reste plus que A et B

L'ordre d'inclusion peut être donc :
H
F
G
C
D
E
A
B

L'algorithme peut être différent, c'est à dire qu'il peut n'ont pas prendre à chaque fois tous les composants sans dépendance mais que le premier. Et réappliquer le cycle.
C'est ce principe qui va être repris dans le code d'exemple la prochaine fois.

lundi 17 juin 2013

Les EJB et le classpath

Lorsqu'on créer un EJB dans un EAR, l'EJB a le même classpath que l'EAR qui le contient.
Voici un schéma représentant les classpaths :

Mais si l'EJB nécessite des dépendances qui ne sont pas dans ce classpath, comment faire alors pour fournir ces dépendances dans l'EAR et que seul l'EJB ai ces dépendance dans son classpath ?

La solution va être fournit par le plugin maven jar.
Celui-ci dispose d'une option addClasspath qui permet d'enrichir le fichier manifest du jar (MANIFEST.MF) afin d'ajouter explicitement des jars dans le classpath.
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-jar-plugin</artifactId>
  ...
  <configuration>
    <archive>
   <manifest>
     <addClasspath>true</addClasspath>
   </manifest>
    </archive>
  </configuration>
  ...
</plugin>
Le manifest ressemble alors à ceci :
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Created-By: Apache Maven
Built-By: ${user.name}
Build-Jdk: ${java.version}
Class-Path: plexus-utils-1.1.jar commons-lang-2.1.jar
Cette méthode n'est pas forcément très élégante. Mais puisque les dépendances sont dans l'EAR, cela dépanne avec un moindre impacte (pas de création de SharedLib en plus sous WebSphere par exemple).

lundi 3 juin 2013

Connaitre le type d'une liste genérique par introspection

En Java, il est possible de faire de l'introspection, c'est a dire de regarder à l'intérieur de l'objet.
Cela permet par exemple, de connaitre les champs privés ou public, les méthodes...

Imaginons maintenant, que vous fassiez de l'introspection sur un objet avec le code suivant :
objetInspecte = ....

// Tout les champs declarer, y compris prive
Field[] fields = objetInspecte.getClass().getDeclaredFields() ;
Class generiqueClass ;
// Valeur du champs introspecte
Object valeurDuChamps ;

for(Field field : fields) {
    // Rend le champ accessible et lecture/ecriture
    field.setAccessible(true);
                
    valeurDuChamps = field.get(objetInspecte) ;
}

Oui, mais voilà, vous avez un liste, de ce fait field.getType().getCanonicalName() renvoie java.util.List.
Très bien, maintenant vous pouvez faire un new ArrayList pour créer la liste si le champs est null.

A présent, vous souhaitez, dans cette liste ajouter un élément. Mais de quel type est cette liste ? String, Integer, un tout autre objet ?
Pour cela, Java fournit une API. Voici un exemple :
// Type pour les generique
ParameterizedType pt ;  
// Class generique
Class generiqueClass ;

// On recupere le type generique
pt = (ParameterizedType) field.getGenericType() ;
generiqueClass = (Class) pt.getActualTypeArguments()[0];

La valeur de generiqueClass ? Et bien nous découvrons, que c'est une liste de Date par exemple !
Pour créer un objet, rien de plus simple : generiqueClass.newInstance().