JSF/Backing Bean

Cette page a été rédigée il y a fort fort longtemps, et n'a pas tellement été mise à jour.

 

Vous savez, moi je ne crois pas qu'il y ait de bonne ou de mauvaise page. Moi, si je devais résumer mon wiki aujourd'hui avec vous, je dirais que c'est d'abord des rencontres. Des gens qui m'ont tendu la main, peut-être à un moment où je ne pouvais pas, où j'étais seul chez moi. Et c'est assez curieux de se dire que les hasards, les rencontres forgent une destinée... Parce que quand on a le goût de la chose, quand on a le goût de la chose bien faite, le beau geste, parfois on ne trouve pas l'interlocuteur en face je dirais, le miroir qui vous aide à avancer. Alors ça n'est pas mon cas, comme je disais là, puisque moi au contraire, j'ai pu ; et je dis merci au wiki, je lui dis merci, je chante le wiki, je danse le wiki... je ne suis qu'amour ! Et finalement, quand des gens me disent « Mais comment fais-tu pour avoir cette humanité ? », je leur réponds très simplement que c'est ce goût de l'amour, ce goût donc qui m'a poussé aujourd'hui à entreprendre une construction logicielle... mais demain qui sait ? Peut-être simplement à me mettre au service de la communauté, à faire le don, le don de soi.

Cet article décrit l’implémentation d’une super-classe pour le développement des backing beans.

Classe abstraite

Cette classe fournit un ensemble de méthodes utiles pour des opérations récurrentes effectuées dans un backing bean. Les backing beans de l’application devront hériter de cette classe abstraite afin de bénéficier des services fournis.

public class AbstractBackingBean implements Serializable {
  ...
}

Accès aux objets JSF

Il s’agit de pouvoir récupérer facilement les objets FacesContext, Application, ExternalContext, VariableResolver, ainsi que les objets déclarés dans le fichier de configuration JSF (méthode getVariable()).

protected FacesContext getFacesContext() {
  return FacesContext.getCurrentInstance();
}
protected Application getApplication() {
  return getFacesContext().getApplication();
}
protected ExternalContext getExternalContext() {
  return getFacesContext().getExternalContext();
}
protected VariableResolver getVariableResolver() {
  return getApplication().getVariableResolver();
}
protected Object getVariable(String name) {
  Object o = getVariableResolver().resolveVariable(getFacesContext(), name);
  return o;
}

Accès aux objets Java Web

Même si JSF masque les objets HttpRequest et HttpSession, il est parfois utile d’intervenir directement sur ces objets.

protected HttpSession getSession() {
  return getRequest().getSession();
}
protected HttpServletRequest getRequest() {
  HttpServletRequest request = (HttpServletRequest)getFacesContext().getExternalContext().getRequest();
  return request;
}
protected String getParameter(String name) {
  HttpServletRequest request = getRequest();
  return request.getParameter(name);
}
protected void addSessionAttribute(String name, Object o) {
  FacesContext facesContext = getFacesContext();
  StringBuilder sb = new StringBuilder();
  sb.append("#{sessionScope.").append(name).append("}");
  getApplication().createValueBinding(sb.toString()).setValue(facesContext, o);
}

Méthode de fabrication d’objets SelectItem

Lorsque l’on utilise des composants graphiques de type sélection (<h:selectOneMenu, <h:selectManyListBox>…​), il faut transformer les listes d’objets récupérés depuis la couche métier, en listes d’objets de type SelectItem pour alimenter les valeurs sélectionnables des composants graphiques. Voici une méthode qui permet d’automatiser cette tâche fastidieuse :

/**
 * @param original collection des objets issus de la couche métier
 * @param labelProperty nom de la propriété des objets métier utilisée comme libellé dans SelectItem
 * @param valueProperty nom de la propriété des objets métier utilisée comme valeur dans SelectItem
 */
protected List<SelectItem> buildSelectCollection(Collection<?> original, String labelProperty, String valueProperty) {
List<SelectItem> result = new ArrayList<SelectItem>();
  if (original != null) {
    try {
      for (Object obj : original) {
        String label = (String) PropertyUtils.getProperty(obj, labelProperty);
        Object value = PropertyUtils.getProperty(obj, valueProperty);
        SelectItem item = new SelectItem(value, label);
        result.add(item);
      }
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
  return result;
}

Voici une version plus élaborées, permettant de fabriquer le libellé des objets SelectItem à partir de plusieurs propriétés et d’un pattern de formattage:

/**
 * @param original collection des objets issus de la couche métier
 * @param valueProperty nom de la propriété des objets métier utilisée comme valeur dans SelectItem
 * @param pattern pattern de formattage utilisé pour la fabrication du libellé des SelectItem
 * @param labelProperties nom des propriétés des objets métier injectées dans le pattern de formattage
 */
protected List<SelectItem> buildSelectCollection(Collection<?> original, String valueProperty,
                                                    String pattern, String... labelProperties) {
  List<SelectItem> result = new ArrayList<SelectItem>();
  if (original != null) {
    try {
      for (Object obj : original) {
        Object[] params = new Object[labelProperties.length];
        for (int i = 0; i < params.length; i++) {
          String prop = (String) PropertyUtils.getProperty(obj, labelProperties[i]);
          params[i] = prop;
        }
        String label = getLabel(pattern, params);
        Object value = PropertyUtils.getProperty(obj, valueProperty);
        SelectItem item = new SelectItem(value, label);
        result.add(item);
      }
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
  return result;
}
private String getLabel(String pattern, Object[] params) {
  String label = MessageFormat.format(pattern, params);
  return label;
}

Ajouter un message

Exemple de méthodes pour ajouter un message JSF.

protected void addMessage(Severity severity, String message) {
  getFacesContext().addMessage(null, new FacesMessage(severity, message, message));
}
protected void addMessage(Severity severity, String summary, String detail) {
  getFacesContext().addMessage(null, new FacesMessage(severity, summary, detail));
}
protected void addMessage(String clientid, Severity severity, String summary, String detail) {
  getFacesContext().addMessage(clientid, new FacesMessage(severity, summary, detail));
}
protected void addMessage(UIComponent component, Severity severity, String summary, String detail) {
  String clientid = component.getClientId(getFacesContext());
  getFacesContext().addMessage(clientid, new FacesMessage(severity, summary, detail));
}

Accès au numéro de phase JSF dans un backing bean

Il est parfois utile de pouvoir lire l’identifiant de la phase JSF en cours de traitement JSF. Le pattern ci-dessous est mis en oeuvre à l’aide d’un listener JSF et d’un backing bean dédié. Le principe est le suivant :

  • un phase listener écoute toutes les phases JSF, puis récupère un lien sur un backing bean qui possède un champ d’instance de type PhaseId

  • le listener injecte le PhaseId dans le backing bean

  • ce dernier est disponible auprès de tous les autres backing beans puisqu’il sera lu dans le backing bean abstrait, via le variable resolver

Mise en oeuvre :

public class PhaseBean implements Serializable {
  private transient PhaseId currentPhaseId;
  public PhaseId getCurrentPhaseId() {
    return currentPhaseId;
  }
  public void setCurrentPhaseId(PhaseId currentPhaseId) {
    this.currentPhaseId = currentPhaseId;
  }
}
public class BasePhaseListener implements PhaseListener {
  public void beforePhase(PhaseEvent event) {
    FacesContext ctx = event.getFacesContext();
    VariableResolver vr = ctx.getApplication().getVariableResolver();
    PhaseBean phaseBean = (PhaseBeanvr.resolveVariable(ctx, "phaseBean");
    phaseBean.setCurrentPhaseId(event.getPhaseId());
  }
  public PhaseId getPhaseId() {
    return PhaseId.ANY_PHASE;
  }
}
public class AbstractBackingBean implements Serializable {
  private PhaseBean phaseBean;
  public PhaseBean getPhaseBean() {
    if (phaseBean == null) {
      phaseBean = (PhaseBean)getVariable("phaseBean");
    }
    return phaseBean;
  }
  public PhaseId getPhaseId() {
    return getPhaseBean().getCurrentPhaseId();
  }
}

Reposter les Value Change Events

Suivant l’article sur les value change listener, on peut ajouter une méthode helper dans notre backing bean abstrait :

protected boolean repostEvent(ValueChangeEvent event) {
  PhaseId phaseId = event.getPhaseId();
  if (phaseId.equals(PhaseId.ANY_PHASE)) {
     event.setPhaseId(PhaseId.UPDATE_MODEL_VALUES);
     event.queue();
  } else if (phaseId.equals(PhaseId.UPDATE_MODEL_VALUES)) {
    return true;
  }
  return false;
}

Cette méthode peut être utilisée de la façon suivante dans un backing bean concret :

public boolean myValueChangeListener(ValueChangeEvent event) {
  if (!repostEvent(event)) {
     // le champ d'instance myValue est mis à jour par
     // le listener et ne sera pas écrasé plus tard par
     // invocation du setter
     this.myValue = event.getNewValue();
  }
}