vendredi 1 novembre 2013

Tester ses plugins Maven

Lorsqu'il y a développement, il y a généralement test.
Mais tester un plugin (Mojo) Maven n'est pas évident. Il existe plusieurs solutions, plus ou moins pratique.
1 - Tester sans framework dédié.
Pour tester un mojo Maven (une classe Java associée à un goal), le plus simple consiste a déclarer tous les attributs exposés à Maven en protected ou en package private.
Par exemple, un Mojo affichant une chaine de caractère.
public class ShowMessageMojo extends AbstractMojo {
 /**
  * @parameter
  * @required
  */
 private String message ;
    
    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        getLog().info(message) ;
    }
}

Pour tester ce mojo, il est nécessaire de changer private String message ; en protected String message ;. Ainsi, il est possible de créer un classe de test qui hérite de ce mojo et qui ajoutera un getter/setter.
Il est aussi tout a fait possible, dans le mojo du départ de mettre des getter/setter. Ces getter/setter seront en plus utilisé par Maven pour modifier l'attribut.

Ensuite, une simple classe JUnit permet de tester.
Le gros inconvénient viendra lorsque le mojo voudra récupérer le projet Maven, les repositories...
2 - Tester avec maven-plugin-testing-harness
Pour pouvoir exécuter un mojo dans un contexte Maven, il existe le plugin maven-plugin-testing-harness.
Dans l'exemple suivant, voici la dépendance utilisée :
<dependency>
  <groupId>org.apache.maven.plugin-testing</groupId>
  <artifactId>maven-plugin-testing-harness</artifactId>
  <version>1.3</version>
  <scope>test</scope>
</dependency>

Reprise de l'exemple précédent, mais avec l'ajout du projet maven :
/**
 * @goal showMessage
 * @requiresProject true
 * 
 * @author Emeric MARTINEAU
 */
public class ShowMessageMojo extends AbstractMojo {
 /**
  * Le POM.
  * 
  * @parameter expression="${project}"
  * @required
  */
 protected MavenProject project;
 
 /**
  * @parameter
  * @required
  */
 private String message ;
    
    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        getLog().info(project.getBasedir()) ;
    }
}

Comment alors créer un projet maven dans le cadre d'un test ?
Pour cela, une classe de test dérivant de AbstractMojoTestCase doit être créée.

Cette classe met a disposition un ensemble de méthode qui facilite le test.

Il faut au préalable, créer un fichier pom.xml qui appel le plugin, exemple :
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                      http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>group-id-bidon</groupId>
    <artifactId>artifact-id-bidon</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>Test plugin</name>

    <build>
        <plugins>
            <plugin>
                <groupId>org.mycompany.plugin</groupId>
                <artifactId>maven-toto-plugin</artifactId>
                <version>1.0.0-SNAPSHOT</version>
                <configuration>
                    <message>SRVF_TestMaven</message>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Ce fichier de pom permet de récupérer une instance du plugin/mojo.
Voici comment instancier le mojo et l'exécuter :
// Par convention, le classe de test porte le nom de la
// classe du mojo, suffixé avec "Test".
public class ShowMessageMojoTest extends AbstractMojoTestCase {
    /**
     * @throws Exception if any
     */
    public void testMojo() throws Exception
    {
        // Lecture du fichier pom pour initiliser le context Plexus
        File pom = getTestFile( "src/test/resources/pom.xml" );
        assertNotNull( pom );
        assertTrue( pom.exists() ) ;
        
        // Recuperation du mojo par le nom du goal
        ShowMessageMojo myMojo = (ShowMessageMojo) lookupMojo( "showMessage", pom );
        assertNotNull( myMojo );
                
        // Execution du mojo
        myMojo.execute();
    }
}

Le résultat ?
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.469 sec <<< FAILURE!
testMojo(ShowMessageMojoTest)  Time elapsed: 0.283 sec  <<< ERROR!
java.lang.NullPointerException: null
 at ShowMessageMojo.execute(ShowMessageMojo.java:31)
 at ShowMessageMojoTest.testMojo(ShowMessageMojoTest.java:30)

NullPointerException à la ligne getLog().info(project.getBaseDir()) ;. Pourquoi ?
Parce que expression="${project}" n'est pas interprété.

Il faudra donc, manuellement affecter une valeur, juste avant l'exécution du mojo, avec :
public class ShowMessageMojoTest extends AbstractMojoTestCase {
    /**
     * @throws Exception if any
     */
    public void testMojo() throws Exception
    {
        // Lecture du fichier pom pour initiliser le context Plexus
        File pom = getTestFile( "src/test/resources/pom.xml" );
        assertNotNull( pom );
        assertTrue( pom.exists() ) ;

        // Recuperation du mojo par le nom du goal
        ShowMessageMojo myMojo = (ShowMessageMojo) lookupMojo( "showMessage", pom );
        assertNotNull( myMojo );

        // Creation du projet maven
        final MavenProject mvnProject = new MavenProject() ;
        mvnProject.setBasedir(new File("/")) ;
        setVariableValueToObject(myMojo, "project", mvnProject) ;
  
        // Execution du mojo
        myMojo.execute();
    }
}

Il en sera ainsi pour toute les proriétés.

Evidement, cela reste encore un peu manuel. Mais d'autre méthode existe pour configurer le mojo.

Il est possible d'utiliser la ligne configureMojo(myMojo, "maven-toto-plugin", pom) ;.
public class ShowMessageMojoTest extends AbstractMojoTestCase {
    /**
     * @throws Exception if any
     */
    public void testMojo() throws Exception
    {
        // Lecture du fichier pom pour initiliser le context Plexus
        File pom = getTestFile( "src/test/resources/pom.xml" );
        assertNotNull( pom );
        assertTrue( pom.exists() ) ;

        // Recuperation du mojo par le nom du goal
        ShowMessageMojo myMojo = (ShowMessageMojo) lookupMojo( "showMessage", pom );
        assertNotNull( myMojo );

        configureMojo(myMojo, "maven-toto-plugin", pom) ;
  
        // Execution du mojo
        myMojo.execute();
    }
}

Le seul inconvénient de ce système, c'est qu'il ne gère pas la possiblilité d'avoir dans le même fichier de configuration, plusieurs goal avec la baslise <execution />.
Il faut donc pour chaque goal créer un pom.

jeudi 10 octobre 2013

La généricité en Java : utilisation particulière

Les génériques en Java permettent une grande souplesses dans le code.

Il peut très intéressant de tirer partie de cette généricité pour par exemple, automtiser la création d'objet.

Si une classe attend un objet en entrée et retourne un autre objet, il peut être pratique d'automatisé la création d'objet.

Voici un exemple de classe :
import org.apache.commons.beanutils.ConstructorUtils;
import java.lang.reflect.ParameterizedType;

public abstract class Services<Q extends Query, R extends Response> {
    private Q q ;
    private R r ;

    public Services() {
        q = (Q) buildGeneric(getClass(), 0) ;
        r = (R) buildGeneric(getClass(), 1) ;
    }
    
    public abstract void execute() ;
    
    public Q getQuery() {
        return q ;
    }

    public R getResponse() {
        return r ;
    }

    private static Object build(Class<?> clazz) {
        Object result = null;
        try {
            result = ConstructorUtils.invokeConstructor(clazz, null);
        } catch (Exception e) {
            // Nothing
        }
        return result;
    }

    private static Object buildGeneric(Class<?> clazz, int position) {
        return build(getClassGeneric(clazz, position));
    }

    private static Class<?> getClassGeneric(Class<?> classGeneric, int position) {
        return (Class<?>) ((ParameterizedType) classGeneric.getGenericSuperclass()).getActualTypeArguments()[position];
    }    
}

Cette classe Services créé automatiquement les objets de requête et de réponse en récupérant dans la classe concrète les paramètres de générique.
Ces objets peuvent être de n'importe quel type.

Le constructeur récupère le nom de la classe des deux génériques et créer les objets (en utilisant une classe externe ConstructorUtils de common-beanutil pour plus de facilité).

Chaque argument générique est attaché à la classe lors de la compilation.
Cela signifie qu'il faut créer une classe à la main.
Il n'est pas possible de créer une classe anonyme à l'éxécution.

Voici une classe héritant de la classe Services :
public class MonService extends Services<MaQuery, MaResponse> {
    @Override
    public void execute() {
        System.out.println("TestSevice : execute()");
        
    }
}

Les deux classes de requête et de réponse :
public class MaQuery {
    @Override
    public String toString() {
        return "MaQuery" ;
    }
}

public class MaResponse {
    @Override
    public String toString() {
        return "MaResponse" ;
    }

}

Maintenant, une classe de test :
public class TestService {
    public static void main(String[] args) {
        Services s = new MonService() ;
        
        s.execute() ;
        
        System.out.println(s.getQuery().toString()) ;
        System.out.println(s.getResponse().toString()) ;
    }
}

Et voilà. Cela permet un code simple, générique, clair.
Il pourrait être ajouter des contrôles commun sur les objets requête et réponse.

Les deux défauts, comme dit précédemment, il faut créer une classe à la main pour qu'elle soit compilée afin d'avoir les informations de généricité (nom classe requête et réponse) via les meta-data.
La performance. Avec ce système, la réflexion est mise en jeu et coûte cher en temps d'exécution.

Toutefois, cette méthode est souvent utilisée. Pour pallier le problème de performance, d'autre mécanisme sont mis en place (cache...)

vendredi 13 septembre 2013

Ajouter un filtre personnalisé au plugin maven resource

Suite au post précédent post Multi encoding avec le plugin resource de Maven, il existe un autre moyen de personnaliser le filtering maven.
Il est possible soit de le substituer, soit de le compléter (ce qui répond aux deux bug maven ressource : https://jira.codehaus.org/browse/MRESOURCES-118 et https://jira.codehaus.org/browse/MRESOURCES-31).

En effet, depuis le version 2.4 du plugin resource, il est possible de spécifier un mavenFilteringHints.
A ce sujet, le site met à disposition la documentation suivante : http://maven.apache.org/plugins/maven-resources-plugin/examples/custom-resource-filters.html
Toutefois, l'exemple est peu parlant.
Nous allons donc voir ici comment créer un filtering personnaliser.

Dans un premier temps, il est nécessaire de créer un projet Maven de type JAR.
Le pom doit être renseigné comme ci-dessous :
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>fr.emeric.filtering</groupId>
  <artifactId>myFiltering</artifactId>
  <packaging>jar</packaging>
  <version>0.0.1-SNAPSHOT</version>
  <name>myFiltering Maven Mojo</name>

  <dependencies>
    <dependency>
      <groupId>org.apache.maven.shared</groupId>
      <artifactId>maven-filtering</artifactId>
      <version>1.1</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.plexus</groupId>
        <artifactId>plexus-maven-plugin</artifactId>
        <version>1.3.4</version>
        <executions>
          <execution>
            <goals>
              <goal>descriptor</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

La dépendance maven-filtering est là pour importer l'interface org.apache.maven.shared.filtering.MavenResourcesFiltering et toutes les autres classes nécessaires.
Le plugin plexus-maven-plugin est là lui pour générer les méta-données nécessaire à plexus.

Ensuite, il suffit de créer la classe ci-dessous :
package fr.emeric.filtering.myFiltering;

import java.io.File;
import java.util.Collections;
import java.util.List;

import org.apache.maven.model.Dependency;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Resource;
import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.filtering.MavenFilteringException;
import org.apache.maven.shared.filtering.MavenResourcesExecution;
import org.apache.maven.shared.filtering.MavenResourcesFiltering;
import org.codehaus.plexus.util.FileUtils.FilterWrapper;

/**
 * @plexus.component role="org.apache.maven.shared.filtering.MavenResourcesFiltering" 
 *                   role-hint="myFilter"
 */
public class MyFiltering
    implements MavenResourcesFiltering
{

  public void filterResources(List resources, File outputDirectory,
      MavenProject mavenProject, String encoding,
      List fileFilters, List nonFilteredFileExtensions,
      MavenSession mavenSession) throws MavenFilteringException {    
  }

  public void filterResources(List resources, File outputDirectory,
      String encoding, List filterWrappers,
      File resourcesBaseDirectory, List nonFilteredFileExtensions)
      throws MavenFilteringException {
    
  }

  public List getDefaultNonFilteredFileExtensions() {
    return Collections.EMPTY_LIST;
  }

  public boolean filteredFileExtension(String fileName,
      List userNonFilteredFileExtensions) {
    return true;
  }

  public void filterResources(MavenResourcesExecution mavenResourcesExecution)
      throws MavenFilteringException {
    System.out.println("Hello depuis mon filtering !");
    
    List l = mavenResourcesExecution.getMavenProject().getDependencies() ;
    
    for(Dependency da : l) {
      System.out.println(da.toString());
    }
    
  }    
}

La méthode public void filterResources(MavenResourcesExecution mavenResourcesExecution) est applelée pour faire le filtre.

Il faut bien garder à l'esprit que le filtre personnalisé est appelé après le filtre par défaut de Maven.
C'est à dire que les fichiers ressources sont déjà filtrés quand le code est appelé.
Cela permet donc de rajouter des filtres.
Si vous voulez remplacer le filtre par défaut, il n'y a aucun problème. En effet, l'objet MavenResourcesExecution mavenResourcesExecution est le même que celui donné au filtre par défaut. Il y aura juste deux filtres exécutés, le premier ne servant à rien.

L'exemple ci-dessus montre que le filtre ne fait rien. Il affiche un texte Hello depuis mon filtering ! et la liste des dépendances.

Maintenant que notre classe de filtre est créer, le jar contenant cette classe doit être générée et installée dans le repository maven (pour cette exemple, le repository local est suffisant) :
mvn clean install

A présent, dans un autre projet, où l'on souhaite utiliser ce filtre personnalisé, le plugin maven resource doit être configuré :
<project>
  ...
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-resources-plugin</artifactId>
        <version>2.6</version>
        <configuration>
          ...
          <dependencies>
            <dependency>
              <groupId>fr.emeric.filtering</groupId>
              <artifactId>myFiltering</artifactId>
              <version>0.0.1-SNAPSHOT </version>
            </dependency>
          </dependencies>
    <mavenFilteringHints>
            <mavenFilteringHint>myFilter</mavenFilteringHint>
          </mavenFilteringHints>
          ...
        </configuration>
      </plugin>
    </plugins>
    ...
  </build>
  ...
</project>

Le jar produit avec le filtre personnalisé est mis en dépendance du plugin maven resource.
Dans le paramètre mavenFilteringHint, le nom indiqué correspond à l'indication de la java doc role-hint="myFilter".
Voici le résultat lorsque maven s'exécute :
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
Hello depuis mon filtering !
Dependency {groupId=org.apache.maven, artifactId=maven-plugin-api, version=2.0.6, type=jar}
Dependency {groupId=org.apache.maven, artifactId=maven-model, version=3.0.5, type=jar}

lundi 9 septembre 2013

Multi encoding avec le plugin resource de Maven

Par défaut, avec le plugin resource de Maven, il n'est pas possible de spécifier un encodage spécifique par ressource.
Dans la balise resources, il est uniquement possible de spécifier un seul encodage.

Il est toutefois possible de contourner le problème même si cela peut sembler un peu fastidieux.

Le plugin resources à un goal copy-resources qui permet de spécifier des ressources supplémentaires avec d'autres paramètres.
C'est là que se situe l'astuce.
Dans la balise générale resources, toutes les resources sont exclues.
Ensuite, "manuellement", les plugins resources avec le goal copy-resources est lancé avec un configuration différente pour chaque encodage.
Ainsi, un répertoire UFT8 contient les ressources avec cet encodage, idem avec le répertoire cp1252.
<build>
  <resources>
    <resource>
      <directory>src/main/resources</directory>
        <excludes>
          <exclude>**/**</exclude>
        </excludes>
    </resource>
  </resources>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-resources-plugin</artifactId>
      <version>2.6</version>
      <executions>
        <execution>
          <id>copy-resources1</id>
          <phase>process-resources</phase>
          <goals>
            <goal>copy-resources</goal>
          </goals>
          <configuration>
            <outputDirectory>${project.build.outputDirectory}</outputDirectory>
            <encoding>UTF-8</encoding>
            <resources>
              <resource>
                <directory>src/main/resources/UTF8</directory>
                <filtering>true</filtering>
              </resource>
            </resources>
          </configuration>
        </execution>
        <execution>
          <id>copy-resources2</id>
          <phase>process-resources</phase>
          <goals>
            <goal>copy-resources</goal>
          </goals>
          <configuration>
            <outputDirectory>${project.build.outputDirectory}</outputDirectory>
            <encoding>CP1252</encoding>
            <resources>
              <resource>
                <directory>src/main/resources/cp1252</directory>
                <filtering>true</filtering>
              </resource>
            </resources>
          </configuration>
        </execution>
      </executions>
    </plugin>
<build>

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().

lundi 27 mai 2013

Modifier le rendu d'une liste de string avec XStream en utilisant un converter

Continuons avec la classe Toto contient une liste de String dont le rendu est le suivant :
<liste__Produit>
  <string>truc</string>
  <string>bidule</string>
</liste__Produit>

Plutôt que d'avoir une suite de balise string, il serait souhaitable d'avoir une balise part.
Dans la documentation, il existe l'annotation @XStreamImplicit.
Il y a deux paramètres : itemFieldName et keyFieldName.
Le premier indique l'alias de la collection et le deuxième sert pour les type Map.
Avec le code suivant :
@XStreamAlias("listeProduit")
@XStreamImplicit(itemFieldName="part")
public List titi = new ArrayList() ;

Le résultat est à présent :
<produit>
  <id>1</id>
  <part>truc</part>
  <part>bidule</part>
  <dateEnvoie>2013-03-01 10:12:04.306 UTC</dateEnvoie>
</produit>

La liste est donc mise à plat avec une suite d'entrée.
Si il est désiré d'avoir une balise parente, il va falloir passer par un converter maison.
Voici l'annotation :
@XStreamAlias("listeProduit")
@XStreamConverter(value=ListToStringXStreamConverter.class, strings={"part"}) 
public List titi = new ArrayList() ;

Avant de continuer, voyons un peu le principe.
value indique le nom de la classe de conversion.
strings est quant à lui disponible dans l'annotation pour des besoins annexes. Ce paramètre sera transmis au constructeur. D'autres champs sont mis à disposition et peuvent être exploités pour des besoins spécifiques comme types qui permet de contenir des classes.
// Come from http://stackoverflow.com/questions/1791178/customising-serialisation-of-java-collections-using-xstream
public class ListToStringXStreamConverter implements Converter {

private String alias;

public ListToStringXStreamConverter(String alias) {
    super();
    this.alias = alias;
}

@SuppressWarnings("rawtypes")
@Override
public boolean canConvert(Class type) {
    return true;
}

@Override
public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {

    @SuppressWarnings("unchecked")
    List list = (List)source;

    for (String string : list) {
        writer.startNode(alias);
        writer.setValue(string);
        writer.endNode();
    }
}

@Override
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
    throw new UnsupportedOperationException("ListToStringXStreamConverter does not offer suport for unmarshal operation");
}

Et mainteant, voici le rendu :
<produit>
  <id>1</id>
  <listeProduit>
    <part>truc</part>
    <part>bidule</part>
  </listeProduit>
  <dateEnvoie>2013-03-01 10:25:19.10 UTC</dateEnvoie>
</produit>

Ce qui est fait, c'est de remplacer le composant qui affiche une liste par le notre.
Par défaut, XStream écrit l'alias du champs et ensuite le converter écrit les données dans le flux. Pour cela, il prend en paramètre le nom du noeud à créer ("part").

lundi 20 mai 2013

Modifier le rendu d'une balise XML avec XStream en utilisant un converter

Toujours en utilisant le même exemple, la classe Toto contient une date dont le rendu est le suivant :
<date__Envoie>2013-02-28 17:01:29.669 UTC</date__Envoie>

Afin de personnaliser l'affichage de la date, il est possible via une annotation d'indiquer a XStream de ne pas utiliser le rendu standard mais un rendu personnalisé.
Il est donc nécessaire d'ajouter l'annotation suivant au champ dateEnvoie (en-dessous de l'annotation alias) :
@XStreamConverter(value=MyDateConverter.class)

Dans le cas d'une date (mais aussi pour une String, Integer...), il s'agit de remplacer le comportement de la classe com.thoughtworks.xstream.converters.basic.DateConverter.
Cette classe hérite de la classe com.thoughtworks.xstream.converters.basic.AbstractSingleValueConverter.
Il est simplement nécessaire de redéfinir les méthodes :
public class MyDateConverter extends AbstractSingleValueConverter {
    // Indique si cette classe prend en charge la conversion
    @Override
    public boolean canConvert(Class type) {
        return type.equals(java.util.Date.class);
    }

    // Transforme la valeur XML en Java
    @Override
    public Object fromString(String str) {
        final SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd") ;
        
        Date dateStr = null ; 
            
        try {
            dateStr = sdf.parse(str) ;
        } catch (ParseException e) {
            // Nothing
        }
         
        return dateStr ;
    }
    
    // Transforme la valeur Java en XML
    @Override
    public String toString(Object obj) {
        final Date date = (Date)obj;
        
        final SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd") ;
        
        return sdf.format(date) ;
    }
}

vendredi 17 mai 2013

Tutoriel MyBatis

Vous cherchez une alternative à Hibernate ?
Vous souhaitez utiliser un ORM plus simple à mettre en oeuvre ?

Alors essayez MyBatis et pour ce faire, vous trouverez sur ce blog un tutoriel complet (voir les liens à côté).

lundi 13 mai 2013

Changer la convention des noms des balises avec XStream (exemple avec les underscores)

Afin de ne pas avoir de double underscore ("_"), il est nécessaire d'indiquer à XStream la convention de nommage des balises.
Cela ce fait à la création de l'objet XStream :
XStream stream = new XStream(new XppDriver(new XmlFriendlyNameCoder("_-", "_")));

lundi 6 mai 2013

Changer le nom des balises XML avec XStream (utilisation des alias)

Il est possible via des annotations de changer le nom de la balise dans le flux XML.
En reprenant la classe Toto :
import com.thoughtworks.xstream.annotations.XStreamAlias;

@XStreamAlias("produit")
public class Toto {
  @XStreamAlias("id")
  public String toto = "1";
  
  @XStreamAlias("liste_Produit")
  public List titi = new ArrayList() ;
  
  @XStreamAlias("date_Envoie")
  public Date tata = new Date() ;

Il est nécessaire d'indiquer à XStream de prendre en compte les annotations, en ajoutant, avant de générer le rendu, la ligne suivante :
stream.autodetectAnnotations(true) ;

Voici le nouveau rendu :
<produit>
  <id>1</id>
  <liste__Produit>
    <string>truc</string>
    <string>bidule</string>
  </liste__Produit>
  <date__Envoie>2013-02-28 17:01:29.669 UTC</date__Envoie>
</produit>
Vous remarquerez peut-être que les alias avec un underscore ("_"), voit celui-ci doublé.
La raison est expliquer dans la FAQ de XStream.

lundi 29 avril 2013

La sérialisation XML avec XStream

XStream est une bibliothèque Java qui permet de transformer un grappe d'objet Java en flux XML et inversement.
Simple d'utilisation dans les cas standards, elle répond à presque tout les besoins.

Avec l'objet ci-dessous :
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class Toto {
 public String toto = "1";
 public List titi = new ArrayList() ;
 public Date tata = new Date() ;
 
 public Toto() {
  titi.add("truc") ;
  titi.add("bidule") ; 
 }
}
Note, pour facilite la lecture et l'exemple, les attributs sont public.

Avec les lignes suivantes, un rendu XML est obtenu :
XStream stream = new XStream();
System.out.println(stream.toXML(new Toto())) ;

Voici le rendu :
<fr.ca.cat.Toto>
  <toto>1</toto>
  <titi>
    <string>truc</string>
    <string>bidule</string>
  </titi>
  <tata>2013-02-28 16:54:48.246 UTC</tata>
</fr.ca.cat.Toto>

Par défaut, les balises sont créées avec le nom du champs, ce qui dans certain cas peut ne pas correspondre.

lundi 22 avril 2013

Vérifier qu'un artifact maven existe v2

Dans le post précédent, un première solution a été donnée pour savoir si un artifact existe ou nom.
Le problème, c'est que cette vérification se base sur le pom.xml, or il est possible d'avoir un pom sans jar par exemple.

Le nouvel exemple, va être plus restrictif.
Il va indiquer si un artifact est présent sur un repository distant. Il peut être très facilement adapté pour supporter le repository local (il y a une log dans le code pour ce cas).

Le code ne supporte que les versions release (1.2.0), les versions SNPASHOT, RELEASE, LASTEST ne sont pas supportées.
Il faudra au préalable résoudre la version.

Le principe est donc :
  • récupérer le descriptor,
  • regarder s'il y a un repository,
  • s'il y a un repository, construire un requête HTTP et lire le status (réponse).

voici le code :
package org.test.maven.plugin;

import java.io.File;
import java.util.List;

import org.apache.maven.RepositoryUtils;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.sonatype.aether.RepositorySystem;
import org.sonatype.aether.RepositorySystemSession;
import org.sonatype.aether.resolution.ArtifactDescriptorException;
import org.sonatype.aether.resolution.ArtifactDescriptorRequest;
import org.sonatype.aether.resolution.ArtifactDescriptorResult;
import org.sonatype.aether.util.artifact.DefaultArtifact;

/**
 * Ce plugin indique si l'artefact existe dans un repository distant pour une version RELEASE uniquement
 * 
 * @goal check-exists-artifact
 */
public class VerifierArtefactExistant2 extends AbstractMojo {
    /**
     * @parameter default-value="${localRepository}" 
     */
    private ArtifactRepository localRepository;    
    
    /**
     * The entry point to Aether, i.e. the component doing all the work.
     * 
     * @component
     */
    private RepositorySystem repoSystem;

    /**
     * The current repository/network configuration of Maven.
     * 
     * @parameter default-value="${repositorySystemSession}"
     * @readonly
     */
    private RepositorySystemSession repoSession;

    /**
     * List of Remote Repositories used by the resolver
     * 
     * @parameter expression="${project.remoteArtifactRepositories}"
     * @readonly
     * @required
     */
    protected List<ArtifactRepository> remoteRepos;

    /**
     * Group Id of Artifact 
     * 
     * @parameter default-value="${project.groupId}"
     * @required
     */
    private String groupId;

    /**
     * Name of Artifact
     * 
     * @parameter default-value="${project.artifactId}"
     * @required
     */
    private String artifactId;

    /**
     * Version of Artifact
     * 
     * @parameter default-value="${project.version}"
     */
    private String version;

    /**
     * Type of Artifact (War,Jar,etc)
     * 
     * @parameter default-value="${project.packaging}"
     * @required
     */
    private String type;

    /**
     * Classifier for Artifact (tests,sources,etc)
     * 
     * @parameter default-value="${project.classifier}"
     */
    private String classifier;

    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        try {
            ArtifactDescriptorResult artefactFile = getDescriptor() ;
            
            boolean exists = checkIfRemoteFileExists(artefactFile) ;
            
            if (!exists)
            {
                // Si l'artefact n'existe pas, il faut supprimer le repertoire car maven
                // 'note' le fait qu'il n'a pas trouve et pour une version RELEASE, il ne
                // tentera pas un nouveau telechargement.
                //
                // Attention, dans le cas d'un artifact present uniquement en local, il ne 
                // faut pas le supprimer
                if (artefactFile.getRepository() == null)
                {
                    getLog().debug("Suppression des informations dans le repository local") ;
                    cleanArtifactLocalRepositoryData(artefactFile) ;
                }
            }
        } catch (ArtifactDescriptorException e) {
            throw new MojoExecutionException("Impossible de recuperer les metadata de l'artefact", e);
        }
    }

    /**
     * Renvoi le descriptor de l'artefact.
     * 
     * @return le descriptor
     * @throws ArtifactDescriptorException
     *             en cas d'erreur lors de la recherche de l'artefact
     */
    private ArtifactDescriptorResult getDescriptor() throws ArtifactDescriptorException {
        DefaultArtifact artifact = new DefaultArtifact(groupId, artifactId,
                classifier, type, version);
        
        ArtifactDescriptorRequest request = new ArtifactDescriptorRequest(artifact,
                RepositoryUtils.toRepos(remoteRepos), null) ;
        ArtifactDescriptorResult descriptor = repoSystem.readArtifactDescriptor(repoSession, request) ;        

        return descriptor ;
    }
    
    /**
     * Suppression du repertoire
     * 
     * @param result resultat de la recherche de l'artefact descriptor
     */
    private void cleanArtifactLocalRepositoryData(ArtifactDescriptorResult result)
    {
        // Le chemin d'acces au repository local + construction du chemin vers 
        // les données
        final File dirToClean = new File(localRepository.getBasedir(),
                String.format("%s/%s/%s",
                        result.getArtifact().getGroupId().replace(".", "/"),
                        result.getArtifact().getArtifactId(),
                        result.getArtifact().getVersion())) ;
        
        if (!dirToClean.exists()) {
            getLog().warn(
                    String.format("Le repertoire '%s' n'existe pas.",
                            dirToClean.getAbsolutePath())) ;
        } else {
            if (getLog().isDebugEnabled())
            {
                getLog().debug(
                        String.format("Suppression du repertoire '%s'.",
                                dirToClean.getAbsolutePath())) ;                    
            }
            
            deleteDirectoryOnExit(dirToClean) ;
        }
    }
    
    /**
     * Supprime un repertoire et son contenu au shutdown de la VM car maven lock les fichiers.
     * 
     * @param directory repertoire a supprimer
     */
    private void deleteDirectoryOnExit(File directory)
    {
        if (directory.exists())
        {
            File[] listFile = directory.listFiles() ;
            
            for(File currentFile : listFile)
            {
                if (currentFile.exists())
                {
                    currentFile.deleteOnExit() ;
                }
            }
            
            directory.deleteOnExit() ;
        }
    }    
    
    /**
     * Verifie qu'une version release existe bien sur le serveur.
     * NE FONCTIONNE PAS POUR version = 'SNAPSHOT', 'LASTEST', 'RELEASE'
     * Il faudra resoudre la version d'abord.
     * 
     * @param artefactFile
     * @return
     */
    private boolean checkIfRemoteFileExists(final ArtifactDescriptorResult artefactFile)
    {
        boolean fileExits = false ;
        
        org.sonatype.aether.repository.ArtifactRepository repository = artefactFile.getRepository() ;
        
        if (repository != null)
        {        
            ArtifactRepository selectedRepository = rechercherRemoteRepository(repository) ;
                        
            if (selectedRepository == null)
            {
                // La variable repository est un repository local
                // Cela veut dire que l'artifact n'existe pas a distance, mais seulement en local,
                // c'est une erreur !
                getLog().warn("L'artifact est present en local mais pas sur le serveur depot distant !") ;
            }
            else
            {
                fileExits = verifierExistanceArtifactDistant(artefactFile, selectedRepository) ;
            }

        }
        
        return fileExits ;
    }
    
    /**
     * Recherche le repository distant dont l'ID correspond a celui en entree
     * 
     * @param repository repository Aether
     * 
     * @return repository maven
     */
    private ArtifactRepository rechercherRemoteRepository(org.sonatype.aether.repository.ArtifactRepository repository)
    {
        ArtifactRepository selectedRepository = null ;
        
        // Recherche le repository distant d'ou le l'artifact a ete recuperer
        for(ArtifactRepository currentRepository : remoteRepos)
        {
            if (repository.getId().equals(currentRepository.getId()))
            {
                selectedRepository = currentRepository ;
            }
        }
        
        return selectedRepository ;
    }
    
    /**
     * Verifie que dans le repository distant, la matiere existe bien.
     * 
     * @param artefactFile artifact
     * @param remoteRepository repository disant
     * @return
     */
    private boolean verifierExistanceArtifactDistant(ArtifactDescriptorResult artefactFile, ArtifactRepository remoteRepository)
    {
        boolean remoteFileExits = false ;
        
        Artifact artifact = artefactFile.getArtifact() ;
        
        // Creer le nom du fichier avec les informations
        String fileName ;
        
        if (StringUtils.isBlank(artifact.getClassifier()))
        {
            fileName = String.format("%s-%s.%s", artifact.getArtifactId(), artifact.getVersion(), artifact.getExtension()) ;
        }
        else
        {
            fileName = String.format("%s-%s-%s.%s", artifact.getArtifactId(), artifact.getVersion(), artifact.getClassifier(), artifact.getExtension()) ;
        }            
        
        // Construit l'URL
        String url = String.format("%s%s/%s/%s/%s", remoteRepository.getUrl(), artifact.getGroupId().replace(".", "/"),
                artifact.getArtifactId(), artifact.getVersion(), fileName) ;
        
        // Ouvre une connexion vers le fichier
        try {
            getLog().info(String.format("Verification de l'existance de l'artifact '%s'", url)) ;
            
            URL u = new URL(url) ;

            HttpURLConnection http = (HttpURLConnection) u.openConnection() ;
            int status = http.getResponseCode() ;

            if (status >= 200 && status < 300)
            {
                remoteFileExits = true;
                getLog().info("Artifact trouve") ;
            }
            else
            {
                getLog().warn("Les informations de description (pom.xml) de l'artifact est present sur le serveur depot distant, "
                        + "mais l'artifact est introuvable sur ce serveur !") ;
            }
            
            http.disconnect() ;
        } catch (MalformedURLException e) {
            getLog().error(
                    String.format("Probleme de creation de l'url '%s'", url), e) ;
        } catch (IOException e) {
            getLog().error(
                    String.format("Impossible d'ouvrir une connexion reseau vers '%s'", url), e) ;
        }        
        
        return remoteFileExits ;
    }    
}

Le code commence à devenir conséquent, mais il répond bien à la problématique.

lundi 15 avril 2013

Vérifier qu'un artifact maven existe

Précédemment, un exemple de récupération de version d'un artifact maven a été présenté.
Maintenant, une demande qui n'est pas nouvelle est "comment savoir si un artifact existe sur le repository local ou distant ?".
En cherchant sur internet, il faut bien avouer que les seules réponses trouvées sont :
  • tenter de télécharger l'artifact (http://stackoverflow.com/questions/4809238/maven-how-to-check-if-an-artifact-exists),
  • faire une requête HTTP sur un serveur type Nexus (http://www.jarradmbattaglia.com/2012/02/08/maven-how-to-check-if-an-artifact-in-nexus-using-http/)

Rien de satisfaisant, surtout si le but est de savoir si l'artifact existe sans télécharger l'artifact en question.

Le principe est le même que l'exemple précédent pour connaitre la version. Mais, l'astuce va être récupérer le descriptor. Et ensuite ?
Est-ce comme pour la résolution de version, si l'artifact n'existe pas, il n'y a pas d'erreur levée, ou un paramètre indiquant une erreur ?

La réponse est oui mais !
Lorsque les informations sur le descriptor est récupéré, il est possible de savoir sur quel repository il a été récupéré.
De sorte, que si il n'y a pas de repository indiqué, cela signifie que l'artifact n'a pas été trouvé !

Toutefois, ce n'est pas encore fini.
En effet, si l'artefact n'existe pas, une entrée est quand même créé dans le repository local et dans ce cas, s'il s'agit d'une version release, et qu'elle existe ensuite, maven n'essaiera pas de un re-téléchargement, car il note que la version n'existe pas.
Il faut alors supprimer le répertoire du repository local mais seulement à l'arrêt de la JVM car maven verrouille le répertoire.

Voici alors le code :
package org.test.maven.plugin;

import java.io.File;
import java.util.List;

import org.apache.maven.RepositoryUtils;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.sonatype.aether.RepositorySystem;
import org.sonatype.aether.RepositorySystemSession;
import org.sonatype.aether.resolution.ArtifactDescriptorException;
import org.sonatype.aether.resolution.ArtifactDescriptorRequest;
import org.sonatype.aether.resolution.ArtifactDescriptorResult;
import org.sonatype.aether.util.artifact.DefaultArtifact;

/**
 * Ce plugin indique si l'artefact existe dans un repository (local ou distant)
 * 
 * @goal check-exists-artifact
 */
public class VerifierArtefactExistant extends AbstractMojo {
    /**
     * @parameter default-value="${localRepository}" 
     */
    private ArtifactRepository localRepository;    
    
    /**
     * The entry point to Aether, i.e. the component doing all the work.
     * 
     * @component
     */
    private RepositorySystem repoSystem;

    /**
     * The current repository/network configuration of Maven.
     * 
     * @parameter default-value="${repositorySystemSession}"
     * @readonly
     */
    private RepositorySystemSession repoSession;

    /**
     * List of Remote Repositories used by the resolver
     * 
     * @parameter expression="${project.remoteArtifactRepositories}"
     * @readonly
     * @required
     */
    protected List<ArtifactRepository> remoteRepos;

    /**
     * Group Id of Artifact 
     * 
     * @parameter default-value="${project.groupId}"
     * @required
     */
    private String groupId;

    /**
     * Name of Artifact
     * 
     * @parameter default-value="${project.artifactId}"
     * @required
     */
    private String artifactId;

    /**
     * Version of Artifact
     * 
     * @parameter default-value="${project.version}"
     */
    private String version;

    /**
     * Type of Artifact (War,Jar,etc)
     * 
     * @parameter default-value="${project.packaging}"
     * @required
     */
    private String type;

    /**
     * Classifier for Artifact (tests,sources,etc)
     * 
     * @parameter default-value="${project.classifier}"
     */
    private String classifier;

    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        try {
            ArtifactDescriptorResult artefactFile = getDescriptor() ;
            
            if (artefactFile.getRepository() == null)
            {                                
                getLog().error("Artifact inexistant") ;    
                
                // Si l'artefact n'existe pas, il faut supprimer le repertoire car maven
                // 'note' le fait qu'il n'a pas trouve et pour une version RELEASE, il ne
                // tentera pas un nouveau telechargement
                cleanArtifactLocalRepositoryData(artefactFile) ;
            }            
            else
            {
                getLog().info("Artefect trouve");
            }
        } catch (ArtifactDescriptorException e) {
            throw new MojoExecutionException("Impossible de recuperer les metadata de l'artefact", e);
        }
    }

    /**
     * Renvoi le descriptor de l'artefact.
     * 
     * @return le descriptor
     * @throws ArtifactDescriptorException
     *             en cas d'erreur lors de la recherche de l'artefact
     */
    private ArtifactDescriptorResult getDescriptor() throws ArtifactDescriptorException {
        DefaultArtifact artifact = new DefaultArtifact(groupId, artifactId,
                classifier, type, version);
        
        ArtifactDescriptorRequest request = new ArtifactDescriptorRequest(artifact,
                RepositoryUtils.toRepos(remoteRepos), null) ;
        ArtifactDescriptorResult descriptor = repoSystem.readArtifactDescriptor(repoSession, request) ;        

        return descriptor ;
    }
    
    /**
     * Suppression du repertoire
     * 
     * @param result resultat de la recherche de l'artefact descriptor
     */
    private void cleanArtifactLocalRepositoryData(ArtifactDescriptorResult result)
    {
        // Le chemin d'acces au repository local + construction du chemin vers 
        // les données
        final File dirToClean = new File(localRepository.getBasedir(),
                String.format("%s/%s/%s",
                        result.getArtifact().getGroupId().replace(".", "/"),
                        result.getArtifact().getArtifactId(),
                        result.getArtifact().getVersion())) ;
        
        if (!dirToClean.exists()) {
            getLog().warn(
                    String.format("Le repertoire '%s' n'existe pas.",
                            dirToClean.getAbsolutePath())) ;
        } else {
            if (getLog().isDebugEnabled())
            {
                getLog().debug(
                        String.format("Suppression du repertoire '%s'.",
                                dirToClean.getAbsolutePath())) ;                    
            }
            
            deleteDirectoryOnExit(dirToClean) ;
        }
    }
    
    /**
     * Supprime un repertoire et son contenu au shutdown de la VM car maven lock les fichiers.
     * 
     * @param directory repertoire a supprimer
     */
    private void deleteDirectoryOnExit(File directory)
    {
        if (directory.exists())
        {
            File[] listFile = directory.listFiles() ;
            
            for(File currentFile : listFile)
            {
                if (currentFile.exists())
                {
                    currentFile.deleteOnExit() ;
                }
            }
            
            directory.deleteOnExit() ;
        }
    }    
}

Le pom qui lance le goal :
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>test.truc</groupId>
    <artifactId>kiki</artifactId>
    <version>1.2.1</version>
    <packaging>jar</packaging>
    <name>test-maven</name>

    <build>
        <plugins>
            <plugin>
                <groupId>rg.test.maven.plugin</groupId>
                <artifactId>maven-test-plugin</artifactId>
                <version>1.0.0-SNAPSHOT</version>
                <executions>
                    <execution>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>check-exists-artifact</goal>
                        </goals>
                        <configuration>
                            <groupId>fr.truc.bidule</groupId>
                            <artifactId>coco</artifactId>
                            <version>1.0.11</version>
                            <type>jar</type>

                            <propertyResult>totoVariable</propertyResult>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <artifactId>maven-antrun-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>compile</phase>
                        <goals>
                            <goal>run</goal>
                        </goals>
                        <configuration>
                            <tasks>
                                <echo>Hello world! ${totoVariable}</echo>
                            </tasks>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>


Une réflexion vient à l'esprit, le contrôle se fait sur le pom.xml, mais il est possible que celui-ci soit présent, sans pour autant que le jar par exemple, exite lui.
La solution sera donné la prochaine fois.

lundi 8 avril 2013

Résoudre la version d'un artefact Maven avec Maven 3

Il peut être utile, dans le cadre d'un projet de résoudre la version d'un artefact lorsqu'une version générique est passée en paramètre (RELEASE, LATEST, x.x.x-SNAPSHOT).
Pour ce faire, il n'y a pas d'autre choix que de créer un plugin maison, pour enchaîner avec un traitement particularité.
Pour ce faire, il est nécessaire de se plonger dans la documentation volumineuse des API maven. De plus, l'API a évolué en version 3 (même si l'API v2 est toujours disponible).

Dans un premier temps, il faut créer le pom adéquat :
package org.test.maven.plugin;

import java.util.List;

import org.apache.maven.RepositoryUtils;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.sonatype.aether.RepositorySystem;
import org.sonatype.aether.RepositorySystemSession;
import org.sonatype.aether.resolution.ArtifactResolutionException;
import org.sonatype.aether.resolution.VersionRequest;
import org.sonatype.aether.resolution.VersionResolutionException;
import org.sonatype.aether.resolution.VersionResult;
import org.sonatype.aether.util.artifact.DefaultArtifact;

/**
 * Ce plugin affiche la version resolue d'un artefact
 * 
 * @goal version-artifact
 */
public class VersionArtefact extends AbstractMojo {
 /**
  * The entry point to Aether, i.e. the component doing all the work.
  * 
  * @component
  */
 private RepositorySystem repoSystem;

 /**
  * The current repository/network configuration of Maven.
  * 
  * @parameter default-value="${repositorySystemSession}"
  * @readonly
  */
 private RepositorySystemSession repoSession;

 /**
  * List of Remote Repositories used by the resolver
  * 
  * @parameter expression="${project.remoteArtifactRepositories}"
  * @readonly
  * @required
  */
 protected List<ArtifactRepository> remoteRepos;

 /**
  * Group Id of Artifact 
  * 
  * @parameter default-value="${project.groupId}"
  * @required
  */
 private String groupId;

 /**
  * Name of Artifact
  * 
  * @parameter default-value="${project.artifactId}"
  * @required
  */
 private String artifactId;

 /**
  * Version of Artifact
  * 
  * @parameter default-value="${project.version}"
  */
 private String version;

 /**
  * Type of Artifact (War,Jar,etc)
  * 
  * @parameter default-value="${project.packaging}"
  * @required
  */
 private String type;

 /**
  * Classifier for Artifact (tests,sources,etc)
  * 
  * @parameter default-value="${project.classifier}"
  */
 private String classifier;

 @Override
 public void execute() throws MojoExecutionException, MojoFailureException {
  try {   
   VersionResult version = getVersionArtifact() ;
   
   getLog().info(version.toString());
  } catch (VersionResolutionException e) {
   throw new MojoExecutionException("Impossible de recuperer la version de l'artefact", e);
  }
 }
 
 /**
  * Renvoi le fichier correspondant à l'artefact.
  * Prend en charge le téléchargement dans le repo local.
  *
  * @return le fichier demandé
  * @throws ArtifactResolutionException en cas d'erreur lors de la recherche de l'artefact
  */
    private VersionResult getVersionArtifact() throws VersionResolutionException {
  DefaultArtifact artifact = new DefaultArtifact(groupId, artifactId,
    classifier, type, version);
  
        VersionRequest request = new VersionRequest(artifact, RepositoryUtils.toRepos(remoteRepos), null);
        VersionResult version = repoSystem.resolveVersion(repoSession, request) ; 
  
        return version;
    } 
}

Il est nécessaire de récupérer les repositories Maven et l'interface permettant de les piloter.
L'interface s’appelle Aether.
Ensuite, le système fonctionne avec un principe de requête et de réponse.
Une requête avec l'artifactId, groupId... est créé, l'interface est appelé est le résultat est récupéré.

Attention ! Si l'artefact n'existe pas et qu'une version générique est passée en paramètre, un exception est levé. Sinon, la version renvoyée est celle donnée en paramètre.
Il n'y a donc pas moyen de savoir si l'artefact existe. En réalité si, mais vous ne trouverez rien sur ce sujet sur internet.
Pour cela, rendez-vous la semaine prochaine pour connaitre l'astuce.

Voici alors le code (très simple) :
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>org.test.maven.plugin</groupId>
    <artifactId>test-plugin-maven</artifactId>
    <packaging>maven-plugin</packaging>
    <version>1.0.0-SNAPSHOT</version>
 
 <build>
  <plugins>
   <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-plugin-plugin</artifactId>
    <version>3.0.4</version>
   </plugin>
  </plugins>
 </build>

 <dependencies> 
  <dependency>
   <groupId>org.apache.maven</groupId>
   <artifactId>maven-aether-provider</artifactId>
   <version>3.0.4</version>
   <type>jar</type>
   <scope>compile</scope>
  </dependency>

  <dependency>
   <groupId>org.apache.maven</groupId>
   <artifactId>maven-core</artifactId>
   <version>3.0.3</version>
   <scope>compile</scope>
  </dependency>
 </dependencies>

</project>

Ainsi, une version SNAPSHOT aura comme retour :
1.2.1-20121227.145212-1 @ nexus-libraries (http://xxxxxxx:8081/nxus/content/groups/public/, releases+snapshots)

lundi 1 avril 2013

Création d'un serveur en Java [2/2]

Dans l'article précédant, un rapide appeçu de comment créer un serveur en Java a été abordé.
Le principal inconvénient, c'est de ne pouvoir avoir qu'un client. Pas très pratique.

Il va donc être nécessaire de déléguer la réponse client à un thread.

Il y aura donc deux classes.
La première créer le serveur et écoute les connexions clientes.
La deuxième dialogue avec le client et est donc un thread.

La première classe :
package org.emeric.exemple.echo2;

import java.net.ServerSocket;


public class EchoServer {

    public static void main(String[] args) throws Exception {
        final ServerSocket socket = new ServerSocket(4444) ;
          
        while (true) {
            System.out.println("Waiting for client...");
            new EchoRequestHandler(socket.accept());
        }      
    }
}

La deuxième classe :
package org.emeric.exemple.echo2;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;

/**
 * Class to read request
 * 
 * @author Emeric MARTINEAU
 */
public class EchoRequestHandler extends Thread implements Runnable  {
    /**
     * Socket of client
     */
    private Socket socket ;
    
    /**
     * Constructor
     * 
     * @param s socket
     */
    public EchoRequestHandler(final Socket s)
    {
        socket = s;
        Thread t = new Thread(this) ;
        t.setDaemon(true) ;
        t.start() ;
    }
    
    /**
     * Read and response to the client
     */
    @Override
    public void run()
    {
        System.out.println("New client...") ;
        
        try {
            // Reader
            final BufferedReader in = new BufferedReader(new InputStreamReader(
                    socket.getInputStream()));
            
            // Auto flush, when println() call
            final PrintWriter out = new PrintWriter(new BufferedWriter(
                    new OutputStreamWriter(socket.getOutputStream())), true);        
        
            String str = null ;
            do {
                // Read and wait if no data
                str = in.readLine() ;
                // Print
                out.println(str) ;
            } while (str != null) ;
            
            out.close() ;
            
            socket.close() ;
        } catch (final IOException e) {
            // TODO
        }
        
        System.out.println("Bye client...");
    }
}

Vous voilà prêt à dévolloper de nombreux serveurs en Java.

lundi 25 mars 2013

Création d'un serveur en Java [1/2]

La création d'un serveur java est relativement aisé.
En effet, la JRE met à disposition un ensemble de classe effectuant le plus gros du travail.

La classe java dédier à la partie serveur est ServerSocket. Elle permet d'écouter un port de la machine, puis d'attendre des clients.

Trois méthodes vont être intéressante getInputStream(), getOutputStream(), accept().

La méthode getInputStream() permet de lire les données du client.
La méthode getOutputStream() permet d'écrire au client.
La méthode accept() permet d'attendre l'arrivé de client.

Il est donc nécessaire de :
  1. créer un serveur,
  2. attendre un client,
  3. créer un stream pour la lecture des données client,
  4. créer un stream pour l'écriture vers le client.

Voic un exemple simple d'un serveur Echo qui affiche les données envoyés par le client :
package org.emeric.exemple.echo;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class EchoServer {

    public static void main(String[] args) throws Exception {
        System.out.println("Demarrage...");
        ServerSocket s = new ServerSocket(4444);
        Socket soc = s.accept();

        // Un BufferedReader permet de lire par ligne.
        BufferedReader plec = new BufferedReader(new InputStreamReader(
                soc.getInputStream()));

        // Un PrintWriter possède toutes les opérations print classiques.
        // En mode auto-flush, le tampon est vidé (flush) à l'appel de println.
        PrintWriter pred = new PrintWriter(new BufferedWriter(
                new OutputStreamWriter(soc.getOutputStream())), true);

        String str = null;

        do {
            // lecture du message
            str = plec.readLine(); 
            // renvoi d'un écho
            pred.println(str); 
        } while (str != null);

        plec.close();
        pred.close();
        soc.close();

        System.out.println("Fin");
    }
}

Ce serveur est très basique car il n'accepte qu'un client.
Dans la prochain étape, ce serveur sera multi-threadé pour accèter plusieurs clients.

lundi 11 mars 2013

Fusion bus JMX de la JVM et JBoss

La technologie JMX dans un environnement Java, est une API qui permet de gérer une application en cours d'éxécution.

Cette API est disponible au niveau JVM. On parle du bus JMX de la JVM.
On peut voir ce bus via la jConsole.

Le serveur d'application JBoss embarque son propre serveur JMX. De ce fait, les informations publiées par défaut sur le bus JMX au sein de JBoss, ne sont pas visibles sur le bus JMX de la JVM.

Depuis JBoss 4.0.3, il est possible de les rassembler, en ajoutant les options suivantes :
# Intégrer JMX de JBoss 4 et de JVM (à partir de JBoss 4.0.3)
JAVA_OPTS="$JAVA_OPTS -Djboss.platform.mbeanserver -Djavax.management.builder.initial=org.jboss.system.server.jmx.MBeanServerBuilderImpl"

# Intégrer JMX de JBoss 5 et de JVM
JAVA_OPTS="$JAVA_OPTS -Djboss.platform.mbeanserver"
Source : JTips

lundi 4 mars 2013

Ajouter un propriété dynamiquement dans un plugin maven

Il peut être parfois utile dans un plugin maven, développé par vos soins, d'ajouter des properties qui pourront être ensuite utilisée dans le pom.xml.

Pour ce faire, rien de bien compliqué.
Il est nécessaire de récupérer l'objet projet de Maven et d'ajouter un propriété :
// injection de l'objet project
/** @parameter default-value="${project}" */
private org.apache.maven.project.MavenProject project ;

// et dans la méthode execute(), ajouter :
project.getProperties().setProperty("maPropriete", maValeur) ;

lundi 25 février 2013

Closing a connection for you. Please close them yourself

Voici un message souvent rencontré sur les projets type JEE.
Que veut dire ce mystérieux message ? Qui l'écrit ?

Tout d'abord, sachez que se message est produit par JBoss, pour être exact, ce message est écrit par le CachedConnectionManager de JBoss, qui encapsule Hibernate.
Il s'agit d'un pool de connexion aux bases de données.

Il écrit ce message, lorsqu'il supprime une connexion de son pool parce qu'elle n'est plus utilisée alors qu'elle est encore ouverte (ce qui est contradictoire donc).

Le code suivant ne pose aucun problème, car on ferme tout :
Connection aConnection = null; /* Connexion à la base de données */
Statement aStatement = null; /* La requête */
ResultSet aResultSet = null; /* résultat de la requête */

try
{
    aConnection = getConnectionDansArbreJndiJBoss() ;
    aStatement = aConnection.prepareStatement(requete) ;
    aResultSet = aStatement.executeQuery(requete);
}
catch (SQLException e)
{
  ...
}
finally
{
    aResultSet.close(); // aResultSet != null !!!!
    aStatement.close(); // aStatement != null !!!!
    aConnection.close(); // aConnection != null !!!!
}

mais enlevez la aConnection.close() et vous verrez apparaître le message !

La difficulté avec ce message est d'identifier l'élément posant problème.

lundi 11 février 2013

Connaitre les requêtes exécutées par session sous Oracle

Afin de diagnostiquer des problèmes, il peut être opportun de connaître les requêtes exécutés par une session. Voici un exemple tiré de ce site http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:497421739750 :
COLUMN STATUS format a10
SET feedback off
SET serveroutput ON
 
COLUMN username format a20
COLUMN sql_text format a55 word_wrapped
 
SET serveroutput ON size 1000000
declare
    x number;
begin
    FOR x IN
    ( SELECT username||'('||sid||','||serial#||
                ') ospid = ' ||  process ||
                ' program = ' || program username,
             to_char(LOGON_TIME,' Day HH24:MI') logon_time,
             to_char(sysdate,' Day HH24:MI') current_time,
             sql_address, LAST_CALL_ET
        FROM v$session
       WHERE STATUS = 'ACTIVE'
         AND rawtohex(sql_address) <> '00'
         AND username IS NOT NULL ORDER BY last_call_et )
    loop
        FOR y IN ( SELECT max(decode(piece,0,sql_text,NULL)) ||
                          max(decode(piece,1,sql_text,NULL)) ||
                          max(decode(piece,2,sql_text,NULL)) ||
                          max(decode(piece,3,sql_text,NULL))
                               sql_text
                     FROM v$sqltext_with_newlines
                    WHERE address = x.sql_address
                      AND piece < 4)
        loop
            IF ( y.sql_text NOT LIKE '%listener.get_cmd%' AND
                 y.sql_text NOT LIKE '%RAWTOHEX(SQL_ADDRESS)%')
            then
                dbms_output.put_line( '--------------------' );
                dbms_output.put_line( x.username );
                dbms_output.put_line( x.logon_time || ' ' ||
                                      x.current_time||
                                      ' last et = ' ||
                                      x.LAST_CALL_ET);
                dbms_output.put_line(
                          substr( y.sql_text, 1, 250 ) );
            end IF;
        end loop;
    end loop;
end;
/
Merci à Drazzib pour l'astuce.

lundi 28 janvier 2013

Debugging à distance d'un serveur JBoss

Il y a peu, une personne de l'équipe dans laquelle je travaille, souhaitait se mettre de debug sur un serveur JBoss de test afin de comprendre un dysfonctionnement.

Pour ceux que ça  intéresse, voici comment faire.
Dans un premier temps, il faut démarrer JBoss en mode debug (la technique dépend de l'environnement utilisé Windows, Distribution Linux...).
Mais attention, par défaut, le serveur attend une connexion sur le port de debug pour se lancer :
-Xrunjdwp:transport=dt_socket,address=8787,server=y,suspend=n

Il faut donc modifié le suspend pour le mettre à "n" (sauf si le problème se situe au démarrage du war ou ear).
Ensuite, il faut repérer le port de debug (ici 8787).

Lancer Eclipse en debug distant sur le port utilisé, 8787 par défaut (Run -> Debug … -> Remote Java Application).

Ensuite, dans Eclipse, dans la barre d'outil, cliquez sur l'icône de debug (sur la petit flèche) > “Debug Configuration”. Dans “Remote Application Java”, clique droit “New”. Dans l'onglet “Connect”, sélectionnez le projet “integration”. Host : “localhost”. Port : “8787”. Connection type : “Standard (Socket Attach)”. Dans l'onglet “Source”, attachez tous les projets.

mercredi 23 janvier 2013

Les distributions linux

Dans l'univers de GNU/Linux, le nombre impressionnant de distributions a de quoi dérouter.
Toutefois, si on y regarde de plus près il y a 3 distributions majeures.
Ces distributions fonctionne généralement sur du matériel assez récent.

Les autres distributions (comme la très côté en ce moment, Linux Mint) ne sont que des surcouches.

Les distributions majeures

Debian
Debian, une distribution à tout faire (serveur, poste de travail...), qui est développé par une comunauté régie par un contrat social. Elle se caractérise par un très grand nombre d'architectures supportées, une logithèque importante et un cycle de développement relativement long permettant ainsi une bonne stabilité.
Sa qualité en fait un choix privilégié par les professionnels.
Le fait qu'au début, cette distribution ne privilégiait pas l'esthétisme (userfriendly), fait qu'elle a l'image d'une distribution réservée aux experts, ce qui est aujourd'hui complètement erroné.

Slackware
Slackware, qui date de la même époque que Debian, est une distribution très stable, mais aussi très simple en outillage. Le principe de cette distribution est de laisser totalement la main à l'utilisateur, quitte à ce que celui-ci doive ouvrir le capot.
Contrairement aux autres distributions, son gestionnaire de paquets (pkgtool) se distingue par un format généraliste tar.gz.
L'intérêt de cette distribution est justement de permettre à l'utilisateur de modifier le système à la main, sans tous casser.
Elle est fort appréciée sur les serveurs.

RedHat
RedHat est une distribution qui a eu un impact historique particulier. En effet, l'entreprise gérant la distribution est la première a être entrée en bourse. Elle a aussi popularisé le gestionnaire de paquets avec gestion de dépendances (format RPM qui est très largement utilisé aujourd'hui).
Cette distribution généralise à contribué au succès de Linux.
C'est une distribution généralise.

Les autres distributions


D'autres distribution n'ont, elles, aucun lien avec les versions citées précédemment (Gentoo, Arch Linux), mais toujours pour du matériel assez récent, sauf...

SliTaz
La distribution SliTaz est un peu une distribution extraterrestre dans le monde GNU/Linux. En effet, son but est d'être très simple et très légère.
Elle fonctionne parfaitement sur de très vielle machine (testé avec un carte graphique 8Mo et 256Mo de RAM).
Par contre, il faudra oublier vos habitudes. Pas de logiciel lourd comme FireFox, LibreOffice.
C'est une distribution généraliste et propose des logiciels pour après tous les besoins. Par contre, ce sont rarement des logiciels répandu, car trop lourd.

vendredi 18 janvier 2013

Fusionner deux images en Java

La fusion de deux images en Java est assez simple.

Il faut transformer les deux images en BufferedImage.

Ensuite, rien de plus simple :
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;

// Buffer image with transparancy support (alpha chanel)
final BufferedImage image = new BufferedImage(20, 20, BufferedImage.TYPE_INT_ARGB) ;
final BufferedImage tilePicture = ....
// Graphic
final Graphics2D g2 = image.createGraphics() ;

g2.drawImage(tilePicture, object.getX(), object.getY(), null) ;

Le null peut être remplacé par un java.awt.image.ImageObserver qui permet d'être notifié lorsque l'image est prête.

lundi 14 janvier 2013

Transparence dans les images Java

Java permet de créer nativement des images transparentes.

Lorsqu'une image est créée, si le paramètre BufferedImage.TYPE_INT_ARGB est indiqué, l'image supportera l'alpha chanel, soit la gestion de transparence.

Par défaut, toute l'image est transparente.

Il peut être intéressant de créer un couleur de transparence qui sera mis dans un tableau par exemple.

Ci dessous voici un exemple complet d'image transparente et de création de couleur transparente :
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;

// Buffer image with transparancy support (alpha chanel)
final BufferedImage image = new BufferedImage(20, 20, BufferedImage.TYPE_INT_ARGB) ;

// Graphic
final Graphics2D g2 = image.createGraphics() ;

// Create color. True is to say the color value contain alpha chanel
// Here, alpha chanel is 0x00
Color c = new Color(0x00FFFFFF, true) ;

g2.setColor(color) ;

final Rectangle rect = new Rectangle(0, 0, 19, 19) ;

// All picture is transparent. Realy not necessary because at create time all picture is transparent
g2.fill(rect) ;

// No transpancy
Color c = new Color(0x000000) ;

// Draw line
g2.drawLine(0, 0, 19, 19) ;

vendredi 11 janvier 2013

Création de plugin maven [2/2]

Dans l'article précédent, il a été vu comment créer un plugin maven très simple.

Il va être abordé un plugin pas plus difficile, mais avec quelques différences (nécessite un projet, paramètre obligatoire...).

Deuxième plugin maven


Dans le même projet, une classe qui liste un répertoire va être créé :
package org.test.maven.plugin;

import java.io.File;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.project.MavenProject;

/**
 * 
 * @author Emeric MARTINEAU
 * @goal listDir
 * @requiresProject true
 */
public class ListDirectory extends AbstractMojo {

 /**
  * @parameter default-value="${project}"
  * @required
  * @readonly
  */
 private MavenProject project; 
 
 /**
  * @required
  * @parameter expression="${dir}"
  */
 private String dir; 
 
 /* (non-Javadoc)
  * @see org.apache.maven.plugin.Mojo#execute()
  */
 @Override
 public void execute() throws MojoExecutionException, MojoFailureException {
  final File directory = new File(dir) ;
  
  if (!directory.exists()) {
   getLog().error(dir.concat(" doesn't exists !")) ;
   
   return ;
  }  
  
  // List file of directory
  final File[] fileList = directory.listFiles() ;
  
  File currentFile ;
  
  // For each file
  for(int index = 0; index < fileList.length; index++) {
   currentFile = fileList[index] ;
   
   if (currentFile.isDirectory()) {
    // Current file is directory
    getLog().info("<DIR> ".concat(currentFile.getName())) ;
   } else {
    getLog().info("      ".concat(currentFile.getName())) ;
   }
  }
  
  getLog().info("Basedir = " + project.getBasedir().getAbsolutePath()) ;
 }
}
Le goal est donc listDir et la classe nécessite un projet @requiresProject true.

Le paramètre dir est rendu obligatoire par la présence dans la java doc de @required.

Le paramètre project représente le projet maven (pom.xml).

Pour fonctionner, la dépendence suivante doit être ajoutée :
<dependency>
    <groupId>org.apache.maven</groupId>
    <artifactId>maven-core</artifactId>
    <version>3.0.3</version>
    <scope>compile</scope>
</dependency>

Voici le résutat si lancé sans pom :
mvn org.test.maven.plugin:test-plugin-maven:listDir

[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] ------------------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.766s
[INFO] Finished at: Thu Dec 13 11:06:23 CET 2012
[INFO] Final Memory: 4M/245M
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.test.maven.plugin:test-plugin-maven:1.0.0-SNAPSHOT:listDir (default-cli): Goal requires a project to execute but there is no POM in this directory (D:\Logiciels\Apache\apache-maven-3.0.3\bin). Please verify you invoked Maven from the correct directory. -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MissingProjectException
Voici le résultat si lancé avec un pom mais sans le paramètre :
mvn org.test.maven.plugin:test-plugin-maven:listDir

[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building toto 1.0.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- test-plugin-maven:1.0.0-SNAPSHOT:listDir (default-cli) @ toto ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.359s
[INFO] Finished at: Thu Dec 13 11:17:21 CET 2012
[INFO] Final Memory: 4M/245M
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.test.maven.plugin:test-plugin-maven:1.0.0-SNAPSHOT:listDir (default-cli) on project toto: The parameters 'dir' for goal org.test.maven.plugin:test-plugin-maven:1.0.0-SNAPSHOT:listDir are missing or invalid -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/PluginParameterException
Voici le résultat lancé correctement :
mvn org.test.maven.plugin:test-plugin-maven:listDir -Ddir=/temp/

[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building toto 1.0.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- test-plugin-maven:1.0.0-SNAPSHOT:listDir (default-cli) @ toto ---
[INFO]       1.jn1
[INFO]       intro.jn1
[INFO] <DIR> jill-binaries
[INFO] <DIR> jill-tiles
[INFO] <DIR> MAVEN
[INFO]       test.png
[INFO] Basedir = /temp
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.578s
[INFO] Finished at: Thu Dec 13 11:17:54 CET 2012
[INFO] Final Memory: 6M/309M
[INFO] ------------------------------------------------------------------------

L'objet MavenProject


La partie de code de la classe :
/**
 * @parameter default-value="${project}"
 * @required
 * @readonly
 */
private MavenProject project;
permet de récupéré via la propriété maven ${project} le projet maven.
Il est donc possible d’accéder à toute les propriétés du projet.
L'objet MavenProject est disponible dans la librairie maven-core.

Il ne reste plus qu'à se reporter à la documentation officiele : Maven - Guide to Developing Java Plugins.