Eclipse 3.5 intègre désormais un gestionnaire SVN. C'est super, plus besoin d'installer subclipse, qui remplit parfaitement son rôle mais qui impose l'ajout d'un update site. Donc on installe le SVN (le paquet "Subversive SVN Team Provider", qui est encore en incubation), et pis on essaye de se connecter à un repository SVN. Et là, malheur, ca ne marche pas, on nous dit qu'il n'y a pas de connecteur. Alors comment qu'on fait?
Il faut rajouter des connecteurs. Et pour ça, il faut rajouter des sites de mises a jour. http://community.polarion.com/projects/subversive/download/integrations/galileo-site/ et http://www.polarion.org/projects/subversive/download/eclipse/2.0/update-site/. Et là, surprise, on a une nouvelle arborescence nommée Subversive SVN Connectors. Il y a trois connecteurs dispo, SVNKit, JavaHL et Native JavaHL. Chacun existe en plusieurs versions, correspondant à des versions de SVN. Pour un svn 1.6, il faut donc installer SVNKit 1.3 ou JavaHL 1.6. Je n'ai pas testé tous les connecteurs, mais j'utilise SVNKit sans aucun problème. Notez qu'il est possible de changer le connecteur utilisé.
mercredi 30 décembre 2009
| [+/-] |
Eclipse - Galileo et SVN |
mardi 16 juin 2009
| [+/-] |
Hibernate - Des String non vides |
Lorsqu'on utilise des objets hibernate dans des IHM, jsf notamment, il arrive que les chaînes de caractères ne contiennent que des espaces ou pire, qu'elles soient vides mais non null. Cela peut poser problème notamment lorsqu'un attribut n'est pas censé être null (nullable a false) ou qu'il y a des contraintes d'unicité. Pour résoudre ce souci, nous allons créer notre propre type de donnée hibernate, que nous appellerons "notEmptyString". L'idée est de retourner "null" si la chaîne est vide.
Attention, un type hibernate n'est pas un type de données java, et ce n'est pas non plus un type de données SQL. C'est un convertisseur qui traduit des types Java en types SQL et vice versa.
package fr.javapowa.hibernate.type;
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.usertype.UserType;
public class NotEmptyString implements UserType {
public int[] sqlTypes() {
return new int[] { Types.VARCHAR };
}
public Class returnedClass() {
return String.class;
}
public boolean equals(final Object _x, final Object _y) {
if (_x == _y) {
return true;
}
if ((_x == null) || (_y == null)) {
return false;
}
return _x.equals(_y);
}
public Object deepCopy(final Object _x) {
return _x;
}
public boolean isMutable() {
return false;
}
public Object nullSafeGet(final ResultSet rs, final String[] _names, final Object _owner)
throws HibernateException, SQLException {
return Hibernate.STRING.nullSafeGet(rs, _names[0]);
}
public void nullSafeSet(final PreparedStatement _st, final Object _value, final int _index)
throws HibernateException, SQLException {
if (_value != null) {
final String v = this.escape((String) _value);
Hibernate.STRING.nullSafeSet(_st, v, _index);
} else {
Hibernate.STRING.nullSafeSet(_st, null, _index);
}
}
public Object assemble(final Serializable _arg0, final Object _arg1) throws HibernateException {
return this.deepCopy(_arg0);
}
public Serializable disassemble(final Object _value) {
return (Serializable) this.deepCopy(_value);
}
private String escape(final String _string) {
if (_string == null || _string.trim().length() == 0) {
return null;
}
return _string;
}
public int hashCode(final Object _arg0) throws HibernateException {
return _arg0.hashCode();
}
public Object replace(final Object _arg0, final Object _arg1, final Object _arg2)
throws HibernateException {
return this.deepCopy(_arg0);
}
}
Ce code est très fortement inspiré du type String. La principale différence se situe au niveau de la méthode "nullSafeSet" qui permet d'écrire la valeur de la chaîne dans le prepared statement. Ici nous nous contentons de vérifier si la chaîne est vide ou pas, puis nous déléguons le travail au type String. Notez que nous n'effectuons pas de vérification sur le nullSafeGet, puisqu'aucune chaîne de caractères vide n'a pu être enregistrée. Le reste des méthodes se passe de commentaires. Remarquez simplement que le type est immuable.
Ensuite, il faut déclarer un fichier de mapping, que nous appellerons notEmptyString.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<typedef
name="notEmptyString"
class="fr.javapowa.hibernate.type.NotEmptyString" />
</hibernate-mapping>
N'oubliez pas de déclarer ce fichier là où vous déclarez votre mapping. Avec spring cela donne :
<!-- Hibernate SessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
<property name="hibernateProperties">
<props>
....
</props>
</property>
<property name="mappingResources">
<list>
<value>notEmptyString.hbm.xml</value>
....
</list>
</property>
<property name="annotatedClasses">
<list>
....
</list>
</property>
<property name="dataSource" ref="dataSource" />
</bean>
Enfin, il ne nous reste plus qu'à utiliser le type de données lors de la déclaration des attributs. Si vous utilisez les annotations :
@Basic(fetch = FetchType.EAGER) @Column(name = "name", length = 255, nullable = true) @Type(type = "notEmptyString") private String name;
Ou si vous utilisez des fichiers hbm :
<property name="nom" type="notEmptyString" nullable="true" />
Notez que vous pouvez utiliser directement le nom pleinement qualifié de la classe plutôt que le nom du type (ici fr.javapowa.hibernate.type.NotEmptyString).
Et voila. Vous ne pourrez plus avoir de chaine de caractères vide dans votre base.
mercredi 20 mai 2009
| [+/-] |
JSF - Plusieurs fichiers de config |
Avec JSF, les fichiers de configuration peuvent être relativement costauds. Il n'est pas rare de voir des fichiers avec plusieurs centaines de beans, autant de règles de navigation, sans compter les converters, les validators, les bundles de messages... Il est donc parfois utile de pouvoir exploser le fichier faces-config.xml en plusieurs. C'est en fait extrêmement simple, il suffit de rajouter les fichiers séparés par des virgules dans la propriété "javax.faces.CONFIG_FILES" du fichier web.xml.
<context-param>
<param-name>javax.faces.CONFIG_FILES</param-name>
<param-value>/WEB-INF/faces-application.xml,/WEB-INF/faces-beans.xml,/WEB-INF/faces-navigation.xml</param-value>
</context-param>
C'est tout simple, mais ça simplifie la gestion des règles.
lundi 11 mai 2009
| [+/-] |
Maven - native2ascii |
native2ascii est un outil fourni avec le jdk permettant de convertir des fichiers contenant des caractères natifs vers un fichier "unicode" (les caractères non ascii du nouveau fichier seront sous la forme "\udddd").
Il existe un plugin maven permettant d'effectuer cette conversion. Malheureusement, il n'est pas très pratique. Par défaut, il ne convertit que les fichiers issus du répertoire "src/main/native2ascii" et curieusement les fichiers convertis ne sont pas inclus dans le jar. De plus, il n'y a pas de "token replacement" ce qui peut poser problème. Il faut donc régler ça de façon à ce que d'une part nous puissions convertir nos fichiers de ressources après le remplacement des tokens, et d'autre part pouvoir inclure le fichier converti dans le jar (ou le war).
La première étape est de paramétrer le plugin au niveau du pom parent. Nous allons avoir besoin du plugin antrun.
Dans le pom parent :
<properties>
<native2ascii.includes>**/*.properties</native2ascii.includes>
<native2ascii.encoding>UTF-8</native2ascii.encoding>
</properties>
<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<inherited>true</inherited>
<dependencies>
<dependency>
<groupId>sun</groupId>
<artifactId>tools</artifactId>
<version>1.5.0</version>
<scope>system</scope>
<systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>native2ascii-maven-plugin</artifactId>
<inherited>true</inherited>
<dependencies>
<dependency>
<groupId>sun</groupId>
<artifactId>tools</artifactId>
<version>1.5.0</version>
<scope>system</scope>
<systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency>
</dependencies>
</plugin>
</plugins>
</pluginManagement>
</build>
La propriété "native2ascii.encoding" indique l'encoding des fichiers sources, l'autre propriété nous permet d'indiquer les fichiers à convertir. Notez qu'il faut inclure rt.jar dans le path.
Dans le pom ou la conversion doit avoir lieu :
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>native2ascii-maven-plugin</artifactId>
<inherited>true</inherited>
<executions>
<execution>
<phase>process-resources</phase>
<goals>
<goal>native2ascii</goal>
</goals>
<configuration>
<encoding>${native2ascii.encoding}</encoding>
<dest>/target/native2ascii</dest>
<src>/target/classes</src>
<includes>${native2ascii.includes}</includes>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<inherited>true</inherited>
<executions>
<execution>
<id>native2ascii</id>
<phase>process-resources</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<copy
todir="${basedir}/target/classes"
overwrite="true">
<fileset
dir="${basedir}/target/native2ascii" />
</copy>
<delete
dir="${basedir}/target/native2ascii" />
</tasks>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
L'idée est de convertir les fichiers de target/classes, qui auront été copiés par maven et où le "token replacement" aura eu lieu. Ensuite, nous récupérons les fichiers convertis pour remplacer les fichiers existants dans la tâche ant. Ainsi les fichiers seront correctement inclus dans le jar/war.
| [+/-] |
JSF - Téléchargement de fichier |
Un petit bout de code tout simple, permettant de lancer le téléchargement d'un fichier sur le poste du client suite à une action.
public final void exportData(final String _fileName, final byte[] _data) throws IOException {
final HttpServletResponse response = FacesContext.getCurrentInstance().getExternalContext().getResponse();
response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "attachment;filename=" + _fileName + ".pdf");
response.setContentLength(_data.length);
response.getOutputStream().write(_data);
FacesContext.getCurrentInstance().renderResponse();
FacesContext.getCurrentInstance().responseComplete();
}
L'idée est de modifier le contenu et le type de la response. En spécifiant un "attachement;filename=Y" on va demander au navigateur de télécharger le fichier de nom Y. Le content type doit être celui du fichier.