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.