SeamFramework.orgCommunity Documentation

Chapitre 13. Le serveur d'application Seam

13.1. Introduction
13.2. Les objets Home
13.3. Le objets Query
13.4. Les objets Controleur

Seam rends vraiment plus simple de créer des applications en écrivant les pures-classes Java avec les annotations, qui n'ont pas besoin d'être étendue avec des interfaces spéciales ou des super-classes. Mais nous pouvons simplifier beaucoup plus des tâches communes de programmation en fournissant un groupe de composant pré-livrés qui peuvent être réutilisés en les configurant dans components.xml (pour les cas très simple) ou par extention.

Le Serveur d'Application Seam peut réduire la quantité de code que vous avez besoin d'écrire quand vous voulez faire les accès classiques à la base de données dans une application web, en utilisant aussi bien Hibernate ou JPA.

Vous devriez apprécier que le serveur d'application soit extrèmement simple, juste un bagage de classes simples qui sont faciles à comprendre et à étendre. La "magie" est dans Seam lui-même — la même magie que vous utiliser quand vous créez tout application Seam même sans utiliser le serveur d'application.

Les composants fournies par le serveur d'application de Seam peuvent être utilisés d'une ou de deux manières différentes. La première façon est d'installer et de configurer une instance de composant dans components.xml, tout comme nous avons fait avec les autres types de composants livrés par Seam. Par exemple, le fragment suivant de components.xml installe un composant qui va réaliser les opérations basiques de CRUD pour l'entité Person:


<framework:entity-home name="personHome" 
                       entity-class="eg.Person" 
                       entity-manager="#{personDatabase}">
    <framework:id
>#{param.personId}</framework:id>
</framework:entity-home
>

i cela ressemble un peu beaucoup à "programmation en XML" à votre goût, vous pouvez utiliser une extention à la place:

@Name("personHome")

public class PersonHome extends EntityHome<Person
> {
    
   @In EntityManager personDatabase;
    
   public EntityManager getEntityManager() {
      return personDatabase; 
   }
    
}

La seconde approche a un énorme avantage; vous pouvez facilement ajouter des fonctionnalités additionnelles, et surcharger les fonctionnalitées livrées par défaut (les classes du serveurd'applications sont précisément conçues pour l'extention et la personalisation).

Un deuxième avantage est que vos classes peuvent être des beans de session EJB avec état, si vous préférez (Ils ne sont pas obligés de l'être, ils peuvent être de pur composants JavaBean si vous préférez.) Si vous utiliser JBoss AS, vous allez avoir besoin de la 4.2.2GA ou supérieure:

@Stateful

@Name("personHome")
public class PersonHome extends EntityHome<Person
> implements LocalPersonHome {
    
}

Vous pouvez aussi faire de vos classes des beans de sessions sans état. Dans ce cas, vous devez utiliser l'injection pour fournir le contexte de persistance, meême s'il ya un appel à entityManager:

@Stateless

@Name("personHome")
public class PersonHome extends EntityHome<Person
> implements LocalPersonHome {
    
   @In EntityManager entityManager;
    
   public EntityManager getPersistenceContext() { 
      entityManager; 
   }
    
}

A cette étape, le Serveur d'application de Seam fourni tout juste quatre composants livrés: EntityHomeet HibernateEntityHome pour le CRUD, avec EntityQuery et HibernateEntityQuery pour les requêtes.

es composants Home et Query sont écris de façon qu' ils puissent fonctionner dans une étendue de session, d'évènement ou de conversation. L'étendue que vous utiliserez dépendra du modèle à état que vous souhaiterez utiliser dans votre application.

Le Serveur d'application de Seam fonctionnement seulement dans les contextes de persistances gérés par Seam. Par défaut, les composants vont chercher un contexte de persistance nommé entityManager.

Un objet Home fourni les opérations de persistance pour une classe d'entité particulière. Supposons que nous avons notre classe valide Person class:

@Entity

public class Person {
    @Id private Long id;
    private String firstName;
    private String lastName;
    private Country nationality;
    
    //getters and setters...
}

Nous pouvons définir un composant personHome via la configuration suivante:


<framework:entity-home name="personHome" entity-class="eg.Person" />

Ou via une extension:

@Name("personHome")

public class PersonHome extends EntityHome<Person
> {}

Un objet Home fourni les opérations suivantes: persist(), remove(), update() et getInstance(). Avant que vous puissiez appeler les opérations remove(), ou update() vous devez en premier définir l'identification de l'objet dont vous êtes en train de vous intéresser en utilisant la méthode s setId().

Vous pouvez utiliser un Home directement depuis une page JSF, pour exemple:


<h1
>Create Person</h1>
<h:form>
    <div
>First name: <h:inputText value="#{personHome.instance.firstName}"/></div>
    <div
>Last name: <h:inputText value="#{personHome.instance.lastName}"/></div>
    <div>
        <h:commandButton value="Create Person" action="#{personHome.persist}"/>
    </div>
</h:form
>

Habituellement, il est plus jolie d'être capable de se référer à la Personpresque comme une person, rendons cela possible en ajoutant une ligne à components.xml:


<factory name="person" 
         value="#{personHome.instance}"/>

<framework:entity-home name="personHome" 
                       entity-class="eg.Person" />

(Si nous utilisons la configuration). Ou en ajoutant la méthode @Factory a PersonHome:

@Name("personHome")

public class PersonHome extends EntityHome<Person
> {
    
    @Factory("person")
    public Person initPerson() { return getInstance(); }
    
}

(Si vous utilisons l'extention.) Cette modification simplifie notre page JSF comme ci-dessous:


<h1
>Create Person</h1>
<h:form>
    <div
>First name: <h:inputText value="#{person.firstName}"/></div>
    <div
>Last name: <h:inputText value="#{person.lastName}"/></div>
    <div>
        <h:commandButton value="Create Person" action="#{personHome.persist}"/>
    </div>
</h:form
>

Et bien, cela nous permet de créer de nouvelles entrées Person . Oui, c'est bien tout le code nécéssaire! Maintenant, si vous voulont être capable d'afficher, de modifier, d'éffacer des Person préexistantes dans la base de données, nous avons besoin de passer l'identifiant de l'entrée à PersonHome. Les paramètres de pages sont une excelente manière de faire cela:


<pages>
    <page view-id="/editPerson.jsp">
        <param name="personId" value="#{personHome.id}"/>
    </page>
</pages
>

Maintenant, nous pouvons ajouter les opérations additionnelles à notre page JSF:


<h1>
    <h:outputText rendered="#{!personHome.managed}" value="Create Person"/>
    <h:outputText rendered="#{personHome.managed}" value="Edit Person"/>
</h1>
<h:form>
    <div
>First name: <h:inputText value="#{person.firstName}"/></div>
    <div
>Last name: <h:inputText value="#{person.lastName}"/></div>
    <div>
        <h:commandButton value="Create Person" action="#{personHome.persist}" rendered="#{!personHome.managed}"/>
        <h:commandButton value="Update Person" action="#{personHome.update}" rendered="#{personHome.managed}"/>
        <h:commandButton value="Delete Person" action="#{personHome.remove}" rendered="#{personHome.managed}"/>
    </div>
</h:form
>

Quand nous lions notre page sans paramètres de requêtes, la page va être affiché comme une page "Création d'une personne". Quand nous fournissons une valeur au paramètre de requête personId, cela va être une page "Edition d'une personne".

Supposons que nous avons besoin de créer une entrée Person avec sa nationnalité initialisée. Nous pouvons faire cela facilement via la configuration:


<factory name="person" 
         value="#{personHome.instance}"/>

<framework:entity-home name="personHome" 
                       entity-class="eg.Person" 
                       new-instance="#{newPerson}"/>

<component name="newPerson" 
           class="eg.Person">
    <property name="nationality"
>#{country}</property>
</component
>

Ou par extention:

@Name("personHome")

public class PersonHome extends EntityHome<Person
> {
    
    @In Country country;
    
    @Factory("person")
    public Person initPerson() { return getInstance(); }
    
    protected Person createInstance() {
        return new Person(country);
    }
    
}

Bien sur, le Country peut être un objet géré par un autre objet Home, par exemple, CountryHome.

Pour ajouter des opérations plus sophistiquées (gestion d'association, etc.), nous pouvons juste ajouter les méthodes à PersonHome.

@Name("personHome")

public class PersonHome extends EntityHome<Person
> {
    
    @In Country country;
    
    @Factory("person")
    public Person initPerson() { return getInstance(); }
    
    protected Person createInstance() {
        return new Person(country);
    }
    
    public void migrate()
    {
        getInstance().setCountry(country);
        update();
    }
    
}

L'objet Home déclenche un évènement org.jboss.seam.afterTransactionSuccess quand une transaction réussie (quand un appel à persist(), update() ou remove() réussit). En observant cet évènement nous pouvons rafraichir nos requêtes quand une entitées sousjacentes se modifie. Si nous voulons seulement rafraichir certains requêtes quand un entité particulière est persistée, mise-à-jour ou retirée, nous pouvons observer l'évènement org.jboss.seam.afterTransactionSuccess.<name> (avec <name> qui est le nom simple de l'entité par exemple une entité appelée "org.foo.myEntity" a comme nom simple "myEntity").

L'objet Home affiche automatiquement les messages faces quand une opération est réussit. Pour personnaliser ces messages, nous pouvons, encore, utiliser la configuration:


<factory name="person" 
         value="#{personHome.instance}"/>

<framework:entity-home name="personHome"
                       entity-class="eg.Person"
                       new-instance="#{newPerson}">
    <framework:created-message
>New person #{person.firstName} #{person.lastName} created</framework:created-message>
    <framework:deleted-message
>Person #{person.firstName} #{person.lastName} deleted</framework:deleted-message>
    <framework:updated-message
>Person #{person.firstName} #{person.lastName} updated</framework:updated-message>
</framework:entity-home>

<component name="newPerson" 
           class="eg.Person">
    <property name="nationality"
>#{country}</property>
</component
>

Ou l'extension:

@Name("personHome")

public class PersonHome extends EntityHome<Person
> {
    
    @In Country country;
    
    @Factory("person")
    public Person initPerson() { return getInstance(); }
    
    protected Person createInstance() {
        return new Person(country);
    }
    
    protected String getCreatedMessage() { return createValueExpression("New person #{person.firstName} #{person.lastName} created"); }
    protected String getUpdatedMessage() { return createValueExpression("Person #{person.firstName} #{person.lastName} updated"); }
    protected String getDeletedMessage() { return createValueExpression("Person #{person.firstName} #{person.lastName} deleted"); }
    
}

Mais la meilleure façon pour spécifier le message est de le mettre dans le lot de ressource connu par Seam (le lot nommé messages, par défaut).

Person_created=New person #{person.firstName} #{person.lastName} created
Person_deleted=Person #{person.firstName} #{person.lastName} deleted
Person_updated=Person #{person.firstName} #{person.lastName} updated

Cela active l'internationnalisation et converse votre code et votre configuration propre de ce qui concerne la présentation.

L'étape finale est d'ajouter la fonctionnalité de validation à la page, en utilisant <s:validateAll> et <s:decorate>, mais je vais vous laisser trouver.

Si nous avons besoin d'une liste de toutes les instance de Person dans la base de données, nous pouvons utiliser un objet Query object. Par exemple:


<framework:entity-query name="people" 
                        ejbql="select p from Person p"/>

Nous pouvons l'utiliser dans une page JSF:


<h1
>List of people</h1>
<h:dataTable value="#{people.resultList}" var="person">
    <h:column>
        <s:link view="/editPerson.jsp" value="#{person.firstName} #{person.lastName}">
            <f:param name="personId" value="#{person.id}"/>
        </s:link>
    </h:column>
</h:dataTable
>

Nous avons probablement besoin de supporter la pagination:


<framework:entity-query name="people" 
                        ejbql="select p from Person p" 
                        order="lastName" 
                        max-results="20"/>

Nous allons utiliser un paramètre de page pour déterminer la page à afficher:


<pages>
    <page view-id="/searchPerson.jsp">
        <param name="firstResult" value="#{people.firstResult}"/>
    </page>
</pages
>

Le code JSF pour le contrôle de la pagination est un peu verbeux mais gérable:


<h1
>Search for people</h1>
<h:dataTable value="#{people.resultList}" var="person">
    <h:column>
        <s:link view="/editPerson.jsp" value="#{person.firstName} #{person.lastName}">
            <f:param name="personId" value="#{person.id}"/>
        </s:link>
    </h:column>
</h:dataTable>

<s:link view="/search.xhtml" rendered="#{people.previousExists}" value="First Page">
    <f:param name="firstResult" value="0"/>
</s:link>

<s:link view="/search.xhtml" rendered="#{people.previousExists}" value="Previous Page">
    <f:param name="firstResult" value="#{people.previousFirstResult}"/>
</s:link>

<s:link view="/search.xhtml" rendered="#{people.nextExists}" value="Next Page">
    <f:param name="firstResult" value="#{people.nextFirstResult}"/>
</s:link>

<s:link view="/search.xhtml" rendered="#{people.nextExists}" value="Last Page">
    <f:param name="firstResult" value="#{people.lastFirstResult}"/>
</s:link
>

Les écrans de recherche réel permettent à l'utilisateur d'entrer un tas de critères de recherches optionnels pour réduire la liste des résultats retournés. L'objet Query vous permet d'indiquer les "restrictions" optionneles pour supporter ce cas d'utilisation important:


<component name="examplePerson" class="Person"/>
        
<framework:entity-query name="people" 
                        ejbql="select p from Person p" 
                        order="lastName" 
                        max-results="20">
    <framework:restrictions>
        <value
>lower(firstName) like lower( concat(#{examplePerson.firstName},'%') )</value>
        <value
>lower(lastName) like lower( concat(#{examplePerson.lastName},'%') )</value>
    </framework:restrictions>
</framework:entity-query
>

Notez l'utilisation d'un objet "exemple".


<h1
>Search for people</h1>
<h:form>
    <div
>First name: <h:inputText value="#{examplePerson.firstName}"/></div>
    <div
>Last name: <h:inputText value="#{examplePerson.lastName}"/></div>
    <div
><h:commandButton value="Search" action="/search.jsp"/></div>
</h:form>

<h:dataTable value="#{people.resultList}" var="person">
    <h:column>
        <s:link view="/editPerson.jsp" value="#{person.firstName} #{person.lastName}">
            <f:param name="personId" value="#{person.id}"/>
        </s:link>
    </h:column>
</h:dataTable
>

Pour rafraichir la requête quand les entitées sousjacentes changent, nous oberservons l'évènement org.jboss.seam.afterTransactionSuccess:


<event type="org.jboss.seam.afterTransactionSuccess">
    <action execute="#{people.refresh}" />
</event
>

Ou juste pour rafraichir la requête quand l'entité personne est peristée, mise-à-jours ou retirée au travers de PersonHome:


<event type="org.jboss.seam.afterTransactionSuccess.Person">
    <action execute="#{people.refresh}" />
    </event
>

Malheureusement, les objets Query ne fonctionnent pas bien avec les reuqêtes join fetch - l'utilisation de la pagination avec ces requêtes n'est pas recommandée, et vous allez devoir implémenter votre propre méthode pour calculer le nombre total de résultats (en surchargeant getCountEjbql()).

Les exemples de cette section qui ont été montré utilisent la configuration. Cependant les utiliser par l'extension est égale possible pour les objets Query.

Une partie complètement optionnelle du Serveur d'Application Seam est la classe Controller et ses sousclasses EntityController HibernateEntityController et BusinessProcessController. Ces classes ne fournissent rien de plus que des méthodes pratiques pour acceder aux composants livré utilisé communément. Ils permettent de d'éviter quelques frappes au claviers (les touches peuvent se reposer) et fournissent une bonne base de lancement pour les nouveaux utilisateurs afin qu'ils explorent les fonctionnalités riches livrées dans Seam.

Par exemple, ici c'est le RegisterAction de l'exemple de reservation de Seam devrait ressemble :

@Stateless

@Name("register")
public class RegisterAction extends EntityController implements Register
{
   @In private User user;
   
   public String register()
   {
      List existing = createQuery("select u.username from User u where u.username=:username")
         .setParameter("username", user.getUsername())
         .getResultList();
      
      if ( existing.size()==0 )
      {
         persist(user);
         info("Registered new user #{user.username}");
         return "/registered.jspx";
      }
      else
      {
         addFacesMessage("User #{user.username} already exists");
         return null;
      }
   }
}

Comme vous pouvez le voir, ce n'est pas une ammélioration stupéfiante...