Table of Contents

Guide d'implémentation d'eP-Core

Introduction

eP-Core est le module d'accès à la BD. Ce module permet d'obtenir les objets Java représentant les données de la BD ePims ainsi que les services permettant d'agir sur ces objets. Ce module est déployé de façon indépendante sur le serveur d'application Java Geronimo afin d'être utilisé et partagé par les autres modules de présentations et de service du système.

Technologies

Figure 1 Architecture globale de l’application

Le schema représente la totalité de l'application ePims. eP-Core représentele noyau de cette application, il ne contient pas ce qui se rapporte à la couche présentation càd interfaces Web, il constitue le point d’accès aux données de l’application ainsi qu’aux actions sur ces données (création, modification, suppression) au travers d’interfaces accessibles par toutes les couches de l’application.

Comme indiqué sur ce schéma, les technologies utilisées dans l'application ePims sont les suivantes:

Structure du projet

Les cibles Ant

Les cibles Ant principales définies pour eP-Core sont :

Architecture

Cette section décrit l’organisation des packages ainsi que les points d’interaction entre les objets des différents frameworks.eP-Core ne concernant pas tout ce qui se rapporte à la partie présentation, seuls les objets de persistance de données et ceux propre à la logique applicative sont définis.

La théorie

Figure 2 Diagramme de classe globale

En résumé, la couche model rend visible d’une part les objets représentant les données (package BO) et, d’autre part, les interfaces Services qui permettent de manipuler ces données.

On retrouve donc dans les fichiers de configuration xml : (spring-*.xml)

REMARQUE : ServiceLocator recherche dans l’ApplicationContext qui lui est spécifié les beans correspondants à l’implémentation de IXXXService (définit dans spring-*.xml).

La pratique

Utilisation d'un générateur d'Objets

La génération des POJOs et DAO se fait via MyEclipse Hibernate Reverse Engineering (HRE). Les hbms sont construits manuellement. Afin de permettre une séparation des POJOs et des DAO dans des packages différents la génération se fait en deux étapes :

Génération des DAO

Depuis MyEclipse Database Explorer, sélectionner toutes les tables de la BD ePims sauf :

  1. prog_contact
  2. proj_contact
  3. study_contact
  4. samples_share

Dans les fenêtres d'HRE

Figure 3 MyEclipse Hibernate Reverse Engineering

      o table ms_protocol  => classe cea.edyp.epims.domain.dao.impl.MSProtocol
      o table msms_protocol => classe cea.edyp.epims.domain.dao.impl.MSMSProtocol
      o table gel_1d => classe cea.edyp.epims.domain.dao.impl.Gel1D
      o table gel_2d => classe cea.edyp.epims.domain.dao.impl.Gel2D
      o table gel_1d_migration  => classe cea.edyp.epims.domain.dao.impl.Gel1DMigration
      o table gel_2d_migration  => classe cea.edyp.epims.domain.dao.impl.Gel2DMigration
      o table sample_species => classe cea.edyp.epims.domain.dao.impl.Species
      o table sample_subcellular_localisation => classe cea.edyp.epims.domain.dao.impl.SubCellularLocalisation

Une fois les classes DAO générées il faut corriger les erreurs suivantes :

public ActorRole findById( cea.edyp.epims.domain.dao.impl.ActorRoleId id) ⇒ public ActorRole findById( cea.edyp.epims.domain.ActorRoleId id)

Attention : A la première création des DAO, nous avons “extrait” les interfaces (via MyEclipse) dans le package cea.edyp.epims.domain.dao.*. Ceci afin de référencer les interfaces dans les services et éventuellement d'enrichir ces interfaces pour certaines classes. Dans ce cas des modifications sur les classes DAO générées sont nécessaires. Par exemple pour l'objet Study :

Figure 4 Diagramme de classes exemple pour les DAO Hibernate

Génération des POJO

Les Objets du domaines sont générés depuis les fichiers *.hbm.xml:

     o spécifier src/main comme output folder, 
     o cocher la case "Create abstract class"
     o ne pas cocher la case custom template

Seules les classes mères “AbstractXXX” sont systématiquement générées. Les sous classes ne sont générées que la première fois ce qui permet de les personnaliser sans tout perdre à chaque génération.

Figure 5 diagramme de classes exemple pour les objets du domaine

Définition des Services

Accès:

Seules les interfaces IxxxService sont visibles depuis toute autre application (telle que la couche présentation) qui utilisera eP-Core. Ces interfaces devront fournir les méthodes nécessaires pour l’accès aux données en lecture et en écriture.

Règles de développement

Les méthodes pour l'écriture des données sont de la forme :

Pour les accès en lecture, les services sont écrits selon le principe suivant :

Il est certain que certaines actions ne peuvent être appliquées par une simple requête (aussi compliquée qu'elle puisse être !!), à ce moment là, les services peuvent aussi avoir des méthodes exécutant des tâches partiulières.

Dans Spring:

Et au niveau des fichiers de configuration, on définit les beans.

      <bean id="contactServiceTarget" class="cea.edyp.epims.service.impl.HibernateContactService"> 
         <property name="actorDao"><ref bean="ActorDAO"></ref></property>
         <property name="contactDao"><ref bean="ContactDAO"></ref></property>
         <property name="actorRoleDao"><ref bean="ActorRoleDAO"></ref></property>
         <property name="companyDao"><ref bean="CompanyDAO"></ref></property>
      </bean>
        <!-- Transactional proxy Services -->   
      <bean id="baseTransactionProwy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"  abstract="true">
         <property name="transactionManager"><ref local="transactionManager"/></property>
         <property name="transactionAttributes">
             <props>
                 <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
                 <prop key="save*">PROPAGATION_REQUIRED</prop>
                 <prop key="create*">PROPAGATION_REQUIRED</prop>
                 <prop key="delete*">PROPAGATION_REQUIRED</prop>
             </props>
         </property>
     </bean>
     ...
     <bean id="contactService" parent="baseTransactionProxy">
        <property name="target"><ref local="contactServiceTarget"/></property>
    </bean>
    <bean id="actorDAO" class="cea.edyp.epims.domain.dao.impl.HibernateActorDAO"  >
        <property name="hibernateTemplate"><ref bean="hibernateTemplate"/></property>
    </bean>

Définition des Packages

Tous les packages du projets sont des sous packages de cea.edyp.epims. Les classes relatives aux données sont sous les packages cea.edyp.epims.domain. Chacun de ces packages contient un package base où Hibernate génère les classes relatives aux données et un package dao où Hibernate génère les interfaces relatives à l’accès des données dans la base, les classes d’implémentation de ces interfaces sont générées dans le sous package dao.impl, package relatif aux packages des domaines.

Les interfaces fournissant les services (voir section précédente) sont regroupées dans le package cea.edyp.epims.service. Ces interfaces seront implémentées par les classes se trouvant dans le package cea.edyp.epims.service.impl

Utilisation de Spring

Configuration

Les différentes configurations nécessaires à Spring sont faites dans des fichiers XML : springContext.xml, springAppContext.xml et springDataSource.xml.

o Des propriétés de hibernate via le bean SessionFactory,

        o Du bean HibernateTemplate utilisé par les DAO
        o Les différents beans DAO
        o Les différents bean de services ainsi que les beans TransactionManager associés.

Les modules ePims déclaré dans l'application EAR partageront le context spring définit dans eP-Core et donc les beans 'singleton' définis dans ce contexte. Voir la doc de l'architecture ePims …

Pour un module d'application web, il est nécessaire de définir le context spring parent que l'on souhaite utiliser dans le fichier web.xml:

    <!-- Spring Context -->
    <context-param>
      <param-name>locatorFactorySelector</param-name>
      <param-value>classpath*:springContext.xml</param-value>
    </context-param>
    <context-param>
      <param-name>parentContextKey</param-name>
      <param-value>span>epCore.context</param-value>
    </context-param>
    <listener>
     <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

il est également possible de l’utiliser dans une application :

   String[] confFiles = {"/springAppContext.xml","/springDataSource.xml"};
   ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(confFiles);

Code

Dans le code des applications utilisant le module eP-Core, lorsque l'on souhaite accéder à un des beans(services) définis dans spring, on utilise la classe ServiceLocator :

    ServiceLocator locator = new ServiceLocator(context);
    IStudyService service= locator.getStudyService();

Hibernate

Attention : Si dans les fichiers de mapping (.hbm) on utilise un set pour les collections non ordonnées, on ne peut pas utiliser ces collections dans des 'datatable' (JSF) pour la partie view. ⇒ Utilisation de 'bag' dont l'implémentation en Java est une ArrayList.

Utilisation de la propriété lazy

Les liens entre les objets de la base sont nombreux ce qui fait que lorsque l’on accède à un objet, comme un Program ou un Projet, on finit par rechercher et récupérer dans la base tous les échantillons rattachés à ce programme ou projet ainsi que les préparations et séparations subis par les échantillons etc. etc.…

Afin d’optimiser ces accès aux données on utilise le paramètre lazy de hibernate qui permet pour un attribut donné de ne récupérer les valeurs que ci celles-ci sont explicitement demandées… Par exemple, dans la définition d’une étude on a bien la collection des échantillons associés, mais la collection n’est renseignée que si l’on accède à celle-ci, en demandant la liste des échantillons ou en recherchant un échantillon particulier.

Mapping Hibernate :

   <hibernate-mapping>
     <class
            name="cea.edyp.epims.domain.Study"
            table="study"
     >
      <bag
            cascade="delete"
            inverse="true"
            name="samples"              
            lazy="true"
      >
            <key column="study" />
            <one-to-many class="cea.edyp.epims.domain.Sample" />
      </bag>
    </class>

Note De plus, on peut imaginer ajouter dans les services des méthodes « light » permettant de récupérer, par exemple, la liste des noms des échantillons sans pour autant tout récupérer, en utilisant par exemple un accès directe via JDBC.

Problème :

Cette solution semble parfaite mais le problème réside dans son utilisation. En effet, si l’on tente d’accéder à la propriété dite lazy une fois la session hibernate fermée, une exception est générée. Pour éviter toute exception, il ne faut donc pas utiliser directement les méthodes définies dans les objets du domaine.

   public class StudyBean{
      private  Study study_ ;public List getSamples(){
       if(study_ == null)
         return new ArrayList();
       return study_.getSamples();
     }

Une des solutions que l’on retrouve dans les différents forum ou docs, est de récupérer les données lors de la lecture de l’entité si l’on sait que l’on en aura besoin …. Cette solution n’est pas convaincante lorsque l’on souhaite séparer complètement la vue des données. En effet, lors du chargement d’une entités au niveau des DAO ou même des Services, on ne connaît pas l’utilisation qui en sera faite.

Solution : Il est donc nécessaire de définir une méthode qui ouvre la session si nécessaire afin de charger les données lazy sur demande. Une première méthode est définie au niveau des DAO. On retrouve dans IStudyDAO la déclaration de la méthode public void refreshLazy(Study study). Au niveau de l’implémentation, dans EPCoreStudyDAO, lors d’une même session on actualise l’état de l’entité et on accède aux propriétés lazy afin que celles-ci soient chargées.

On ajoute une méthode au niveau des services (interface et implémentation) pour permettre l’accès aux données :

   public List getStudySamples (Study study) {     
     studyDao_.refreshLazy(study);
     return new ArrayList(study.getSamples());
   }

L'utilisateur d'ePCore verra deux méthodes pour accéder aux données d’une entité : Study.getSample() et IStudyService.getStudySample(Study study). L’utilisation de l’une ou l’autre de ces méthodes entraîne un comportement différent, voire la génération d’une exception dans le premier cas. Un commentaire devra expliquer le comportement de ces méthodes.

Archivage

Services

On fait apparaître au niveau d'eP-Core la notion d’archivage. Il existe un repository, PIMS_ARCHIVE, dédié à l’archivage, un champ archived, est ajouté dans la table acquisitions_result et les études ont un état qui peut être ‘archivable’ ou ‘archived’. Enfin des services permettent d’archiver certaines données.

La classe IStudyService définit la méthode archiveStudy(Study s) qui

  o recrée l’arborescence depuis PIMS_ROOT jusqu’au répertoire de l’étude sous PIMS_ARCHIVE
  o déplace récursivement depuis le répertoire de l’étude tous les fichiers dans le répertoire correspondant sous PIMS_ARCHIVE
  o copie un fichier texte standard, en mettant à jour la date, dans le répertoire (vide) de l’étude
  o change l’état de l’étude dans la base de données à ‘archived’
  o positionne le champs ‘archived’, dans la base de données, des acquisitions associées à l’étude

La classe IAcquisitionService définit la méthode archiveAcquisitionResult(List) qui pour chaque AcquisitionResult :

  o recrée l’arborescence depuis PIMS_ROOT jusqu’au répertoire de l’acquisition sous PIMS_ARCHIVE
  o déplace l’acquisition dans le répertoire créé sous PIMS_ARCHIVE
  o positionne le champ archived, dans la base de données.

L’archivage peut être long puisqu’il consiste en la copie de données pouvant représenter plusieurs GO. C’est pourquoi, l'archivage est fait par un thread indépendant.

ArchiveManager

Quartz

Quartz est une librairie qui permet de gérer des « jobs » et de les programmer afin qu’ils soient exécuter à un moment précis, de manière récursive ou non. L’opération que l’on souhaite exécuter est défini dans une classe implémentant l’interface Job. On définit par ailleurs un Trigger qui s’activera à une certaine date/heure (et qui pourra ensuite se déclencher régulièrement), enfin, on spécifie à la classe Scheduler l’association Job-Trigger ou plus exactement JobDetail-Trigger. La classe JobDetail contenant non seulement la classe Job a exécuter mais également, un identifiant, un groupe d’appartenance et un espace permettant de partager des données avec les instances de Job. Lorsque le Trigger se déclenche, le Job qui lui est associé est exécuté.

Les Job sont exécutés dans des threads, une instance de Job est créée à chaque exécution, ils peuvent lever des exceptions, de type JobExecutionException.

Un autre fichier de propriété quartz.properties est présent sous ../resources/main. Ce fichier est utilisé par Quartz et qui est utilisé pour la gestion des archivages (les opérations sont executées dans des thread).

ArchiveManager

D’une manière plus générale, une classe gère toute la partie archivage, de la définition du répertoire d’archivage à l’archivage proprement dit. Toute les références à l’archivage sont ainsi regroupées en une seule classe. Cette classe, ArchiveManager, utilise Quartz pour l’exécution de l’archivage, opération qui peut être longue et qui doit donc être exécuté dans un thread. L’utilisation de Quartz, permet également aux opérations d’archivage de lever des exceptions en cas d’erreur.

Un autre intérêt de cette définition est qu’il est possible à tout moment de connaître l’ensemble des opération en cours. On pourrait même conserver un historique sur les X derniers archivages.

Figure 7 diagramme de classes : Gestion de l’archivage

Le diagramme de classes ci-dessus représente de façon grossière les classes définit dans le projet Quartz, en jaune, ainsi que les classes spécifique à eP-Core ArchiveManager et ArchiveXXJob. La classe ArchiveManager, qui est un singleton, masque l’utilisation du package Quartz. C’est elle qui crée et démarre le Scheduler. Elle s’enregistre également auprès du Scheduler afin d’être notifiée des évènements relatifs aux Job.

L’utilisateur (développeur) fait appel à l’ArchiveManager lorsqu’il souhaite archiver des acquisitions, dans le cadre d’une étude ou d’un groupe de contrôle ; le Job approprié est alors créé et l’ArchiveManager dédie au Scheduler la gestion de cette opération. L’archivage est exécuté dans un thread à part, créé par le Scheduler. Il est possible d’interroger l’ArchiveManager à tout moment sur l’état de l’archivage : en cours, terminé, en erreur.

Le diagramme de séquence ci-après reprend le scénario d’archivage d’une étude.

Le diagramme de classes ci-dessus représente de façon grossière les classes définit dans le projet Quartz, en jaune, ainsi que les classes spécifique à eP-Core ArchiveManager et ArchiveXXJob. La classe ArchiveManager, qui est un singleton, masque l’utilisation du package Quartz. C’est elle qui crée et démarre le Scheduler. Elle s’enregistre également auprès du Scheduler afin d’être notifiée des évènements relatifs aux Job.

L’utilisateur (développeur) fait appel à l’ArchiveManager lorsqu’il souhaite archiver des acquisitions, dans le cadre d’une étude ou d’un groupe de contrôle ; le Job approprié est alors créé et l’ArchiveManager dédie au Scheduler la gestion de cette opération. L’archivage est exécuté dans un thread à part, créé par le Scheduler. Il est possible d’interroger l’ArchiveManager à tout moment sur l’état de l’archivage : en cours, terminé, en erreur.

Le diagramme de séquence ci-après reprend le scénario d’archivage d’une étude.

Figure 8 diagramme de séquence : Archivage d’une étude

Run Robot

Lors de la validation d'un run robot, plusieurs opérations sont nécessaire. En effet, tant qu'un run n'est pas effectivement validé, on créé des virtual_plate et virtual_well pour organiser les échantillons dans les plaques. Une fois le run robot réalisé, il faut :