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.

lundi 7 janvier 2013

Création de plugin maven [1/2]

Maven permet de facilement (une fois son fonctionnement compris) de construire, livrer, installer un projet.

Maven fonctionne sur la base de plugin. Toutefois, il n'est pas rare qu'un besoin particulier s'exprime et qu'aucun plugin n'y réponde.
Il faudra alors écrire soit même un plugin.

L'écriture de plugin est relativement aisée. C'est plutôt ce que doit faire le plugin qui n'est pas trivial.

Il existe deux types de plugin. Celui qui nécessite un projet pour s'exécuter (donc un pom.xml), comme par exemple maven-compiler-plugin.
Et celui qu'il est possible de lancer sans projet.

Premier plugin maven

Pouc commencer, un plugin sans projet pas être créé. Il affichera un message dans les logs. Ce message sera paramètrable (donc facultatif).

Voici la classe du plugin :
package org.test.maven.plugin;

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

/**
 * 
 * @author Emeric MARTINEAU
 * @goal hello
 * @requiresProject false
 */
public class HelloWorld extends AbstractMojo {

 /**
  * @parameter expression="${hello.str}" default-value="Hello World!"
  */
 private String helloStr;  
  
 @Override
 public void execute() throws MojoExecutionException, MojoFailureException {
  getLog().info(helloStr) ;
 }

}
Elle hérite nécessairement de la classe org.apache.maven.plugin.AbstractMojo.
Sera implémenté la méthode execute().

La configuration du plugin se fait via la JavaDoc et certaines balises @.
Ici pour la classe il y a le goal définit par @goal hello et si un projet est requis @requiresProject false.
Il est possible d'indiquer la phase avec @phase.

La classe accèpte un paramètre hello.str configuré avec @parameter expression="${hello.str}" default-value="Hello World!".
Il peut être valoriser en ligne de commande via -Dhello.str=xxxx ou dans un pom avec :
<configuration>
 <hello.str>Bonjour le monde !</hello.str>
</configuration>
Si aucun paramètre n'est communiqué, le paramètre prendra la valeur Hello World!.

Et son pom associé :
<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>

    <dependencies>
        <dependency>
            <groupId>org.apache.maven</groupId>
            <artifactId>maven-plugin-api</artifactId>
            <version>3.0.4</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugin</groupId>
                <artifactId>maven-plugin-plugin</artifactId>
                <version>3.2</version>
                <configuration>
                    <goalPrefix>testPluginMaven</goalPrefix>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Exemple d'exécution sans paramètre :
mvn org.test.maven.plugin:test-plugin-maven:hello

[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- test-plugin-maven:1.0.0-SNAPSHOT:hello (default-cli) @ standalone-pom ---
[INFO] Hello World!
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 5.297s
[INFO] Finished at: Thu Dec 13 11:01:56 CET 2012
[INFO] Final Memory: 4M/245M
[INFO] ------------------------------------------------------------------------
Exemple d'exécution avec paramètre :
mvn org.test.maven.plugin:test-plugin-maven:hello -Dhello.str="bonjour ca va ?"

[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- test-plugin-maven:1.0.0-SNAPSHOT:hello (default-cli) @ standalone-pom ---
[INFO] bonjour ca va ?
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.234s
[INFO] Finished at: Thu Dec 13 11:55:40 CET 2012
[INFO] Final Memory: 6M/309M
[INFO] ------------------------------------------------------------------------
Le fait que le plugin est exécuté sans pom se remarque à standalone-pom juste au dessus du texte affiché par le plugin.

lundi 31 décembre 2012

Différence entre la classe Properties et ResourceBundle

Lorsqu'il est nécessaire de mettre dans un fichier des paramètres de configuration, deux possibilités principales s'offrent, en Java, au développeur.

Bien souvent, un fichier de configuration sera de la forme suivante :
clef=valeur
Les deux classes Properties et ResourceBundle permettent de lire ce type de fichier. Mais quelle est donc la différence ?

Le cas Properties

La classe Properties charge son contenu à partir d'un flux. Exemple :
Properties prop = new Properties() ;
     
try {
 prop.load(this.getClassLoader().getResourceAsStream("file.properties")) ;   
} catch (IOException e) {
 e.printStackTrace() ;
}
Il apparait que le fichier doit être chargé par le développeur.
Il n'y a donc qu'un fichier.
Si pour une raison, le fichier change suivant le contexte, le développeur doit indiquer un autre nom.

Le cas ResourceBundle

La classe ResourceBundle permet la localisation, c'est à dire permet de prendre un fichier en fonction de certains paramètres.

La localisation permet de définir des valeurs suivant le contexte.

Exemple :
Locale locale_fr = new Locale("fr", "FR") ;
Locale locale_us = new Locale("us", "US") ;
A présent, un fichier de paramétrage doit être chargé. Ce fichier dépend de l’environnement (langue) car il contient les libellées d'une page web.
Locale locale_fr = new Locale("fr", "FR") ;
ResourceBundle messages = ResourceBundle.getBundle("messages", locale_fr) ;
La classe ResourceBundle va donc chercher le premier fichier dans l'ordre suivant :
messages_fr_FR.properties
messages_fr.properties
messages.properties
Il est a noter que la classe ResourceBundle ajoute automatiquement les constantes et l'extension du fichier.

Ce principe peux aussi s'appliquer à des environnements techniques :
Locale locale_unix = new Locale("UNIX");
Locale locale_windows = new Locale("WINDOWS");
Dans lequel pourra être mis, la commande pour invoquer un shell par exemple.

Conclusion

La classe Properties sera principalement utilisée pour stocker des données sans contexte (la liste d'un id avec une url par exemple) alors que ResourceBundle sera plus utilisé pour des données qui changent suivant un contexte/environnement.

vendredi 14 décembre 2012

Indiquer une classe de lancement par défaut dans un jar

Régulièrement, sur les forums, des personnes se demandent comment, il est possible de définir une classe de lancement par défaut dans un jar.

Pour indiquer à la JVM un classe de lancement par défaut, il faut dans le fichier META-INF/MANIFEST.MF :
Main-Class: org.test.ClassMain
Si vous utiliser Maven, il est posssible directement avec le plugin maven-jar-plugin de le spécifier 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>com.maventest</groupId>
 <artifactId>aproject</artifactId>
 <packaging>jar</packaging>
 <version>1.0-SNAPSHOT</version>
 <name>aproject</name>
 <url>http://maven.apache.org</url>
 <build>
  <plugins>
   <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <configuration>
     <archive>
      <manifest>
       <mainClass>org.test.ClassMain</mainClass>
      </manifest>
     </archive>
    </configuration>
   </plugin>
  </plugins>
 </build>
</project>

mardi 27 novembre 2012

Forcer maven à vérifier qu'une version release n'a pas été modifiée

Maven repose sur un principe pour la production des artefacts (war, jar, ear...).
Il existe des versions en cours de développement (SNAPSHOT) et des versions définitives (release).

Les versions en cours de développement, par définition, change régulièrement avec le même numéro de vesion (1.2.0-SNAPSHOT).
Il se peut qu'il y en ait plusieurs par jour de produites.

Les versions release, elles au contraire sont livrées qu'une seule fois. Si elles doivent être relivrées, c'est qu'il y a une correction faite donc, le principe de versioning fait que le numéro de version va changer.

De ce fait, lorsque maven s'exécute, il vérifie dans son repository local si la dépendance existe.
Si c'est un version SNAPSHOT, elle va vériffier si une nouvelle version a été produite.
Si c'est le cas, maven la télécharge.

Si la dépendance est une version release et que cette version n'est pas dans le repository local, elle sera téléchargée.
Toutefois, si la version est déjà dans le repository local, maven ne va pas vérifier qu'elle a changer.

Dans les fait, il arrive régulièrement, pour diverses raisons, de devoir reproduire une version release.
Alors comment faire pour que maven retélécharge la dépendance ?

Si on se fit à la documentation, en option de lancement, maven dispose de :
 -U,--update-snapshots             Forces a check for updated releases and
                                   snapshots on remote repositories
En réalité, ça ne fonctionne que pour les versions snapshot (testé sur maven 2.1, 2.2 et 3.0.3).

La seule solution est de purger manuellement le repository local, soit par une commande shell, soit par le goal purge-local-repository de maven-dependency-plugin.

mardi 13 novembre 2012

Décompresser un fichier Zip

Je souhaitais, via un programme ou code java décompresser un fichier Zip.
J'ai donc dans un premier temps cherché du côté de l'api standard Java (http://docs.oracle.com/javase/6/docs/api/java/util/zip/package-summary.html).
Le problème, c'est qu'il n'y a rien d'automatique.

Heureusement pour moi, MkYong (http://www.mkyong.com) s'est déjà penché sur la question.
Si vous ne connaissez pas ce site, je vous le conseil, régulièrement ses publications sont forts utiles.

J'ai apporté le support des répertoires dans le zip pour les créer autmatiquement et corrigé une erreur.
/**
 * 
 */
package org.util.zip ;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/**
 * @author MkYong & MARTINEAU Emeric
 * http://www.mkyong.com/java/how-to-decompress-files-from-a-zip-file/
 * 
 */
public class UnZip {
 /**
  * Unzip it
  * 
  * @param zipFile
  *            input zip file
  * @param output
  *            zip file output folder
  */
 public void unZipIt(String zipFile, String outputFolder) {
  try {

   // create output directory is not exists
   File folder = new File(outputFolder) ;
   
   if (!folder.exists()) {
    folder.mkdir() ;
   }

   // get the zip file content
   ZipInputStream zis = new ZipInputStream(
     new FileInputStream(zipFile)) ;
   // get the zipped file list entry
   ZipEntry ze = zis.getNextEntry() ;

   String fileName ;
   
   while (ze != null) {
    fileName = ze.getName() ;
    
    if (ze.isDirectory()) {
     System.out.println("Creating directory : ".concat(fileName)) ;
     
     new File(outputFolder + File.separator + fileName).mkdirs() ;
    } else {
     extractFile(outputFolder, zis, fileName) ;
    }

    zis.closeEntry();
    ze = zis.getNextEntry() ; 
   }

   zis.close();

   System.out.println("Done");

  } catch (IOException ex) {
   ex.printStackTrace();
  }
 }
 
 private void extractFile(final String outputFolder, final ZipInputStream zis, 
   final String fileName) throws IOException {
  final byte[] buffer = new byte[1024] ;
  
  final File newFile = new File(outputFolder + File.separator
    + fileName);

  System.out.println("file unzip : " + newFile.getAbsoluteFile());

  // create all non exists folders
  // else you will hit FileNotFoundException for compressed folder
  new File(newFile.getParent()).mkdirs();

  final FileOutputStream fos = new FileOutputStream(newFile);

  int len;
  
  while ((len = zis.read(buffer)) > 0) {
   fos.write(buffer, 0, len);
  }

  fos.close();
   
 }
}
Un problème étrange toutefois se produit. Si vous créer un fichier zip avec 7Zip il arrive qu'il y ai un problème de CRC

Un autre exemple de qualité : http://www.java-forums.org/blogs/java-io/973-how-work-zip-files-java.html

vendredi 2 novembre 2012

Vider les caches Oracle

Depuis Oracle 10g, pour vider la zone Buffer Cache de la SGA :
alter system flush buffer_cache;
Pour vider le Shared Pool :
alter system flush shared_pool;
Rappels :

  • Shared Pool : zone mémoire Oracle qui stocke les plans d'exécutions, le dictionnaire de données et les structures de contrôle
  • Buffer Cache : zone mémoire Oracle qui stocke blocks de données utilisateurs (c-a-d cache disque)
Merci à Drazzib pour l'astuce.