Dozer

Introduction

Dozer est un framework Java opensource qui permet de mapper des beans java vers d’autres beans java en recopiant récursivement les données d’un objet à un autre, généralement des objets de types différents, de manière automatique en utilisant la réflexion Java et/ou de manière personnalisée avec un fichier de configuration XML.

C’est un outil très utile lorsqu’on développe des applications en adoptant un modèle en couches, où l’on doit recopier les données des beans d’une couche (DAO, DTO, etc…) vers les beans de la couche supérieure, ou inférieure.

Site Officiel

Vous trouverez le site officiel de Dozer à l’adresse suivante : http://dozer.sourceforge.net/

Tutoriel

Avant d’entamer ce tutoriel, vérifiez que vous ayez bien les pré requis :

  • Le JDK doit être installé sur votre machine.
  • Eclipse doit être installé sur votre machine (il est conseillé d’utiliser la version Eclipse IDE for Java EE Developers pour ce tutoriel).

Nous allons montrer comment on peut simplement recopier les données d’un bean vers un autre avec Dozer.

Télécharger Dozer

  • On commence par télécharger Dozer à l’adresse suivante : Dozer SourceForge
  • On extrait l’archive dans le répertoire /home/user/lib sous linux, ou C:\DocumentsAndSettings\user\MesDocuments\lib sous Windows en remplaçant user par votre nom d’utilisateur.

Si vous rencontrez des problèmes de droits pour copier le jar dozer-4.4.1.jar dans votre répertoire lib de votre projet, vous pouvez utiliser la commande sudo chmod dans un terminal pour modifier récursivement (option -R) les droits d’accès sur l’ensemble du répertoire dozer-4.4.1
sudo chmod -R ug+rx dozer-4.4.1

Création du projet Eclipse

On commence par créer un nouveau projet dans Eclipse :

  1. Lancer Eclipse
  2. Menu File → New → Java project
  3. Dans la fenêtre New Java Project, on indique le nom :
  4. sf-tutorial-dozer
  5. On valide en cliquant sur Finish.




Ajout des librairies

Pour ajouter la librairie Dozer au projet :

  • Il faut copier l’archive jar dozer-4.4.1.jar du dossier /home/user/lib/dozer-4.4.1/dist. Pour ce faire, on ouvre un explorateur de fichiers, pour accéder au workspace (sous Ubuntu il sera situé par défaut dans /home/user/workspace), on va dans le répertoire du projet, dans lequel on créé un nouveau répertoire lib qui contiendra les librairies. On colle ensuite le fichier jar dedans.
  • Il faut ensuite spécifier à Eclipse dans notre projet qu’on a ajouté une librairie pour pouvoir l’utiliser. On fait un clic droit sur le projet dans le package explorer qui ouvre un menu contextuel : on ouvre Build Path > Configure Build Path. On clic sur l’onglet Librairies, puis sur le bouton add JARS…. Il suffit ensuite de sélectionner le fichier dozer-4.4.1.jar situé dans le répertoire lib du projet sf-tutorial-dozer. On valide les modifications sur le projet en cliquant sur OK.



D’autres librairies sont nécessaires et sont à ajouter de la même façon. Elles peuvent être trouvées dans le dossier lib/jakarta-commons du Framework Spring :

  • commons-beanutils.jar
  • commons-collections.jar
  • commons-lang.jar
  • commons-logging.jar




Implémentation des classes

Nous allons ensuite implémenter deux beans java assez simples pour faire un exemple de recopie des données de l’un à l’autre. Les deux classes auront des attributs similaires portant des noms différents. On va créer une classe Utilisateur et UtilisateurDto

  1. Clic droit sur répertoire src du nouveau projet dans le Package Explorer.
  2. Dans le menu contextuel on va dans New → class
  3. Dans fenêtre New Java class, on indique :
      • le nom du package suivant : com.scub.foundation.tutorial.dozer.model
      • le nom de la classe : Utilisateur

On valide en cliquant sur Finish.

Voici le code source de la classe Utilisateur et de la classe AdresseUtilisateur qui fait partie des attributs de la classe Utilisateur :

La classe Utilisateur :

package com.scub.foundation.tutorial.dozer.model;
 
/**
 * Classe modélisant l'utilisateur d'une application.
 * @author Scub-Foundation
 */
public class Utilisateur {
 
	/** Identifiant de connexion de l'utilisateur. */
	private String identifiant;
 
	/** Mot de passe de l'utilisateur. */
	private String motDePasse;
 
	/** Adresse email de l'utilisateur. */
	private String adresseEmail;
 
	/** Adresse postale de l'utilisateur. */
	private AdresseUtilisateur adressePostale;
 
	/**
	 * Constructeur.
	 * @param identifiant 
	 * @param motDePasse
	 * @param adresseEmail
	 */
	public Utilisateur(String identifiant, String motDePasse, String adresseEmail) {
		super();
		this.identifiant = identifiant;
		this.motDePasse = motDePasse;
		this.adresseEmail = adresseEmail;
	}
 
	/**
	 * Accesseur en lecture.
	 * @return le identifiant
	 */
	public String getIdentifiant() {
		return identifiant;
	}
 
	/**
	 * Accesseur en lecture.
	 * @return le motDePasse
	 */
	public String getMotDePasse() {
		return motDePasse;
	}
 
	/**
	 * Accesseur en lecture.
	 * @return le adresseEmail
	 */
	public String getAdresseEmail() {
		return adresseEmail;
	}
 
	/**
	 * Accesseur en lecture.
	 * @return le adressePostale
	 */
	public AdresseUtilisateur getAdressePostale() {
		return adressePostale;
	}
 
	/**
	 * Accesseur en écriture.
	 * @param adressePostale le nouveau adressePostale
	 */
	public void setAdressePostale(AdresseUtilisateur adressePostale) {
		this.adressePostale = adressePostale;
	}
}

La classe AdresseUtilisateur :

package com.scub.foundation.tutorial.dozer.model;
 
/**
 * Classe modélisant l'adresse d'un Utilisateur.
 * @author Scub-Foundation
 */
public class AdresseUtilisateur {
 
	/** première ligne de l'adresse de l'utilisateur. */
	private String ligneAdresse1;
 
	/** seconde ligne de l'adresse de l'utilisateur. */
	private String ligneAdresse2;
 
	/** la ville. */
	private String ville;
 
	/** le code postal de la ville. */
	private String codePostal;
 
	/** le pays. */
	private String pays;
 
	/**
	 * Constructeur.
	 * @param ligneAdresse1 première ligne de l'adresse
	 * @param ligneAdresse2 seconde ligne de l'adresse
	 * @param ville ville
	 * @param codePostal code postal
	 * @param pays pays
	 */
	public AdresseUtilisateur(String ligneAdresse1, String ligneAdresse2, String ville, String codePostal, String pays) {
		super();
		this.ligneAdresse1 = ligneAdresse1;
		this.ligneAdresse2 = ligneAdresse2;
		this.ville = ville;
		this.codePostal = codePostal;
		this.pays = pays;
	}
 
	/**
	 * Accesseur en lecture.
	 * @return le ligneAdresse1
	 */
	public String getLigneAdresse1() {
		return ligneAdresse1;
	}
 
	/**
	 * Accesseur en écriture.
	 * @param ligneAdresse1 le nouveau ligneAdresse1
	 */
	public void setLigneAdresse1(String ligneAdresse1) {
		this.ligneAdresse1 = ligneAdresse1;
	}
 
	/**
	 * Accesseur en lecture.
	 * @return le ligneAdresse2
	 */
	public String getLigneAdresse2() {
		return ligneAdresse2;
	}
 
	/**
	 * Accesseur en écriture.
	 * @param ligneAdresse2 le nouveau ligneAdresse2
	 */
	public void setLigneAdresse2(String ligneAdresse2) {
		this.ligneAdresse2 = ligneAdresse2;
	}
 
	/**
	 * Accesseur en lecture.
	 * @return le ville
	 */
	public String getVille() {
		return ville;
	}
 
	/**
	 * Accesseur en écriture.
	 * @param ville le nouveau ville
	 */
	public void setVille(String ville) {
		this.ville = ville;
	}
 
	/**
	 * Accesseur en lecture.
	 * @return le codePostal
	 */
	public String getCodePostal() {
		return codePostal;
	}
 
	/**
	 * Accesseur en écriture.
	 * @param codePostal le nouveau codePostal
	 */
	public void setCodePostal(String codePostal) {
		this.codePostal = codePostal;
	}
 
	/**
	 * Accesseur en lecture.
	 * @return le pays
	 */
	public String getPays() {
		return pays;
	}
 
	/**
	 * Accesseur en écriture.
	 * @param pays le nouveau pays
	 */
	public void setPays(String pays) {
		this.pays = pays;
	}
}

La classe UtilisateurDto dans le package com.scub.foundation.tutorial.dozer.dto :

package com.scub.foundation.tutorial.dozer.dto;
 
/**
 * Classe DTO (Data Transfert Object) pour la classe Utilisateur.
 * @author Scub-Foundation
 */
public class UtilisateurDto {
 
	/** Identifiant de connexion de l'utilisateur. */
	private String login;
 
	/** Mot de passe de l'utilisateur. */
	private String password;
 
	/** Adresse email de l'utilisateur. */
	private String emailAddress;
 
	/** Pays ou réside l'utilisateur. */
	private String country;
 
	/**
	 * Accesseur en lecture.
	 * @return le login
	 */
	public String getLogin() {
		return login;
	}
 
	/**
	 * Accesseur en écriture.
	 * @param login le nouveau login
	 */
	public void setLogin(String login) {
		this.login = login;
	}
 
	/**
	 * Accesseur en lecture.
	 * @return le password
	 */
	public String getPassword() {
		return password;
	}
 
	/**
	 * Accesseur en écriture.
	 * @param password le nouveau password
	 */
	public void setPassword(String password) {
		this.password = password;
	}
 
	/**
	 * Accesseur en lecture
	 * @return le emailAddress
	 */
	public String getEmailAddress() {
		return emailAddress;
	}
 
	/**
	 * Accesseur en écriture.
	 * @param emailAddress le nouveau emailAddress
	 */
	public void setEmailAddress(String emailAddress) {
		this.emailAddress = emailAddress;
	}
 
	/**
	 * Accesseur en lecture.
	 * @return le country
	 */
	public String getCountry() {
		return country;
	}
 
	/**
	 * Accesseur en écriture.
	 * @param country le nouveau country
	 */
	public void setCountry(String country) {
		this.country = country;
	}
}


Spécifier le mapping dans un fichier XML

Le mapping entre deux classes ayant des attributs portant le même nom se fait automatiquement. Il n’est donc pas nécessaire de les déclarer dans le fichier suivant.

On va ensuite spécifier le mapping entre nos deux classes dans un fichier XML. C’est assez simple, il suffit de déclarer une classe A et une classe B, puis de définir un à un les correspondances entre les attributs des deux classes. Notez qu’il est possible de recopier des sous attributs : ici on recopie seulement le pays de l’adresse postale de l’objet A vers l’objet B.

On peut aussi redéfinir la configuration de Dozer dans ce fichier, ici pour l’exemple, même si nous ne l’utiliserons pas, on montre qu’on peut redéfinir le format de date, etc… très simplement.
Voici le fichier dozer-bean-mappings.xml à créer directement dans le dossier src du projet.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mappings PUBLIC "-//DOZER//DTD MAPPINGS//EN"
   "http://dozer.sourceforge.net/dtd/dozerbeanmapping.dtd">
<mappings>
	<!-- Configuration de Dozer -->
	<configuration>
		<stop-on-errors>true</stop-on-errors>
		<date-format>MM/dd/yyyy HH:mm</date-format>
		<wildcard>true</wildcard>
	</configuration>
 
	<!-- beans mappés par Dozer -->
	<mapping>
		<class-a>com.scub.foundation.tutorial.dozer.model.Utilisateur</class-a>
		<class-b>com.scub.foundation.tutorial.dozer.dto.UtilisateurDto</class-b>
		<field>
			<a>identifiant</a>
			<b>login</b>
		</field>
		<field>
			<a>motDePasse</a>
			<b>password</b>
		</field>
		<field>
			<a>adresseEmail</a>
			<b>emailAddress</b>
		</field>
		<field>
			<a>adressePostale.pays</a>
			<b>country</b>
		</field>
	</mapping>
</mappings>


Création du test unitaire

Il s’agit maintenant de tester la recopie partielle de notre objet Utilisateur vers notre objet UtilisateurDto. Pour cela on implémente une classe de test unitaire.
La démarche pour créer un test unitaire est simple :

  • clic droit sur le répertoire src du projet
  • depuis le menu contextuel on va dans New → Other…
  • Dans la nouvelle fenêtre on tape dans le champ de recherche junit
  • Puis on sélectionne Junit Test Case dans la liste
  • Dans la nouvelle fenêtre, sélectionnez New JUnit 4 Test, indiquez comme package com.scub.foundation.tutorial.dozer.test, comme nom TestDozer et cochez SetUpBeforeClass().

Voici le code commenté de notre classe de test unitaire :

package com.scub.foundation.tutorial.dozer.test;
 
import static junit.framework.Assert.assertEquals;
 
import java.util.ArrayList;
import java.util.List;
 
import net.sf.dozer.util.mapping.DozerBeanMapper;
import net.sf.dozer.util.mapping.DozerBeanMapperSingletonWrapper;
 
import org.junit.BeforeClass;
import org.junit.Test;
 
import com.scub.foundation.tutorial.dozer.dto.UtilisateurDto;
import com.scub.foundation.tutorial.dozer.model.AdresseUtilisateur;
import com.scub.foundation.tutorial.dozer.model.Utilisateur;
 
/**
 * Classe de test unitaire pour le tutoriel de Scub Foundation sur Dozer.
 * @author Scub-Foundation
 */
public class TestDozer {
 
	/** Notre Mapper de Beans. */
	private static DozerBeanMapper mapper;
 
	/**
	 * Méthode appelée au chargement de la classe.
	 * @throws Exception
	 */
	@BeforeClass
	public static void setUpBeforeClass() throws Exception {
 
		// Définition des fichiers de mapping
		List <String> mappingFiles = new ArrayList <String>();
		mappingFiles.add("dozer-bean-mappings.xml");
 
		// Déclaration des fichiers de mapping
		mapper = (DozerBeanMapper) DozerBeanMapperSingletonWrapper.getInstance();
		mapper.setMappingFiles(mappingFiles);
	}
 
	/**
	 * Classe de test.
	 */
	@Test
	public void testDozer() {
 
		// Création de notre objet Utilisateur
		Utilisateur beanSource = new Utilisateur("nprouteau","nprouteau17","nicolas.prouteau@scub.net");
		AdresseUtilisateur adressePostale = new AdresseUtilisateur("147, route de Limoges", null, "Angoulême","16000","France");
		beanSource.setAdressePostale(adressePostale);
 
		// Recopie partielle de l'objet Utilisateur vers un objet UtilisateurDto
		UtilisateurDto beanDestination = (UtilisateurDto) mapper.map(beanSource, UtilisateurDto.class);
 
		// On vérifie que la recopie a été faite correctement en comparant les attributs un à un
		assertEquals("L'identifiant recopié est différent ",beanSource.getIdentifiant(), beanDestination.getLogin());
		assertEquals("Le mot de passe recopié est différent ",beanSource.getMotDePasse(), beanDestination.getPassword());
		assertEquals("L'adresse email recopiée est différente ",beanSource.getAdresseEmail(), beanDestination.getEmailAddress());
		assertEquals("Le pays ou réside l'utilisateur recopié est différent ",beanSource.getAdressePostale().getPays(), beanDestination.getCountry());
	}
}

Il ne reste plus qu’à exécuter notre test unitaire, en procédant comme ceci :

  • On clique droit sur la classe TestDozer
  • Dans le menu contextuel on va dans Run As → JUnit Test

Si tout se passe bien le test unitaire devrait être valide.

Integration Spring

Il est possible d’intégrer très facilement Dozer au Framework Spring en ajoutant dans le fichier de configuration XML de Spring, le bean décrit par les lignes de codes suivantes :

<bean id="net.sf.dozer.util.mapping.MapperIF" 
  class="net.sf.dozer.util.mapping.DozerBeanMapper" singleton="true">
  <property name="mappingFiles">
    <list>
      <value>dozer-global-configuration.xml</value>			  
      <value>dozer-bean-mappings.xml</value>
    </list>
  </property>
</bean>

et on pourra récupérer notre bean mapper de Dozer avec le code suivant dans une classe java :
MapperIF mapper = VotreSpringBeanFactory.getBean(MapperIF.class);

Télécharger les sources

Les sources du projet Eclipse pour ce tutoriel sont téléchargeables sur Source Forge à l’adresse suivante.