1 Reply Latest reply on Oct 31, 2007 6:49 PM by gonzalad

    Custom ObjectConverter for selectItems - howto El in convert

    gonzalad Apprentice

      Hello,

      I'm coding a generic object converter which will work for non jpa backends (i.e. jca, jdbc, ...) - similar to the one in http://www.jboss.com/index.html?module=bb&op=viewtopic&t=122149&postdays=0&postorder=asc&start=10.
      But stateless.

      The converter is used for selectItems - Here is a sample jsf code to display a country selectBox (sx:convertObject) :

      <h:selectOneMenu id="country"
       value="#{client.adress.country}" styleClass="selectBox">
       <sx:convertObject var="value" asStringEl="value.id"/>
       <s:selectItems value="#{countryManager.allCountries}"
       var="country" label="#{country.libelle}"
       noSelectionLabel="Please Select..." />
      </h:selectOneMenu>


      How does it work ?
      On render, it applies the el #{value.id} on every object of selectItems (so call country.getId for every country).
      On submit, it applies once more this el on all selectItems objects until retrieving the selected string value.

      Here's my questions :
      1. I would lik to use a real EL in asStringEl attribute.
      How can I achieve it in a converter ?
      I've tried setting asStringEl="#{value.id}", but facelets interprets it *before* my converter - and I retrieve value "" in my converter.
      2. Does this approach appears fine or is there a better one (it's my first Jsf converter - so quite a newbie...).

      Sorry for this long mail and thank you very much for you help.

      Here's my converter code :

      @Name("com.natixis.sphinx.jsf.component.objectConverter")
      @org.jboss.seam.annotations.jsf.Converter
      @Scope(ScopeType.STATELESS)
      @Install(precedence=Install.FRAMEWORK)
      public class ObjectConverter implements Converter, Serializable {
      
       /** Champ serialVersionUID. */
       private static final long serialVersionUID = -8229857238533003490L;
       private String asStringEl;
       private String var;
      
       public Object getAsObject(FacesContext aFacesContext, UIComponent aComponent, String aValueAsString) {
       if (aValueAsString == null) {
       return null;
       }
      
       //1. récupération valeurs selectItems
       SelectItemsIterator lIterator = new SelectItemsIterator(aComponent);
      
       //2. recherche du SelectItem correspondant à aValueAsString
       while (lIterator.hasNext()) {
       Object lItem = lIterator.next().getValue();
       String lAsString = convertToString (lItem, aFacesContext);
       if (aValueAsString.equals (lAsString)) {
       return lItem;
       }
       }
       //TODO : should never happen or illegal data.... ou aucune valeur sélectionnée
       return null;
       }
      
       @SuppressWarnings("unchecked")
       private void restoreInRequest(Object aOld, FacesContext aFacesContext) {
       Map lRequestMap = aFacesContext.getExternalContext().getRequestMap();
       if (aOld == null) {
       lRequestMap.remove(getVar());
       } else {
       lRequestMap.put(getVar(), aOld);
       }
       }
      
       @SuppressWarnings("unchecked")
       private Object storeInRequest(Object aItem, FacesContext aFacesContext) {
       Map lRequestMap = aFacesContext.getExternalContext().getRequestMap();
       return lRequestMap.put(getVar(), aItem);
       }
      
       public String getVar() {
       //valeur attribut var
       return var;
       }
      
       public void setVar (String aVar) {
       var = aVar;
       }
      
       public String getAsStringEl() {
       return "#{"+asStringEl+"}";
       }
      
       public void setAsStringEl(String aValue) {
       asStringEl = aValue;
       }
      
       public String getAsString(FacesContext aFacesContext, UIComponent aComponent, Object aValue) {
       if (aValue == null) {
       return null;
       }
       return convertToString(aValue, aFacesContext);
       }
      
       private String convertToString (Object aObject, FacesContext aFacesContext) {
       Object lOld = storeInRequest (aObject, aFacesContext);
       try {
       return Interpolator.instance().interpolate(getAsStringEl());
       } finally {
       restoreInRequest (lOld, aFacesContext);
       }
       }
      }
      


      Here's the taglib.xml file :

      <?xml version="1.0"?>
      <!DOCTYPE facelet-taglib PUBLIC
       "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN"
       "facelet-taglib_1_0.dtd">
      
      <facelet-taglib>
       <namespace>http://test.com/sphinx-jsf/taglib</namespace>
       <tag>
       <tag-name>convertObject</tag-name>
       <converter>
      <converter-id>com.natixis.sphinx.jsf.component.objectConverter</converter-id>
       </converter>
       </tag>
      </facelet-taglib>
      


      An utility file (from myfaces 1.1.5) :

      package com.natixis.sphinx.jsf.component;
      
      import java.util.*;
      
      import javax.faces.component.UIComponent;
      import javax.faces.component.UISelectItem;
      import javax.faces.component.UISelectItems;
      import javax.faces.component.UIViewRoot;
      import javax.faces.el.ValueBinding;
      import javax.faces.model.SelectItem;
      
      
      /**
       * <p>Cette classe est issue de MyFaces 1.1.5.</p>
       *
       * <p>Elle nous permet de récupérer simplement les valeur d'un composant selectItems
       * à partir du composant parent (i.e. selectOneMenu par exemple).</p>
       */
      class SelectItemsIterator implements Iterator<SelectItem>
      {
       private final Iterator _childs;
       private Iterator _nestedItems;
       private Object _nextItem;
       private String _collectionLabel;
       private UISelectItems _currentUISelectItems;
      
       public SelectItemsIterator(UIComponent selectItemsParent)
       {
       _childs = selectItemsParent.getChildren().iterator();
       }
      
       @SuppressWarnings("unchecked")
       public boolean hasNext()
       {
       if(_nextItem != null)
       {
       return true;
       }
       if(_nestedItems != null)
       {
       if(_nestedItems.hasNext())
       {
       return true;
       }
       _nestedItems = null;
       }
       if (_childs.hasNext())
       {
       UIComponent child = (UIComponent) _childs.next();
       if (child instanceof UISelectItem)
       {
       UISelectItem uiSelectItem = (UISelectItem) child;
       Object item = uiSelectItem.getValue();
       if (item == null)
       {
       Object itemValue = ((UISelectItem) child).getItemValue();
       String label = ((UISelectItem) child).getItemLabel();
       String description = ((UISelectItem) child)
       .getItemDescription();
       boolean disabled = ((UISelectItem) child).isItemDisabled();
       if (label == null)
       {
       label = itemValue.toString();
       }
       item = new SelectItem(itemValue, label, description,
       disabled);
       }
       else if (!(item instanceof SelectItem))
       {
       ValueBinding binding = ((UISelectItem) child)
       .getValueBinding("value");
       throw new IllegalArgumentException(
       "Value binding '"
       + (binding == null ? null : binding.getExpressionString())
       + "' of UISelectItem : "
       + getPathToComponent(child)
       + " does not reference an Object of type SelectItem");
       }
       _nextItem = item;
       return true;
       }
       else if (child instanceof UISelectItems)
       {
       _currentUISelectItems = ((UISelectItems) child);
       Object value = _currentUISelectItems.getValue();
      
       if (value instanceof SelectItem)
       {
       _nextItem = value;
       return true;
       }
       else if (value instanceof SelectItem[])
       {
       _nestedItems = Arrays.asList((SelectItem[]) value)
       .iterator();
       _collectionLabel = "Array";
       return hasNext();
       }
       else if (value instanceof Collection)
       {
       _nestedItems = ((Collection)value).iterator();
       _collectionLabel = "Collection";
       return hasNext();
       }
       else if (value instanceof Map)
       {
       Map map = ((Map) value);
       Collection items = new ArrayList(map.size());
       for (Iterator it = map.entrySet().iterator(); it
       .hasNext();)
       {
       Map.Entry entry = (Map.Entry) it.next();
       items.add(new SelectItem(entry.getValue(), entry
       .getKey().toString()));
       }
       _nestedItems = items.iterator();
       _collectionLabel = "Map";
       return hasNext();
       }
       else
       {
       ValueBinding binding = _currentUISelectItems.getValueBinding("value");
      
       throw new IllegalArgumentException(
       "Value binding '"
       + (binding == null ? null : binding
       .getExpressionString())
       + "'of UISelectItems with component-path "
       + getPathToComponent(child)
       + " does not reference an Object of type SelectItem, SelectItem[], Collection or Map but of type : "
       + ((value == null) ? null : value
       .getClass()
       .getName()));
       }
       }
       else
       {
       //todo: may other objects than selectItems be nested or not?
       //log.error("Invalid component : " + getPathToComponent(child) + " : must be UISelectItem or UISelectItems, is of type : "+((child==null)?"null":child.getClass().getName()));
       }
       }
       return false;
       }
      
       public SelectItem next()
       {
       if (!hasNext())
       {
       throw new NoSuchElementException();
       }
       if(_nextItem != null)
       {
       Object value = _nextItem;
       _nextItem = null;
       return (SelectItem) value;
       }
       if (_nestedItems != null)
       {
       Object item = _nestedItems.next();
       if (!(item instanceof SelectItem))
       {
       ValueBinding binding = _currentUISelectItems
       .getValueBinding("value");
       throw new IllegalArgumentException(
       _collectionLabel + " referenced by UISelectItems with binding '"
       + (binding == null ? null : binding
       .getExpressionString())
       + "' and Component-Path : " + getPathToComponent(_currentUISelectItems)
       + " does not contain Objects of type SelectItem");
       }
       return (SelectItem) item;
       }
       throw new NoSuchElementException();
       }
      
       public void remove()
       {
       throw new UnsupportedOperationException();
       }
      
      
       private String getPathToComponent(UIComponent component)
       {
       StringBuffer buf = new StringBuffer();
      
       if(component == null)
       {
       buf.append("{Component-Path : ");
       buf.append("[null]}");
       return buf.toString();
       }
      
       getPathToComponent(component,buf);
      
       buf.insert(0,"{Component-Path : ");
       buf.append("}");
      
       return buf.toString();
       }
      
       private void getPathToComponent(UIComponent component, StringBuffer buf)
       {
       if(component == null)
       return;
      
       StringBuffer intBuf = new StringBuffer();
      
       intBuf.append("[Class: ");
       intBuf.append(component.getClass().getName());
       if(component instanceof UIViewRoot)
       {
       intBuf.append(",ViewId: ");
       intBuf.append(((UIViewRoot) component).getViewId());
       }
       else
       {
       intBuf.append(",Id: ");
       intBuf.append(component.getId());
       }
       intBuf.append("]");
      
       buf.insert(0,intBuf);
      
       if(component!=null)
       {
       getPathToComponent(component.getParent(),buf);
       }
       }
      }
      


        • 1. Re: Custom ObjectConverter for selectItems - howto El in con
          gonzalad Apprentice

           

          1. I would lik to use a real EL in asStringEl attribute.
          How can I achieve it in a converter ?

          It appears only UIComponents support value expression associated with them.
          See http://issues.apache.org/jira/browse/MYFACES-189 or chapter 5 page 5-1 of JSF 1.2 spec.
          So, I'm stuck using a non EL expression and adding "#{" + "}" in my converter code before evaluating the resulting EL - quite a hack !

          2. Does this approach appears fine or is there a better one (it's my first Jsf converter - so quite a newbie...).

          I would really appreciate feedback on this one.
          Is this converter approach fine or just awfully bad (and why ? performance problems ?).

          Thanks once more !