JSF/Backing Bean

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 :

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();
  }
}