mardi 29 octobre 2013

Migration de Blogger vers GitHub



En ce temps d'Halloween, je réveille les morts-vivants et migre mon blog de Blogger vers GitHub Pages.


Ça fait un moment que je me dis que je posterais pus facilement si je pouvais écrire mes billets en Markdown plutôt qu'en HTML. On verra bien...


Je profite de ce billet pour lister rapidement les outils utilisés pour la migration du blog.


La suite sur http://cthiebault.github.io

mardi 21 décembre 2010

Premiers pas sous Git



Je me décide enfin à succomber au buzz et tente ma chance sous Git.
Pas tant pour le cote décentralisé que pour les merge simplifiés et les gros move lors d'important refactoring.

Pour tester la bête je vais d'abord migrer des projets existants de Subversion à Git en m'inspirant de cet excellent billet de Jon Maddox :
http://www.jonmaddox.com/2008/03/05/cleanly-migrate-your-subversion-repository-to-a-git-repository

Assembla propose un hébergement Git gratuit jusqu'à 2Go par repository.
J'utilise msysgit pour Git sous Windows.

Une fois Git installé, on identifie l'utilisateur qui fera les commit:

git config --global user.name "Cedric Thiebault"
git config --global user.email "cedric.thiebault@gmail.com"

Sans cette configuration, Git utilisera directement les informations contenues dans votre profil utilisateur système.

Ensuite, il faut créer un fichier texte qui va faire le mapping entre les users SVN et les users Git (svn-git-users.txt):

cthiebault = Cedric Thiebault <cedric.thiebault@gmail.com>

Nous allons maintenant récupérer le projets et son historique depuis SVN dans un répertoire temporaire (sous Git). Finalement, nous clonerons se projet temporaire vers un vrai repository final que nous "pousserons" vers le repository Assembla dans la branche principale (master).

mkdir project-tmp
cd project-tmp
git svn init https://svn-repository/project/trunk --no-metadata
git config svn.authorsfile ../svn-git-users.txt
git svn fetch
cd ..
git clone project-tmp project
cd project
git remote add project git@git.assembla.com:project.git
git push project master

Quelques sites bien pratique pour débuter avec Git :
Git dans la pratique (1/2)
Git dans la pratique (2/2)
Git Ready
#gitfr

lundi 19 avril 2010

GWT et Spring Security (ex ACEGI)



Ce billet présentera comment rapidement sécuriser une application GWT (SmartGWT en fait) en utilisant un formulaire d'authentification géré par GWT et non une page web avec un formulaire HTML classique.

L'application est developpée avec les outils suivants :

Le projet GWT-ent propose une intégration avec Spring Security mais seulement avec la version 2.5 et la qualité du code laisse à désirer...
De plus, avec les dernières versions de Spring Security la configuration a été grandement simplifiée!

Configuration du projet avec Maven

-- POM.xml --

<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-config</artifactId>
  <version>3.0.2.RELEASE</version>
</dependency>
<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-core</artifactId>
  <version>3.0.2.RELEASE</version>
</dependency>
<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-web</artifactId>
  <version>3.0.2.RELEASE</version>
</dependency>

Configuration de l'application web

-- web.xml --

<web-app id="gwtSecurity">

  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
      classpath:applicationContext.xml
      classpath:applicationContext-security.xml
    </param-value>
  </context-param>

  <filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  </filter>

  <filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <listener>
    <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
  </listener>

  <servlet>
    <servlet-name>rpc-dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>rpc-dispatcher</servlet-name>
    <url-pattern>*.rpc</url-pattern>
  </servlet-mapping>

  [...]

</web-app>

Configuration de Spring Security

Ici, nous indiquons à Spring d'utiliser notre UserService pour accéder aux utilisateurs ainsi que le SHA pour crypter les mots de passe.
Nous ne protégeons aucune URL puisque notre application est hébergée sur une unique page.
Nous avons été obligé de redéfinir le RememberMeServices pour forcer le alwaysRemember à vrai. par défaut Spring s'attend à recevoir un paramètre dans l'URL qui lui demanderait de se souvenir de cet utilisateur lors du prochain accès. Comme nous utilisons des appels RPC, nous ne pouvons pas passer ce paramètre dans le request.

-- applicationContext-security.xml --

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:security="http://www.springframework.org/schema/security" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">

  <security:authentication-manager alias="authenticationManager">
    <security:authentication-provider user-service-ref="userService">
      <security:password-encoder hash="sha" />
    </security:authentication-provider>
  </security:authentication-manager>

  <security:http auto-config="true">
    <security:intercept-url pattern="/*" access="IS_AUTHENTICATED_ANONYMOUSLY" />
    <security:intercept-url pattern="/css/**" filters="none" />
    <security:intercept-url pattern="/images/**" filters="none" />
    <security:logout logout-url="/logout" logout-success-url="/gwt.html" />
    <security:remember-me key="gwtSecurity" />
  </security:http>

  <bean id="rememberMeServices" class="org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
    <property name="alwaysRemember" value="true" />
    <property name="userDetailsService" ref="userService" />
    <property name="key" value="gwtSecurity" />
  </bean>

</beans>

Configuration de la servlet pour les appels RPC

On utilise ici Gilead et GWT-SL.
-- rpc-dispatcher-servlet.xml --

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

  <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="mappings">
      <map>
        <entry key="/user.rpc" value-ref="userRemoteService" />
        <entry key="/security.rpc" value-ref="securityRemoteService" />
        [...]
      </map>
    </property>
  </bean>

  <bean id="abstractGileadRPCServiceExporter" class="org.gwtwidgets.server.spring.gilead.GileadRPCServiceExporter" abstract="true">
    <property name="beanManager" ref="persistentBeanManager" />
  </bean>

  <bean id="userRemoteService" parent="abstractGileadRPCServiceExporter">
    <property name="service" ref="userService" />
    <property name="serviceInterfaces" value="service.user.UserRemoteService" />
  </bean>

  <bean id="securityRemoteService" parent="abstractGileadRPCServiceExporter">
    <property name="service" ref="securityService" />
    <property name="serviceInterfaces" value="service.security.SecurityRemoteService" />
  </bean>

  [...]

</beans>

Implementation du service d'authentification

Le Authentication Manager de Spring doit pouvoir récupérer un utilisateur par son nom d'usager. Ici c'est la class UserService qui implémentera l'interface org.springframework.security.core.userdetails.UserDetailsService pour pouvoir être utilisé par le Authentication Manager de Spring.

@Service("userService")
@Transactional(readOnly = true)
public class UserServiceImpl implements UserService, UserDetailsService {

  [...]

  @Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
    if (StringUtils.isBlank(username)) throw new UsernameNotFoundException(username);

    User user = findByLogin(username);
    if (user == null) throw new UsernameNotFoundException(username);

    List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(user.getPermissions().size());
    for (String perm : user.getPermissions()) {
      authorities.add(new GrantedAuthorityImpl(perm));
    }

    boolean enabled = user.isActive();
    boolean accountNonExpired = true;
    boolean accountNonLocked = true;
    boolean credentialsNonExpired = true;
    return new org.springframework.security.core.userdetails.User(username, user.getPassword(), enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
  }

}

Dans notre cas, on ne vas pas d'authentifier par un formulaire HTML qui posterait vers une adresse géré par Spring Security car tout est géré en Javascript et on ne veut pas quitter la page courante.
Il va donc falloir définir un service que l'on pourra appeler via RPC pour ouvrir une session. Ce service s'occupera de gérer aussi le "Remember Me".

@Service("securityService")
public class SecurityServiceImpl implements SecurityService {

  @Resource
  private AuthenticationManager authenticationManager;

  @Resource
  private RememberMeServices rememberMeServices;

  @Override
  public boolean login(String username, String password, boolean rememberMe) {
    try {
      Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
      SecurityContextHolder.getContext().setAuthentication(authentication);
      if (rememberMe) {
        rememberMeServices.loginSuccess(ServletUtils.getRequest(), ServletUtils.getResponse(), authentication);
      }      
      return authentication.isAuthenticated();
    } catch (AuthenticationException e) {
      if (rememberMe) {
        rememberMeServices.loginFail(ServletUtils.getRequest(), ServletUtils.getResponse());
      }
      return false;
    }
  }

  [...]

}

Finalement, il en reste plus qu'à notre interface (ici avec SmartGWT) à appeler notre service d'authentification :

public class LoginForm extends DynamicForm {

  private SecurityRemoteServiceAsync securityService = GWT.create(SecurityRemoteService.class);

  TextItem login;
  PasswordItem password;
  CheckboxItem rememberMe;

  [...]

  void login() {
    securityService.login(username, password, remeberMe,
        new AsyncCallback<Boolean>() {
          public void onFailure(Throwable caught) {
            loginFailed();
          }

          public void onSuccess(Boolean success) {
            if (success) {
              // user successfully logged in
            } else {
              loginFailed();
            }
          }
        });
  }

}

vendredi 26 septembre 2008

Wicket: Checkbox, AbstractCheckBoxModel et enum



Voici maintenant 3 semaines que je travaille avec Wicket et je dois dire que je suis vraiment impressionné par la vitesse de développement avec ce framework.
Ça prend un petit peu de temps pour se familiariser avec l'approche "component oriented" mais une fois que l'on a compris... ça dépote :-)

Seul petit défaut à mon avis : la documentation!
Quand on vient du monde merveilleusement documenté de Spring, Wicket est un peu dur...

Dans le billet d'aujourd'hui, je présenterais comment afficher une liste de checkbox pour toutes les valeurs d'un enum.
Je n'utilise pas de radio car je veux pouvoir désélectionner tous les checkbox (ie la valeur nulle est permise).
En fait c'est exactement le fonctionnement d'une liste de radio (un seul choix possible) mais je peux tout désélectionner , ce qui n'est pas possible avec les radios.




Évidemment dans l'exemple ci-dessus, le choix n'est pas unique mais c'est juste pour montrer à quoi ça va ressembler à la fin... ;-)

Pour commencer, mes enum implémentent l'interface suivante qui permet de connaître le label correspondant à chaque enum.

public interface LabeledEnum {
  String getLabel();
} 
public enum Browser implements LabeledEnum {

  IE, FIREFOX, OPERA, CHROME;

  public String getLabel() {
    return "browser." + name();
  }
  
}
browser.IE=Internet Explorer
browser.FIREFOX=Firefox
browser.OPERA=Opera
browser.CHROME=Chrome

Le composant Checkbox ne travaille qu'avec un Model de type boolean. Donc je ne peux pas lui passer directement mon enum.
Il faut utiliser par la classe AbstractCheckBoxModel pour transformer mon enum en boolean.

public class EnumCheckBoxModel extends AbstractCheckBoxModel {

  private static final long serialVersionUID = 1L;

  private final IModel model;
  private final LabeledEnum enumValue;

  public EnumCheckBoxModel(IModel model, LabeledEnum enumValue) {
    this.model = model;
    this.enumValue = enumValue;
  }

  @Override
  public boolean isSelected() {
    return model.getObject() == enumValue;
  }

  @Override
  public void select() {
    model.setObject(enumValue);
  }

  @Override
  public void unselect() {
    model.setObject(null);
  }

}

Et voici enfin le code de mon composant :

public class EnumCheckBoxes<TEnum extends Enum<?> & LabeledEnum> extends Panel {

  private static final long serialVersionUID = 1L;

  TEnum[] enumList;
  IModel checkBoxModel;

  public EnumCheckBoxes(String id, IModel checkBoxModel, Class<TEnum> enumClass) {
    this(id, checkBoxModel, enumClass.getEnumConstants());
  }

  public EnumCheckBoxes(String id, IModel checkBoxModel, TEnum... enumList) {
    super(id);
    this.checkBoxModel = checkBoxModel;
    this.enumList = enumList;
    createComponent();
  }

  private void createComponent() {

    // div that will be refreshed via ajax when user select a checkbox
    final WebMarkupContainer listViewContainer = new WebMarkupContainer("listContainer");
    listViewContainer.setOutputMarkupId(true);

    final ListView listView = new ListView("list", new Model((Serializable) Arrays.asList(enumList))) {
      private static final long serialVersionUID = 1L;

      @Override
      protected void populateItem(ListItem item) {
        TEnum labeledEnum = (TEnum) item.getModelObject();

        CheckBox checkBox = new CheckBox("input", new EnumCheckBoxModel(checkBoxModel, labeledEnum));
        checkBox.setLabel(new ResourceModel(labeledEnum.getLabel()));

        // don't use OnChangeAjaxBehavior because of IE event propagation for checkbox onchange
        checkBox.add(new AjaxFormComponentUpdatingBehavior("onclick") {
          private static final long serialVersionUID = 1L;

          @Override
          protected void onUpdate(AjaxRequestTarget target) {
            target.addComponent(listViewContainer);
          }
        });
        item.add(checkBox);
        item.add(new SimpleFormComponentLabel("label", checkBox));
      }

    };
    listViewContainer.add(listView);
    add(listViewContainer);
  }

}
<html xmlns:wicket>
  <wicket:panel>

    <div wicket:id="listContainer">
      <div wicket:id="list">
        <div class="checkboxWithLabel">
          <input type="checkbox" wicket:id="input" />
          <label wicket:id="label"> </label>
        </div>
      </div>
    </div>

  </wicket:panel>
</html>

jeudi 11 septembre 2008

Wicket & Maven packaging (fichiers html, css, js...)



Si vous utilisez Maven pour packager votre appplication Wicket, n'oubliez pas de modifier le POM.xml pour inclure les fichiers html, css, js, etc. dans votre war (ou jar).
A moins que vous placiez tous ces fichiers non-java sous src/main/resources au lieu de src/main/java (ça fait quand même bizarre de voir tous ces fichiers non-java à coté des classes).

<build>
 <resources>
  <resource>
   <directory>src/main/java</directory>
   <includes>
    <include>**/*</include>
   </includes>
   <excludes>
    <exclude>**/*.java</exclude>
   </excludes>
  </resource>
 </resources>
</build>

mercredi 27 août 2008

Wicket & Spring



Depuis le temps que j'entends parler de Wicket, je me lance enfin dans l'aventure :-)
J'essayerais ici de décrire les différentes étapes de la migration d'une application Spring MVC 2.5 / Weblow 2.0 / Hibernate vers Wicket 1.3.4.

Pour démarrer, je suis parti des articles du blog Xebia France qui sont vraiment bien fait... Merci à eux!

Premier problème : Spring!

Après avoir ajouté mes dépendances dans le POM.xml, Spring en peut loader les contextes :

ERROR org.springframework.web.context.ContextLoader.initWebApplicationContext:205 - Context initialization failed
org.springframework.beans.factory.BeanDefinitionStoreException: Unexpected exception parsing XML document from class path resource [applicationContext-service.xml]; nested exception is java.lang.NoSuchMethodError: org.springframework.util.ClassUtils.isPresent(Ljava/lang/String;Ljava/lang/ClassLoader;)Z
Caused by: 
java.lang.NoSuchMethodError: org.springframework.util.ClassUtils.isPresent(Ljava/lang/String;Ljava/lang/ClassLoader;)Z
 at org.springframework.context.annotation.AnnotationConfigUtils.<clinit>(AnnotationConfigUtils.java:75)
 at org.springframework.context.annotation.AnnotationConfigBeanDefinitionParser.parse(AnnotationConfigBeanDefinitionParser.java:45)

La librairie wicket-spring dépend de spring 2.0 et non de la dernière version 2.5.x.
Il faut donc juste exclure le Spring de Wicket comme suit dans le pom.xml du projet :

<dependency>
 <groupId>org.apache.wicket</groupId>
 <artifactId>wicket-spring</artifactId>
 <version>${wicket.version}</version>
 <exclusions>
  <exclusion>
   <groupId>org.springframework</groupId>
   <artifactId>spring</artifactId>
  </exclusion>
 </exclusions>
</dependency>

Internationalisation

Tous mes traductions sont dans un seul fichier properties et je ne veux pas les séparer par page ou déplacer et renommer ce fichier à la racine de l'application Wicket.

En cherchant un peu, je suis tombé sur ce blog :
http://www.jroller.com/eyallupu/entry/spring_as_a_message_provider
Il propose ici d'utiliser le MessageSource de Spring directement dans Wicket.
Ça marche parfaitement bien :-)

mardi 27 novembre 2007

Intégration native de Maven à Eclipse



Enfin un support natif de Maven dans Eclipse!
The Eclipse Integration for Apache Maven

Mais a priori il va falloir s'armer de patience : première release en septembre 2008.

vendredi 22 juin 2007

Hibernate Annotations - @CollectionOfElements



Depuis la version 3.1 de Hibernate Annotations (il me semble), on peut enfin avoir une collection de types primitifs (String par exemple) grâce à @CollectionOfElements.
Avant ça, on était un peu bloqué avec des affaires comme un champ texte qui contient la liste séparée par des virgules (ou un autre caractère), ou alors une entité gérée par Hibernate qui ne contient qu'une ID et la primitive à stocker :-(

Attention cependant, l'annotation @org.hibernate.annotations.CollectionOfElements est spécifique à Hibernate. Elle ne fait pas partie des spécifications JPA...

@Entity
public class User {
  @CollectionOfElements
  private Set<String> nicknames;
}

mercredi 6 juin 2007

Inversion de contrôle et injection de dépendances



Excellent article sur la différence entre l'inversion de contrôle et l'injection de dépendances. La confusion est souvent faites entre les deux: on pense, à tort, que c'est la même chose...
S'mythology - A Little Clarity - Inversion of Control and Dependency Injection

mardi 5 juin 2007

NoClassDefFoundError: ReflectionManager



NoClassDefFoundError: org/hibernate/annotations/common/reflection/ReflectionManager

En mettant à jour Hibernate Annotations à la dernière version (3.3.0.ga) via Maven2 (qui semble enfin avoir les dernières versions de Hibernate), j'avais cette exception :
NoClassDefFoundError: org/hibernate/annotations/common/reflection/ReflectionManager

C'est que le POM de Hibernate Annotations est incomplet.... Il manque la dépendance à hibernate-commons-annotations.
Donc en attendant que le POM de Hibernate Annotations soit corrigé ( http://jira.codehaus.org/browse/MAVENUPLOAD-1532), ajoutez dans le POM de votre projet la dépendance à hibernate-commons-annotations 3.3.0.ga.

<dependency>
  <groupId>org.hibernate</groupId>
  <artifactId>hibernate-commons-annotations</artifactId>
  <version>3.3.0.ga</version>
</dependency>

vendredi 1 juin 2007

Convention over configuration - Spring MVC



Convention plutôt que configuration en bon français : il est inutile de préciser des détails lorsqu'ils respectent des conventions établies ( Wikipedia). On en entend de plus en plus parler avec la popularité grandissante de Ruby On Rails.
Le but de ce post est de réduire un peu la taille des fichiers de configuration de Spring qui peuvent vite devenir trés gros...

Autowire des controlleurs

Pour commencer on peut utiliser le autowiring lors de la définition de nos beans. Spring va effectuer les résolutions automatiquement soit par type, soit par nom.
Dans l'exemple suivant, je définis le autowire par defaut pour tout le contexte :

-- default-servlet.xml --

<beans xmlns="http://www.springframework.org/schema/beans" default-autowire="byName">
  <bean id="userController" class="x.y.z.userController" autowire="default" />
</beans>

L'utilisation du autowire n'est pas recommandée par les développeurs de Spring pour les projets de grande taille mais je pense vraiment qu'au niveau du MVC ca ne pose pas de problèmes.

Mapping des controlleurs

La plupart du temps les URLs ressemblent aux noms des controlleurs... En utilisant le ControllerClassNameHandlerMapping Spring va chercher dans la liste des controlleurs enregistrés ceux dont le nom (moins le suffixe Controller) match l'url. Par exemple :

- WelcomeController <--> '/welcome*'
- DisplayShoppingCartController <--> '/displayshoppingcart*'

Dans le cas de MultiActionController , le mapping se fera comme suit :

- UserController <--> '/user/*'
- UserController.edit(...) <--> '/user/edit.htm'

Mapping des vues (jsp ou autres)

Pour les parties plus statique des sites, il est idiot de créer un controlleur par page... La classe UrlFilenameViewController transforme une URL en chemin sur le disque pointant vers la vue correspondante :

/users/list.htm --> /users/list

Conclusion

Si on met tout ca ensemble dans la configuration du servlet de Spring, on obtient :

-- default-servlet.xml --

<beans xmlns="http://www.springframework.org/schema/beans" default-autowire="byName">

  <bean id="userController" class="x.y.z.userController" autowire="default" />

  <!-- jsp view resolver that points to /jsp folder -->
  <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
    <property name="prefix" value="/jsp/" />
    <property name="suffix" value=".jsp" />
  </bean>

  <bean id="fileNameViewController" class="org.springframework.web.servlet.mvc.UrlFilenameViewController" />

  <!-- maps request URLs to Controller names -->
  <bean id="urlMappingWithControllers" class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping" />

  <bean id="urlMappingNoControllers" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="mappings">
      <props>
        <prop key="*">fileNameViewController</prop>
      </props>
    </property>
  </bean>

</beans>

mercredi 30 mai 2007

En java et en musique :)



Quelques petits liens pour coder Java sur un air de Java ;-)
Java - Sexe, accordéon et alcool
Boris Vian - La Java Martienne
Boris Vian - La Java des bombes atomiques
La Java bleue

mercredi 23 mai 2007

Maven2 et MyEclipse



MyEclipse ne supporte pas officiellement Maven2 mais en utilisant le plugin m2eclipse on peut s'en sortir assez facilement....

Selon le standard définit par Maven les classes devraient être compilées dans le répertoire target, mais si l'on veut utiliser MyEclipse pour déployer l'application (Hot deployment) il faut que nous compilions les classes vers src/main/webapp/WEB-INF/classes.
L'idéal serait que MyEclipse supporte le déploiement des classes compilées dans différents répertoires vers le serveur comme ça nous pourrions suivre le standard Maven et compiler vers target mais ce n'est pas encore le cas...

Attention : lorsque l'on déploit avec MyEclipse, on perd la fonctionnalité des filtres de Maven :-(

Dans les propriétés du projet web, définir le webrootdir à src/main/webapp et configurer le fichier .classpath comme suit :

<classpath>
  <classpathentry kind="src" path="src/main/java" output="src/main/webapp/WEB-INF/classes" />
  <classpathentry kind="src" path="src/main/resources" output="src/main/webapp/WEB-INF/classes" />
  <classpathentry kind="src" path="src/test/java" output="target/test-classes" />
  <classpathentry kind="src" path="src/test/resources" output="target/test-classes" />
  <classpathentry kind="output" path="src/main/webapp/WEB-INF/classes" />
  <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" />
  <classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER" />
</classpath> 

Le plus gros problème dans tout ça, c'est que MyEclipse déploit tous les jars définis dans le POM... même ceux qui ne servent qu'à la compilation (scope=provided). Par exemple les fichiers servlet-api.jar et jsp-api.jar seront copiés dans WEB-INF/lib et cela peut créer des problèmes au démarrage du serveur web.
En attendant le support officiel de Maven2 par MyEclipse, il nous reste à effacer ces fichiers manuellement ou à ne pas les inclure dans le POM :(

La communauté des utilisateurs de MyEclipse pousse de plus en plus fort pour le support officiel de Maven2 et certains proposent déjà des solutions qui ne semblent pas demander une tonne de code pour exploiter le plugin m2eclipse (légèrement adapté) et déployer seulement les jar définis pour le runtime.... Patience donc!

mardi 22 mai 2007

Mac vs PC.... Java vs Ruby



Dans le style des publicités Mac vs PC, voici Java vs Ruby vu par des adeptes de Ruby on rails... Evidemment :)

mercredi 16 mai 2007

Version des dépendances avec Maven2



Voici quelques petits trucs pratique pour alléger les POM de Maven2 dans les projets multi-modules...

Classiquement chaque POM défini ses dépendances comme suit :

-- pom.xml --

<project>
  <dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>2.0.5</version>
      <exclusions>
        <exclusion>
          <groupId>struts</groupId>
          <artifactId>struts</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
  </dependencies>
</project>

Si plusieurs modules utilisent Spring MVC, on ne veut pas dupliquer ce code dans chaque POM... On définit la configuration de la dépendance dans le POM parent et on inclus seulement la dépendance dans les modules sans spécifier la version ni les exclusions.

-- pom.xml parent --

<project>
  <groupId>demo</groupId>
  <artifactId>parent</artifactId>
  <version>1.0-SNAPSHOT</version>
  <modules>
    <module>child</module>
  </modules>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>2.0.5</version>
        <exclusions>
          <exclusion>
            <groupId>struts</groupId>
            <artifactId>struts</artifactId>
          </exclusion>
        </exclusions>
      </dependency>
    </dependencies>
  </dependencyManagement>
</project>

-- pom.xml child --

<project>
  <artifactId>child</artifactId>
  <parent>
    <groupId>demo</groupId>
    <artifactId>child</artifactId>
    <version>1.0-SNAPSHOT</version>
  </parent>  
  <dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
    </dependency>
  </dependencies>
</project>

Dernier petit truc pour simplifer la gestion des version des dépendances : au lieu de hardcoder la version de la dépendance dans sa définition, on peut utiliser des 'properties'. Par exemple pour Spring où l'on dépend de plusieurs jars, on peut facilement upgrader en modifiant seulement la propriété 'spring.version'.

-- pom.xml --

<project>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>${spring.version}</version>
      </dependency>
      <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-dao</artifactId>
        <version>${spring.version}</version>
      </dependency>
      <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-hibernate3</artifactId>
        <version>${spring.version}</version>
      </dependency>
    </dependencies>
  </dependencyManagement>
  <properties>
    <spring.version>2.0.4</spring.version>
  </properties>
</project>

Au passage, le site mvnrepository.com est un bon outils pour trouver les jars publiés dans Maven2 et surtout pour trouver lesquels ont été déplacé...

lundi 14 mai 2007

UTF-8 encoding



Après plusieurs heures passées à trouver mon erreur d'encodage UTF-8, voici un petite liste des points à vérifier sur une application web.

Mon environnement :

  • MySQL
  • Hibernate
  • Spring MVC
  • Velocity (pour les mails)
  • Tomcat

MySQL

Vérifier que les tables et les champs textes utilisent le charset UTF-8 :

CREATE TABLE [...] DEFAULT CHARSET=utf8;

Hibernate

-- hibernate.cfg.xml --

<hibernate-configuration>
  <session-factory>
    <property name="hibernate.connection.useUnicode">true</property>
    <property name="hibernate.connection.characterEncoding">utf-8</property>
  </session-factory>
</hibernate-configuration>

Spring framework

Utiliser le servlet filter CharacterEncodingFilter dans le web.xml afin de forcer l'encodage. Attention de bien positionner ce filtre en première position dans la liste des différents filtres!

-- web.xml --

<web-app>
  <filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
      <param-name>forceEncoding</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>
</web-app>

Velocity et mails

Lors du load des templates Velocity, spécifier l'encodage UTF-8.

String content = VelocityEngineUtils.mergeTemplateIntoString(velocityEngine, fileName, "UTF-8", tokens);

Encoder aussi les mails. Dans mon cas avec MimeMessageHelper.

MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");

JSP

Définir l'encodage des JSP.

<%@ page contentType="text/html; charset=UTF-8"%>
<%@ page pageEncoding="UTF-8"%>

Tomcat

Si après tout ça vous avez encore des problèmes, vous pouvez toujours essayer de configurer Tomcat mais je pense que ça ne changera pas grand chose...

-- server.xml --

<Server>
  <Service>
    <Connector URIEncoding="UTF-8" useBodyEncodingForURI="true" [...] />
  </Service>
</Server>

Conclusion

Tous ces points ne sont surement pas nécessaire... il faut que je vérifie. Je pense que l'encodage au niveau de la base de données, du web.xml et des mails (Velocity et MimeMessage) est suffisant mais cette liste peut ammener des indices à d'autres qui rencontrent ce genre de problèmes...

Dans mon cas, le problème venait du servlet filter d'encodage de Spring : il n'était pas en première position :-(