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.