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.

jeudi 11 octobre 2012

Rechercher un champs dans un des formulaires de la page

Il y a peu, on m'a demander d'aider quelqu'un a faire un code JavaScript pour rechercher un champs dans un formulaire.
Il fallait aussi que le code puisse renvoyer le formulaire si le nom cherché était le nom d'un formulaire.

Voici un exemple :
// Recherche par le nom donc l'element chercher est un input
function rechercherDansFormulaire(nomChamp) {                
    // Fonction qui affiche le nom de tous les formulaires
    // et de tous les les éléments d'un document HTML
    // Boucle tous les formulaire
    for (var i = 0; i < document.forms.length; i++) {
        // S'il s'agit d'un formulaire
        if (nomChamp == document.forms[i].name) {
            return document.forms[i] ;
        }
        
        // Boucle tous les éléments du formulaire i
        for (var l = 0; l < document.forms[i].elements.length; l++) {
            if (nomChamp == document.forms[i].elements[l].name) {
                return document.forms[i].elements[l] ;
            }
        }
    }

    return null ;
}

mercredi 3 octobre 2012

Personnaliser le filtre d'un JFileChooser

Afin de pouvoir personnaliser les filtres dans un JFileChooser, il est nécessaire de créer une classe héritant dans FileFilter.
Cette classe va retourner une description qui sera affichée dans la boite de dialogue en bas et surtout la méthode accept() qui va indiquer au JFileChooser s'il faut prendre en compte (et donc afficher dans la boite de dialogue) le fichier.
import java.io.File;

import javax.swing.filechooser.FileFilter;


public class FileExtension extends FileFilter {
 private String extension ;
 
 private String description ;
 
 public FileExtension(final String extension, final String description)
 {
  if (extension != null)
  {
   this.extension = extension.toLowerCase() ;
  }
  
  this.description = description ;
 }
 
 @Override
 public boolean accept(File f) {
  return (testFile(f.getAbsolutePath()) || f.isDirectory()) ;
 }

 @Override
 public String getDescription() {
  return description ;
 }

 private boolean testFile(final String fileName)
 {
  return ((extension != null) && (!"".equals(extension)) &&
    fileName.toLowerCase().endsWith(extension)) ;
 }
}
Ensuite, cette objet doit être ajouter à la liste des filtres du JFileChooser :
//Create a file chooser
fc = new JFileChooser() ;

// Delete previous filter (by default *.*)
fc.resetChoosableFileFilters();
fc.addChoosableFileFilter(new FileExtension("*.exe", "Executable file"));

lundi 1 octobre 2012

Les subtilités des versions JBoss

Voici un retour d'expérience sur une migration JBoss 3.5 vers 4.3.
Je n'avais encore jamais participé à aucune migration de ce type.
Nous avions fait des tests de chaque module et tout semblait correct. Pas de modification spécifique en vue.
Erreur !

Lors du test d'intégration, nous avons donc mis plusieurs WAR dans la même instance JBoss.
Ces modules utilisaient les mêmes librairies mais les embarquaient chacune dans leur WAR.
En effet, ces librairies contenaient des singletons propre à chaque application.

Phénomène étrange, les WAR semblaient partager le même classloader.

En effet, il n'est possible d'indiquer à JBoss, de ne pas partager le même classloader (java2ParentDelegation = false) que pour les EAR, dans sa configuration générale.
Pourtant, en JBoss 3.5 cela fonctionnait, alors que se passe-t-il ?

Du côté du fichier jboss-web.xml qui embarqué dans chaque WAR permet de configurer le comportement JBoss, tout semblait correct :
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE jboss-web PUBLIC "-//JBoss//DTD Web Application 2.3//EN"
"http://www.jboss.org/j2ee/dtd/jboss-web_3_2.dtd">

<jboss-web>
 <class-loading>
  <loader-repository>
   nom_war:loader=nom.war
   <loader-repository-config>
    java2ParentDelegation=false
   </loader-repository-config>
  </loader-repository>
 </class-loading>

</jboss-web>
Après, une plongée dans la documentation de JBoss, un commentaire me met la puce à l'oreille.
Ce commentaire indique une particularité de JBoss 4.2.x à 5.0.0.

Le format que fichier à changé, pour ces versions, il faut :
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE jboss-web PUBLIC "-//JBoss//DTD Web Application 2.3//EN"
"http://www.jboss.org/j2ee/dtd/jboss-web_4_2.dtd">

<jboss-web>

   <!-- Fonctionne uniquement avec JBoss de 4.2.x a 5.0.0 -->
   <loader-repository> 
      nom_war:archive=nom.war
   <loader-repository-config>
    java2ParentDelegation=false
   </loader-repository-config>
   </loader-repository> 

</jboss-web>
Attention, à partir de la version 6, on revient comme en 3.5 !

lundi 24 septembre 2012

Maven, des phases et des goals

De nombreux utilisateurs de maven ne comprennent pas bien l'esprit maven.
En effet, Maven ne se comporte pas comme du Ant ou un Makefile.

Maven, il faut bien l'avouer, n'a rien inventé. Il s'est inspiré de la vie. Encore fallait-il y penser.
Un produit (jar, war, un téléphone...) est régit par la loi de la nature. Il y a un cycle. De la naissance à la retraite (on va s'arrêter là c'est mieux).

Suivant le produit, le cycle de vie n'est pas le même. Maven définit des produits standard, à savoir des packaging : pom, jar, maven-plugin, ejb, war, ear, rar, par.
Tous c'est package ont le même cycle de vie.

Un cycle de vie c'est quoi ?
Toujours comme la vrai vie. Un être humain sera : foetus, bébé, enfant, adolescent, adulte, retraité.
Ce sont les phases de la vie.
Un jar aura donc comme phases dans son cycle de vie :
validate
initialize
generate-sources
process-sources
generate-resources
process-resources
compile
process-classes
generate-test-sources
process-test-sources
generate-test-resources
process-test-resources
test-compile
process-test-classes
test
prepare-package
package
pre-integration-test
integration-test
post-integration-test
verify
install
deploy
D'abord, il y a validation de la structure du pom.xml, puis initialisation...

Et comme dans la vrai vie, il y a dans chaque phase, des étapes appelées goal dans maven.
Je pense qu'une des incompréhension vient d'une mauvaise traduction du mot goal (ce n'est pas un but).

Si on prend un être humain, dans la phase adulte, il passe son permit voiture, achète une voiture, s'installe en couple, se marie, achète une maison... Ce sont bien des étapes.
Les étapes (goal) sont dans un ordre bien définit. Cet ordre est lui aussi définit dans le cycle de vie.

Peut-on redéfinir un cycle de vie ?
Oui, pour cela, il faut créer un nouveau packaging (exemple zip). Mais là, c'est une autre histoire.

Donc, si on résume :
Un cycle de vie contient des phases, qui elles même contiennent des goals.

Une chose à se rappeler, si on lance un plugin dans un goal donné (qui donc appartient à une phase), toutes les phases et goals se situant avant seront exécutés.

Il faut quand même savoir qu'il est possible d'avoir des goals non attachés à une phase. Mais bon, c'est pour plus tard.

lundi 17 septembre 2012

Changer de schéma par défaut sous Oracle

Il existe deux méthodes d'accès aux tables dans une requête SQL :

  • sans préfixe : “select * from table” : pour les tables du schéma de l'utilisateur
  • avec préfixe : “select * from autre_schema.table” : pour les tables d'un autre schéma (sur lequel l'utilisateur dispose des bons droits)

Lors de chaque connexion à une base de données Oracle, un utilisateur est donc affecté au schéma qui porte le même nom que son identifiant de connexion. Les accès aux tables sans préfixes (“from table”) sont alors effectués dans ce schéma par défaut.

Description du processus coté Oracle :

  • Connexion avec l'utilisateur TOTO
  • Affectation du schéma TOTO comme “current_schema” de la session
  • Réception d'une requpete “select * from table” qui est transformée en “select * from TOTO.table”

Problème : si on se connecte avec l'utilisateur “system”, et que l'on souhaite faire une requête sur les données de TOTO, on doit ajouter le préfixe correspondant au schéma devant chaque accès aux tables. Ceci peut s'avérer très long et risqué sur des requêtes complexes.

Solution : il est possible changer, pour la session courante, de “current_schema”. Après la connexion, il suffit d'exécuter la requête
alter session set current_schema=<IDENTIFIANT_DU_SCHEMA_CIBLE>
pour que les accès sans préfixes soient fait sur le schéma <IDENTIFIANT_DU_SCHEMA_CIBLE>.

Merci à Drazzib pour l'astuce.

vendredi 7 septembre 2012

Créer dynamiquement une image en Java

Il y a peu, j'ai voulu créer un ripper d'image du jeu mythique des années 90, "Jill of the Jungle".
J'ai trouvé sur le site www.shikadi.net une explication sommaire des fichiers d'image.
Il me fallait donc créer une image dynamiquement et l'enregistrer.

J'ai cherché de la documentation à ce sujet. J'ai trouvé l’officielle d'Oracle, très sommaire, et des exemples sur internet un peu trop complexe.

L'idée est simple : créer une image, dessiner un pixel et l'enregistrer.

Voici l'exemple ci-dessous.
BufferedImage image =
    new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB) ;

Graphics2D g2 = image.createGraphics() ;
  
BufferedImage image =
    new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB) ;

Graphics2D g2 = image.createGraphics() ; 

g2.setColor(color) ;
                  
g2.drawLine(x, y, x, y) ;

outputfile = new File(fileName) ;
ImageIO.write(bi, "png", outputfile);
Création de l'image (un BufferImage puisque tout est en mémoire), de type Alpha (transparence), red, green, blue.
Création d'un graphic (objet permettant le dessin).
Définition de la couleur du pinceau.
Dessin du pixel.

Graphic2D ne permet pas de dessiner un pixel, via une méthode. On dessine donc une ligne, d'un point.

A la fin, est utilisé la classe ImageIO pour enregister le buffer.

C'est aussi simple que ça

lundi 27 août 2012

Les nombres non signés en Java

Voici la liste des types primitifs en Java

TypeSignificationTaille (en octets)Plage de valeurs acceptées
charCaractère Unicode2'\u0000' → '\uffff' (0 à 65535)
byteEntier très court1-128 → +127
shortEntier court2-32 768 → +32 767
intEntier4-231≈-2,147×109 → +231-1≈2,147×109
longEntier long8-263≈-9,223×1018 → +263-1≈9,223×1018
floatNombre réel simple4±2-149≈1.4×10-45 → ±2128-2104≈3.4×1038
doubleNombre réel double8±2-1074≈4,9×10-324 → ±21024-2971≈1,8×10308
booleanValeur logique (booléen)1true (vrai), ou false (faux)

Ce que l'on constate, c'est qu'il n'y a que des nombres non signé.
Si vous lisez un fichier binaire et que vous récupérer un octet (byte), naturellement, vous vous attendez à un nombre non signé.

Mais, voilà, Java ne vois pas les choses de la sorte.
Alors comment avoir la valeur d'un byte non signé ?
L'astuce est plutôt simple quoique déconcertante :
final DataInputStream file = ...
int byte0 = file.readByte() & 0xff ;
En appliquant un masque 0xff et en le mettant dans un int, on force Java à ne pas interpréter le contenu du byte.
Exemple, si le byte = -127, le signe disparait et la valeur voulue (255) est récupérée.

jeudi 23 août 2012

Analyser le trafic réseau depuis l'IDE Eclipse

Lorsqu'une personne développe des WebService ou qu'elle y fait appel, il peut être intéressant de "voir" le flux SOAP pour ensuite, par exemple, l'utiliser via SOAPUI.

Si vous n'êtes pas un habitué de Wireshark, vous serez certainement séduit par une possibilité d'Eclipse.

L'idée est simple : ouvrir un port sur lequel le client se connecte et tout ce qu'il envoit est redirigé vers le serveur.
Et réciproquement.

Une fois Eclipse lancé, allez dans le menu Window > Show View > Other.
Dans la boite de dialogue, entrez "TCP".


Dans le rectangle blanc, en haut, click droit "Properties".

Dans la nouvelle fenêtre, cliquez sur "Add".

Le "Local monitoring port" correspond au port d'écoute, sur lequel le client va se connecter.
La zone "Monitor" correspond au server vers lequel rediriger.

Vous pouvez choisir de démarrer ce port avec le lancement d'Eclipse (Start monitor automatically).

Il n'y a plus qu'à lancer votre serveur, puis votre client et admirer les requêtes apparaitre.

mardi 21 août 2012

Rechercher un élément récurcivement dans l'arbre DOM

Pour le fun, aujourd'hui, voici un code qui recherche un input à partir d'une balise.
La recherche se fait dans la balise pour dans les parents, jusqu'à arriver au document lui même (root.parentNode == null).
function chercherInput(nom, root) {
    // Remonte jusqu'a trouve l'input
    var allRows = root.getElementsByTagName('input') ;
    var item = null ;

    while ((root != null) && (item == null)) {
        for (var i = 0; i < allRows.length; i++) {
            if (allRows[i].name.match(nom) != null) {
                item = allRows[i] ;
                break ;
            }
        }

        root = root.parentNode ;
        allRows = root.getElementsByTagName('input') ;
    }

    return item ;  
}

function selectNumber(root) {
    var item = chercherInput('^toto\\[[0-9]+\\]\\.numero$', root) ;

    if (item != null) {
        item.value = 'coucou' ;
    }
}
Code HTML :
<div onclick="selectNumber(this)">Cliquez moi !</div>

vendredi 17 août 2012

Tuer une session utilisateur Oracle

SELECT s.username, s.machine, s.osuser, s.sid, s.serial#
  FROM v$session s, v$process p, sys.v_$sess_io si WHERE
  s.paddr = p.addr(+) AND
  si.sid(+) = s.sid AND
  s.type='USER' AND
  s.username='toto';
 
ALTER system KILL session 'session-id,session-serial'
Merci à Drazzib pour l'astuce.

mardi 14 août 2012

Connaitre le contexte applicatif d'un EAR

Rien de très technique, juste une petite astuce que j'ai indiqué à un débutant.
Celui-ci cherchait quelle URL mettre dans son navigateur pour appeler un WAR se trouvant dans un EAR.
Dans l'EAR, il y a un répertoire META-INF. Dedans, le fichier application.xml décrit le contexte de chaque module, donc l'URL du WAR.

jeudi 9 août 2012

Format de date dans un flux SOAP

Aujourd'hui, une personne cherchait via SOAPUI, a faire un appel à un WebService.
Un des paramètres d'entrée est une date.
Mais la personne ignorait le format pour une date en SOAP.

Tout d'abord, rappelons que le format de date/heure dans un flux SOAP se base sur le standard ISO 8601, sans toutefois supporter le format de base CCYYMMDDThhmmss.

Il faut se reporter au chapitre 4.5 de la norme ISO 8601 :
[-]CCYY-MM-DDThh:mm:ss[Z|(+|-)hh:mm]
Le Z signifie Zulu time (Z time) soit UTC.
Sinon, il est possible de spécifier le décalage horaire avec + ou -

Voici des exemples :
2001-10-26T21:32:52

// Heure Paris
2001-10-26T21:32:52+02:00

// UTC
2001-10-26T19:32:52Z

// Sans décalage mais pas UTC
2001-10-26T19:32:52+00:00

-2001-10-26T21:32:52

// Avec milliseconde
2001-10-26T21:32:52.12679

lundi 6 août 2012

Activer le suivi de lien symbolique dans JBoss

L'astuce du jour, est une petite astuce qui peut être pratique si vous effectuez une migration (exemple JBoss 3.5 vers JBoss 4.3).
Lors d'une migration de JBoss en version majeur, bien souvent, il y a des paramètres "cachés" ou du moins qu'on a jamais l'habitude de modifier qui change.
C'est ce qui m'est arrivé chez un client.

En effet, le WAR déployé, contenait des liens symboliques (donc sous Unix). En JBoss 3 pas de problème, mais en JBoss 4, problème.

Et oui, dans deploy/jboss-web.deployer/context.xml, un paramètre devait être modifié :
<Context cookies="true" crossContext="true" allowLinking="true">

vendredi 3 août 2012

Lister les sessions utilisateurs avec Oracle

Pour lister les sessions utilisateurs, connecter vous en administrateur sur Oracle et exécuter la requête suivante :
s.username utilisateur oracle de connexion à rechercher.
SELECT * FROM v$session s, v$process p, sys.v_$sess_io si WHERE
  s.paddr = p.addr(+) AND si.sid(+) = s.sid AND
  s.type='USER' AND
  s.username='toto'
ou
SELECT * FROM v$session s, v$process p, sys.v_$sess_io si WHERE
  s.paddr = p.addr(+) AND
  si.sid(+) = s.sid AND
  s.type='USER';
Merci à Drazzib pour l'astuce.

mardi 31 juillet 2012

Mettre à jour des propriétaires de fichier sous Unix (niveau avancé)

Il arrive parfois, des malheurs. Dernièrement, il m'ai arrivé, au travail, sous Linux en root de faire un rm -rf /*.
Ceux connaissant bien Unix comprenne l'étendu de la catastrophe !
Heureusement, seuls les répertoires "sbin", bin et etc on été supprimés.

Fort chanceusement, les postes sont tous des clones.
J'ai donc recopié les répertoires en question.

Oui mais voilà, le répertoire etc contient le fichier passwd et group qui associe UID et nom d'utilisateur.
De ce fait, je me retrouve avec des noms d'utilisateur inversé...

J'aurais pu m'arranger avec ces fichiers, mais les risques d'erreurs étaient trop nombreux.
Mieux vaut utiliser chmod.

D'accord, mais comment savoir les fichiers concernés et comment modifier juste l'utilisateur si c'est l'utilisateur qui est mauvais, ou simplement le groupe ?
Nous allons partir du principe que l'utilisateur toto doit devenir l'utilisateur emeric.

Avant d'écrire un script, il est bon d'essayer de le faire à la main pour comprendre ce qui va se passer.

La commande ls -l permet de lister les fichiers et répertoires avec leurs informations. Le premier problème vient du fait que si c'est un répertoire, le contenu est listé.
La parade ? ls -l -d, -d pour afficher un répertoire comme s'il s'agissait d'un fichier.
$> ls -l
 drwxr-xr-x   2 root  wheel      512 Aug 13  2008 altroot/
 drwxr-xr-x   2 root  wheel     1024 Aug 13  2008 bin/
 -rw-r--r--   1 root  wheel    43348 Feb 20 01:52 boot
 -rw-r--r--   1 root  wheel  6861562 Aug 13  2008 bsd
 -rw-r--r--   1 root  wheel  5534077 Aug 13  2008 bsd.rd
 drwxr-xr-x   3 root  wheel    20992 Apr  3 08:29 dev/
 drwxr-xr-x  19 root  wheel     2560 Apr  3 08:29 etc/
$>
On a donc comme information le user, le group et le nom du fichier.
La difficulté vient du fait que les champs sont séparés avec un ou plusieurs espaces.

Le contournement va faire appel à une astuce du BASH, les tableaux et IFS (internal field separator).

BASH ne connait que les chaînes de caractères. Pour lui, un tableau est simplement une chaîne avec des séparateurs.
Pour créer un tableau, il suffit de déclarer :
monTableau=( 1 2 3 )
Nous allons redéfinir l'IFS car par défaut, il n'est pas initialisé comme on le souhaite, et des effets de bord risque d'apparaître.
On le définit comme un espace :
IFS=' '
Si nous prenons une ligne pour faire un test à la main, voici ce qu'on peut faire :
$> IFS=' '
$> TAB=( $(echo ' -rw-r--r--   1 root  wheel    43348 Feb 20 01:52 boot') ) 
$> echo ${#TAB[*]} / ${TAB[*]}
9/-rw-r--r-- 1 root wheel 43348 Feb 20 01:52 boot
On voit donc que les multiples espaces ne sont pas pris en compte. En effet, BASH initialise une "case" de tableau que s'il y a une valeur.

Il sera donc nécessaire de faire le ls -l -d sur chaque fichier unitairement. Pour cela, find sera utilisé. On utilisera la capacité de find pour exécuter le ls sur les fichiers trouvés va l'option -exec.

Les éléments sont suffisants pour créer déjà ce premier script
#!/bin/sh

# Sauvegarde l'ancien field separator
OLDIFS="$IFS";

# Saut de ligne est le separateur
IFS_NL=$'\n'
IFS_SPACE=' '

IFS="$IFS_NL"

# Recherche des fichiers uniquement
for fichier in $(find $1 -exec ls -l -d {} \;)
do
    # Definit le fiel separator en tant qu'espace
    IFS="$IFS_SPACE" ;

    fichier_propriete=( $(echo $fichier) )

    username=${fichier_propriete[2]}
    groupname=${fichier_propriete[3]}
    filename=${fichier_propriete[8]}

    echo ------------
    echo $username
    echo $groupname
    echo $filename
    echo -----------

    IFS="$IFS_NL"
done

IFS="$OLDIFS"
A présent, il ne reste plus qu'à détecter les fichiers concernés et faire la modification nécessaire (user ou group ou user/group). U simple grep sur la ligne retournée par find sera suffisant.
Enfin, pas tout à fait. Une fois que la ligne est identifiée comme devant être modifiée, il faudra de nouveau utiliser grep.

Voici enfin le rendu final :
#!/bin/sh

PATTERN_USER=toto
REPLACE_USER=emeric

# Sauvegarde l'ancien field separator
OLDIFS="$IFS";

# Saut de ligne est le separateur
IFS_NL=$'\n'
IFS_SPACE=' '

IFS="$IFS_NL"

# Recherche des fichiers uniquement
for fichier in $(find $1 -exec ls -l -d {} \;)
do
    user_detect=$(echo $fichier | grep $PATTERN_USER)

    if [ -n "$user_detect" ] ;
    then
        # Definit le fiel separator en tant qu'espace
        IFS="$IFS_SPACE" ;

        fichier_propriete=( $(echo $fichier) )

        username=${fichier_propriete[2]}
        groupname=${fichier_propriete[3]}
        filename=${fichier_propriete[8]}

        echo "Proceed file "$filename

        if [ -n $(echo "$username" | grep "$PATTERN_USER") ] ;
        then
            echo "  > Change user name"
            username="$REPLACE_USER"
        fi

        if [ -n $(echo "$groupname" | grep "$PATTERN_USER") ] ;
        then
            echo "  > Change groupe name"
            groupname="$REPLACE_USER"
        fi

        chown "$username":"$groupname" "$filename"

        IFS="$IFS_NL"
    fi
done

IFS="$OLDIFS" 
Attention toutefois, si le nom de fichier contient un espace, l'utilisation de fichier_propriete[8] posera un problème car il n'y aura que la première partie du nom.

Comment résoudre ce problème ? En utilisant le nom de fichier récupéré par find plutôt que celui de ls.
Pour cela, il faudra modifier le début du script :
# Recherche des fichiers uniquement
for fichier in $(find $1)
do
    ligne=$(ls -l -d $fichier)
    user_detect=$(echo $ligne | grep $PATTERN_USER)

    if [ -n "$user_detect" ] ;
    then
        # Definit le fiel separator en tant qu'espace
        IFS="$IFS_SPACE" ;

        fichier_propriete=( $(echo $ligne) )

        username=${fichier_propriete[2]}
        groupname=${fichier_propriete[3]}
        filename="$fichier"

dimanche 29 juillet 2012

Struts et la balise option/select

Struts met à disposition un moyen de générer un dropbox (liste déroulante) en HTML.
Son utilisation est assez simple finalement.
Voici un exemple d'utilisation.

Dans votre JSP, ajouté :
<%@taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>

<html:select property="proprietaire" >
  <html:optionsCollection name="InputForm"
    property="booleanList"
    label="label"
    value="value" />
</html:select>
Dans votre formulaire ajoutez :
import org.apache.struts.util.LabelValueBean;

public class InputForm extends ActionForm
{
    private String proprietaire ;

    private List<LabelValueBean> booleanList = new ArrayList<LabelValueBean>() ;
    
    public InputForm() 
    {
        booleanList.add(new LabelValueBean("OUI", "true")) ;
        booleanList.add(new LabelValueBean("NON", "false")) ;
    }

    public void setBooleanList(List<LabelValueBean> booleanList)
    {
        this.booleanList = booleanList;
    }

    public List<LabelValueBean> getBooleanList()
    {
        return booleanList;
    }

    public void setProprietaire(String proprietaire)
    {
        this.proprietaire = proprietaire;
    }

    public String getProprietaire()
    {
        return proprietaire;
    }
}
Le principe de fonctionnement est le suivant :
  • Struts génère la liste avec le contenu de l'attibut booleanList (property="booleanList") du formulaire InputForm (name="InputForm")
  • Ensuite, il prend la valeur de l'attribut proprietaire (property="proprietaire") pour sélectionner la bonne option.
  • A la validation du formulaire, il valorise l'attribut proprietaire.
Comment fait-il pour savoir ce qu'il faut afficher dans la liste et la valeur a transmettre ?
C'est l'attribut label="label" et value="value" qui indique quelle propriété de l'objet se trouvant dans la liste, prendre.
En effet, la classe LabelValueBean contient :
public class LabelValueBean implements Serializable
{
    private String label;
    private String value;
}
Cela veut donc dire qu'on peut utiliser nos propre objet et simplement indiquer les attributs à prendre.

Pour ceux qui ne souhaite pas utiliser de liste dynamique, il est possible d'utiliser directement :
<html:option value="0">Select Langue</html:option>
<html:option value="1">France</html:option>
<html:option value="2">English</html:option>
dans le html:select

vendredi 27 juillet 2012

Créer un nouveau user Oracle (schema)

Export du SID

A chaque connection, il faut exporter le SID
export ORACLE_SID=TITI

sqlplus /nolog
Cela permet de ne pas enregistrer les informations de connexion (user pwd) dans l'historique des commandes de l'OS.
SQL> connect / as sysdba
Visualiser l'ensemble des user Oracle (schema)

select username from dba_users;
Création du nouvel utilisateur

create user TOTO
identified by TOTO
default tablespace "USERSTOTO"
TEMPORARY TABLESPACE "TEMP01";

GRANT UNLIMITED TABLESPACE TO TOTO;
Cela permet de donner l'accès illimité à tous les tablespaces (!) à un utilisateur, ici TOTO.
GRANT CONNECT, RESOURCE TO TOTO;
Cela permet d'autoriser l'accès à distance (via port 1521) à ce schéma.

Si l'import ne s'est pas passé correctement, utiliser la commande suivante afin de tout purger :
DROP USER TOTO CASCADE;
Visualiser les rôles d'un utilisateur

En se connectant avec le user “system” (mot de passe habituel), exécuter une requête du type :
select * from dba_role_privs
WHERE GRANTEE LIKE 'TOTO%';

mardi 24 juillet 2012

Une recherche de balise par ID multi navigateur

Il arrive parfois d'utiliser la recherche d'un tag par getElementById. Toutefois, il m'est arrivé de constater que ça ne fonctionne pas.

Pourquoi ? Et bien, il faut savoir que cette méthode n'est pas supportée par tous les navigateurs (les anciens par exemple) mais aussi que cette méthode ne fonctionne simplement pas dans certain cas.

Après de nombreuse recherche sur internet, j'ai cumuler les astuces pour que cela fonctionne dans tout les cas.
Le résultat ? Une simple fonction JavaScript.
// Recherche un element par son ID suivant le navigateur
function getElement(id) {
    if (document.getElementById) {
        return document.getElementById(id) ;
    } else if (document.all) {
 
        return window.document.all[id] ;
    } else if (document.layers) {
 
        return window.document.layers[id] ;
    }
 
    return null ;
}

lundi 23 juillet 2012

Pourquoi mes expressions <%= variable %> ne fonctionne pas dans mon taglib ?

Récemment, je devais utiliser un taglib développé en interne de type :

<t:bouton onClick="verifierFormulaire(<%= index%>)" texte="Valider" />

Ce bouton est intégré à un iterator.
Je souhaitais donc passer à une fonction JavaScript l'index de l'itérateur.

Seulement voila, ça ne fonctionnait pas.

J'ai donc vérifier les paramètres du taglib :
<tag>
    <name>bouton</name>
    <tagclass>xxxxxxxxxxxxxx.taglib.BoutonTag</tagclass>
    <bodycontent>empty</bodycontent>

    <attribute>
        <name>onClick</name>
        <required>false</required>
        <rtexprvalue>true</rtexprvalue>
    </attribute>
</tag>
J'avais bien le paramètre RtExprValue à true qui indique que l'attribut peut résulter d'une expression JSP.

Ce n'est malheureusement pas là le problème.

En effet, le problème vient du fait qu'il faut que tout le contenu de onClick doit être valorisé :
<t:bouton onClick="<%= "verifierFormulaire(" + index + ");" + %>" texte="Valider" />

Voilà donc le problème réglé.

Si ce n'est pas votre problème peut-être que ce blog peut y répondre : http://blog.lecacheur.com/2006/04/27/des-attributs-dynamiques-dans-les-taglibs/

Pourquoi un blog ?

Après 10 ans de carrière dans l'informatique (5 dans le réseau, 5 dans le développement), à noter dans un coin les solutions trouvées aux problèmes rencontrés, j'ai décider de créer un blog pour partager mes connaissances ou mes déboires.

dimanche 25 mars 2012

JAX et les Calendar

L'équipe, dont je faisais partie, devant mettre en place des WebService utilisant JAX à rencontré un étrange phénomène.
En effet, le serveur envoyait une date (exemple : 12/05/1450 00:00:00) et le client récupérait une autre date.

Après analyse, le problème se posait dans la couche JAX.

A se sujet, un collègue à ouvert le ticket suivant chez Oracle : XMLGregorianCalendar.toGregorianCalendar() return a bag milisecond for some year.

La réponse est sans appel, il n'y a pas de problème.

JAX renvoit un GregorianCalendar via XMLGregorianCalendarImpl.toGregorianCalendar, donc un calendrier Grégorien. Or le calendrier Grégorien a été mis en place à partir de 15 octobre 1582.
Avant, le calendrier Julien était utilisé. Or entre les 2, il y a un décalage.

Voici l'explication de Wikipedia :
La réforme principale et suffisante éliminant cette dérive (et qui a été
appliquée facilement dans les autres pays par la réforme limitée du
calendrier julien) était celle du mode d’application des années
bissextiles lors des années séculaires. La différence principale entre
le calendrier grégorien et son ancêtre, le calendrier julien non réformé,
repose dans la distribution des années bissextiles.

L'introduction du calendrier grégorien comprend aussi une deuxième réforme
d’application plus délicate, le décalage grégorien qui supprima 10 jours
du calendrier.

Ces 10 jours permettaient de rattraper d’un coup le retard croissant pris
par l’ancien calendrier julien sur les dates des équinoxes depuis le
concile de Nicée, plus de 12 siècles avant, et de retrouver la concordance
entre l'équinoxe de printemps et le 21 mars calendaire. Neuf années
bissextiles ont été comptées en trop : en 500, 600, 700, 900, 1000, 1100,
1300, 1400 et 1500 suivant les nouvelles règles de calcul.
Voilà le problème.

Le GregorianCalendar contiens la date de passage du calendrier Julien à celui de Grégorien.
Cette date n'est la même quelle vienne du xml ou du new Gregorian.
La correction de cette erreur ce fait en définissant la date de changement du nouveau GregorienCalendar avec ce lui qui vient du xml via setGregorianChange().

La date du changement de calendrier venant de XMLGregorianCalendarImpl est Date(Long.MIN_VALUE).
La date du changement de calendrier créer à la main est 15 octobre 1582.