Viadeo

[lang_en]How to use Hibernate filters in Seam[/lang_en][lang_fr]Utilisation des filtres hibernate dans Seam[/lang_fr]

[lang_fr]JBoss Seam est vraiment un framework très riche mais certaines de ses fonctionalités sont mal documentées.

C’est le cas des filtres Hibernate. La documentation de Seam sur le sujet est plutôt lapidaire et inutilisable en l’état.[/lang_fr]

[lang_en]JBoss Seam is very rich and powerful framework but some of its features are badly documented.

Hibernate Filters is one of these forgotten features. In fact filters documentation in Seam is rather useless.
[/lang_en]

[lang_fr]
Je ne vais pas revenir en détail sur les filtre Hibernate qui sont très bien documentés dans la documentation d’Hibernate, rappelons juste qu’il s’agit d’une solution ingénieuse permettant d’enrichir la clause “where” d’une requête hibernate au runtime.

Pour créer un filtre Hibernate dans le contexte d’un projet Seam, on aura recours aux annotations Hibernate correspondantes.

Prenons comme exemple un stock de véhicule d’occasion pour lequel on a développé toute une série de formulaires de recherche, de pages de navigation et un composant Seam exploitant une collection de requêtes pour peupler les listes de mots clés des formulaires de recherche et orchestrer la navigation dans le stock de véhicules.

Les véhicules comportent un champ “stockVoLib” qui détermine l’origine du véhicule (reprise, véhicule de direction ou véhicule de démonstration). Ce champ était ignoré jusque là et tout à coup notre client souhaite créer une navigation identique à celle existante mais restreinte à un type d’origine (je veux pouvoir naviguer uniquement dans les véhicules de direction).

Pour répondre à cette demande. On peut soit modifier l’existant en enrichissant toutes les pages et toutes les requêtes avec le critère “stockVoLib” soit mettre en œuvre un filtre qui nous fera économiser beaucoup de travail et évitera de fragiliser les processus existant en nous épargnant un refactoring lourd.

Suivant ma règle d’or préférée : “quand on peut déplacer du développement en paramétrage et qu’on ne créé pas une usine à gaz, il ne faut pas hésiter”, j’opte pour le filtre et ça tombe bien pour la suite de l’aticle :-) .

1.Création d’un filtre Hibernate en annotations

On commence par définir le filtre soit dans l’entité concernée, soit au niveau package. Comme un filtre peut s’appliquer a plusieurs entités je préfère le définir au niveau package. J’ajoute donc un fichier “package-info.java” dans le package de mes entités pour y définir mon filtre.
[/lang_fr]

[lang_en]

I won’t explain in details Hibernate Filters here. Hibernate documentation does it ways better. To make short it’s a smart solution to add conditions to the “where” clause of a request at runtime.

To create such a filter in Seam, one will also have to read Hibernate annotations on that matter.

For instance let say we have an used cars stock for which we had developed a bunch of forms and pages to search and navigate through it. We also created a Seam component to handle a requests collection to populate search form’s combo box fields and orchestrate all the search and navigation.

In database, cars have a “stockVoLib” field which gives car origin (demonstration, management car, etc…). Until now this field wasn’t used by the application and search were done on all the stock db. But now, our customer ask for a new navigation having exactly the same look at the first one but restricted on a given car origin.

We can answer to this request in two different ways.

  • Refactor all the running website’s engine to introduce the new “stcokVoLib” parameter in all the requests and code calling these requests
  • Create a filter which will save us a lot of refactoring work on existing code.

Guess what ? I prefer to create a filter :-) (good news for the end of this post).

1.Hibernate filter creation with annotations

First thing to do : define the filter. We can dot it in the target entity or at the package level. As a filter can be applied to more than one entity I prefer the package level option. So I add a “package-info.java” file to my entities package to define the filter in it.
[/lang_en]

@FilterDef(name = "stockLib", defaultCondition = "stockLibele = :aStockLib",
parameters = @ParamDef(name = "aStockLib", type = "string"))

package org.fpp.domain;

import org.hibernate.annotations.FilterDef;
import org.hibernate.annotations.ParamDef;

[lang_fr]
Mon filtre s’appelle stockLib, il comporte une condition par défaut qui sera utilisée si aucune condition n’est définie à l’application du filtre sur l’entité et il comporte un paramètre de type chaîne appelé “aStockLib”.
A toutes fins utiles, je rappelle que la condition définie dans un filtre correspond à un “where” SQL et non HQL.

J’applique ensuite le filtre sur une ou plusieurs entités de mon application (un filtre peut aussi être appliqué à une collection). En l’occurrence je n’ai qu’une seule entité concernée “usedCar”;
[/lang_fr]
[lang_en]
The name of the filter is “stockLib”, it has a default condition which will be used if there is no condition defined when the filter is applied on the entity and it also has one string parameter : “aStockLib”.
Keep in mind that the condition defined here is not a HQL “where” but a SQL one.

After that I apply the filter on one or more entities (I could also attach it to a collection). In my use case I only have one entity concerned : “usedCar”.
[/lang_en]

@Entity
@Filter(name = "stockLib")
public class usedCar {
...

[lang_fr]

2.Utilisation du filtre sous Hibernate

A ce stade, le filtre est utilisable avec Hibernate. On peut l’activer et le paramétrer en récupérant la session Hibernate (si on est en JPA on utilise le getDelegate de l’entityManager).
[/lang_fr]
[lang_en]

3.Using the filter with Hibernate

Now, I can use the filter with Hibernate. I can activate it and send it parameter values by getting Hibernate session (if we are using JPA we do that with EntityManager.getDelegate())
[/lang_en]

Session session=(Session) entityManager.getDelegate();
session.enableFilter("stockLib").setParameter("aStockLib", "VD");
...
session.disableFilter("stockLib");

[lang_fr]

3.Et dans Seam

Mais Seam permet d’aller plus loin en donnant la possibilité de créer un composant filtre (encapsulant le filtre Hibernate) dans lequel on peut injecter dynamiquement les paramètres.
Il suffit d’ajouter ce composant dans le fichiers components.xml de Seam.
[/lang_fr]

[lang_en]

3.What about Seam ?

You can do better with Seam by creating a filter component (wraping Hibernate filter) in which you can inject parameter at runtime.
We only have to have this component in the Seam components.xml file.
[/lang_en]

<persistence:filter name="stockLibFilter">
  <persistence:name>stockLib</persistence:name>
  <persistence:parameters>
    <key>aStockLib</key>
    <value>#{parameterHdl.stockLib}</value>
  </persistence:parameters>
</persistence:filter>

[lang_fr]
A noter que la valeur des paramètres dans ce “meta-filtre” Seam doivent toujours être en expression language même si on souhaite injecter une constante : #{‘VD’}.
Ici, “parameterHdl” est un composant Seam dans lequel le paramètre stockLib est injecté depuis l’URL grâce à l’entrée suivante dans le fichier pages.xml
[/lang_fr]
[lang_en]
Remember that parameters value in this “meta-filter” should always be expressed in expression language even if we want inject a constant : #{‘VD’}.
In our example “parameterHdl” is a Seam component in which the stockLib parameter is injected from the URL according tto the the following configuration in pages.xml
[/lang_en]

<param name="stockLib" value="#{parameterHdl.stockLib}" required="false"/>

[lang_fr]
A présent le filtre Seam peut être utilisé pour créer un EntityManager spécifique sur lequel le filtre est activé.
[/lang_fr]
[lang_en]
At that point Seam filter can be injected in a specific EntityManager on which the filter will be always activated.
[/lang_en]

<persistence:managed-persistence-context name="emStockLib" auto-create="true" persistence-unit-jndi-name="java:/fppEntityManagerFactory" >
  <persistence:filters>
    <value>#{stockLibFilter}</value>
  </persistence:filters>
</persistence:managed-persistence-context>

[lang_fr]
Ce nouvel entityManager pourra être injecté dans les composants à la place l’entityManager standard (sans filtre) pour bénéficier du filtre. A noter que contrairement à Hibernate, le filtre est actif par défaut sur cet entityManger, il n’y a pas de manipulation à faire pour l’activer.
Mais ce n’est pas tout !
[/lang_fr]
[lang_en]
This new entityManager could be injected in components instead of the standard entityManager (without filter). There is nothing to do to activate the filter, it is on by default.
But that’s not all !
[/lang_en]

[lang_fr]

4.Activation dynamique des filtres dans Seam

Et c’est là où la documentation de Seam est vraiment lacunaire : l’activation des filtres peut être gérée dynamiquement.

Ainsi on peut écrire quelque chose du type :
[/lang_fr]
[lang_en]

4.Dynamic filters activation in Seam

It’s here that Seam documentation is lacking something very useful : filters activation can be dynamically set at run time.

Thus we can write someting like :
[/lang_en]

<persistence:filter name="stockLibFilter" enabled="#{!(empty parameterHdl.stockLib)}">
  <persistence:name>stockLib</persistence:name>
  <persistence:parameters>
    <key>aStockLib</key>
    <value>#{parameterHdl.stockLib}</value>
  </persistence:parameters>
</persistence:filter>

[lang_fr]
Notez le paramètre enabled qui contient une expression booléenne. Ici on n’active le filtre que si le champ stockLib du composant parameterHdl n’est pas vide, c’est à dire, par extension, si l’url contient un paramètre stockLib avec une valeur.

Dans ce mode d’utilisation on peut appliquer le filtre à l’entityManager principal de l’application (pas besoin d’en créer un spécifique pour le filtre) et le filtre s’activera ou non en fonction de l’évaluation de son paramètre enabled. Paramètre qui sera évalué à chaque appel de l’entityManager (et oui on est sous Seam).
[/lang_fr]
[lang_en]
Pay attention to the “enabled” parameter in which we put a boolean expression. It means that the filter is activated only if stockLib field of component parameterHdl is not empty. As this field is filled by URL parameter injection, an existing stockLib parameter in URL activate the filter.

In this usage we can apply the filter to the application’s main entityManager (no need to create a specific one) and this filter will be activated if its enabled parameter is satisfied. This test will be evaluated each time the entityManager is called (that’s Seam’s “magic”).
[/lang_en]

<persistence:managed-persistence-context name="entityManager" auto-create="true" persistence-unit-jndi-name="java:/fppEntityManagerFactory" >
  <persistence:filters>
    <value>#{stockLibFilter}</value>
  </persistence:filters>
</persistence:managed-persistence-context>

[lang_fr]
On a donc injecté un filtre Hibernate activé conditionnellement dans l’entityManager et celui-ci s’activera ou non en fonction du context de l’application.
Seule précaution à prendre, si le paramètre enabled du filtre utilise un composant Seam comme c’est le cas ici, il faut que ce composant n’utilise pas l’entityManager dans lequel le filtre est injecté , sinon on obtient un plantage pour cause de référence circulaire au démarrage de l’application.
[/lang_fr]
[lang_en]
So we have injected a dynamically activated Hibernate filter in the entityManager and its activation will depend of the application context.
One last thing to keep in mind : if the enabled parameter of the filter uses a Seam component (like in my example), you should not inject the “filtered” entityManager in this component. If you did you would get an exception at the application launch caused by the circular reference.
[/lang_en]