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.

dimanche 21 octobre 2012

Créer des propriétés dynamiquement au sein d'un pom Maven

Pour compléter mon dernier post, voici comment résoudre le problème de version identique dans un manifest.

Il existe un plugin maven qui est une véritable boite à outils. Ce plugin vous aidera de très nombreuses fois, une fois que vous l'aurez découvert.
Ce plugin répond au doux nom de build-helper-maven-plugin et est disponible sur Codehaus.
Il contient un goal très intéressant qui permet de créer une variable dynamiquement en lui appliquant une expression régulière (ou rationnelle).
Voici dans notre cas, l'utilisation que nous pourrions en faire :
<plugin>
 <groupId>org.codehaus.mojo</groupId>
 <artifactId>build-helper-maven-plugin</artifactId>
 <version>1.7</version>
 <executions>
  <execution>
   <id>create-property-versionNonSnapshot</id>
   <goals>
    <goal>regex-property</goal>
   </goals>
   <configuration>            
    <name>project.versionNonSnapshot</name>
    <value>${project.version}</value>
    <!-- http://jira.codehaus.org/browse/MBUILDHELPER-34 -->
    <regex>(.*)-(SNAPSHOT)</regex>
    <replacement>$1</replacement>
    <failIfNoMatch>false</failIfNoMatch>
   </configuration>
  </execution>
 </executions>
</plugin>
Une variable project.versionNonSnapshot va être crée a partir de la valeur de project.version.
Ne sera gardé que la première partie soit la version sans snapshot.

A présent, si on reprend le code du prost précédent :
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-war-plugin</artifactId>
            <configuration>
                <archive>
                    <manifest>
                        <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                        <manifestEntries>
                            <Specification-Version>${project.versionNonSnapshot}</Specification-Version>
                        </manifestEntries>
                    </manifest>
                </archive>
            </configuration>
        </plugin>
    </plugins>
</build>
On obtient :
Specification-Version : 1.0.0
Implementation-Version : 1.0.0-SNAPSHOT

Je vous invite à découvrir ce plugin, véritable couteau suisse du développeur maven.

mardi 16 octobre 2012

Personnaliser le manifest d'un jar ou d'un war

Si vous créez un jar utiltaire à destination d'équipe utilisatrice, vous voudrez certainement que la commande de lancement du jar se résume à :
java -jar monjar.jar

Pour ce faire, il est possible avec maven, lors de la phase de package, d'ajouter des informations dans le manifest du jar.
Avec l'entrée Main-Class: com.maventest.App, une classe peut-être lancée automatiquement.

Pour cela, il est nécessaire d'ajouter dans le pom :
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <configuration>
                <archive>
                    <manifest>
                        <mainClass>com.maventest.App</mainClass>
                    </manifest>
                </archive>
            </configuration>
        </plugin>
    </plugins>
</build>

Ce principe peut être reprit aussi pour un war.

Ce qui est intéressant, c'est d'ajouter dans le manifest, l'entrée Specification-Version et Implementation-Version.
Cela permet de connaitre la version logique d'un service par exemple, et la version réel.
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-war-plugin</artifactId>
            <configuration>
                <archive>
                    <manifest>
                        <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
                        <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                    </manifest>
                </archive>
            </configuration>
        </plugin>
    </plugins>
</build>
Ce qui donne :
Specification-Version : 1.0.0-SNAPSHOT
Implementation-Version : 1.0.0-SNAPSHOT
Inconvénient, la même version est répétée deux fois.
Dans le prochain post, j'expliquerais comment résoudre ce problème.