Package org.ajax4jsf.component

Source Code of org.ajax4jsf.component.UIDataAdaptor

/**
* License Agreement.
*
* Rich Faces - Natural Ajax for Java Server Faces (JSF)
*
* Copyright (C) 2007 Exadel, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License version 2.1 as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
*/

package org.ajax4jsf.component;

import java.io.IOException;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import javax.el.ELContext;
import javax.el.ValueExpression;
import javax.faces.FacesException;
import javax.faces.application.FacesMessage;
import javax.faces.component.ContextCallback;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.NamingContainer;
import javax.faces.component.StateHolder;
import javax.faces.component.UIColumn;
import javax.faces.component.UIComponent;
import javax.faces.component.UIData;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.FacesEvent;
import javax.faces.event.FacesListener;
import javax.faces.event.PhaseId;
import javax.faces.model.DataModel;
import javax.faces.model.ListDataModel;
import javax.faces.render.Renderer;

import org.ajax4jsf.model.DataComponentState;
import org.ajax4jsf.model.DataVisitor;
import org.ajax4jsf.model.ExtendedDataModel;
import org.ajax4jsf.model.SerializableDataModel;
import org.ajax4jsf.renderkit.AjaxChildrenRenderer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
* Base class for iterable components, like dataTable, Tomahawk dataList,
* Facelets repeat, tree etc., with support for partial rendering on AJAX
* responces for one or more selected iterations.
*
* @author shura
*
*/
public abstract class UIDataAdaptor extends UIData implements AjaxDataEncoder {

  /**
   *
   */
  public static final String COMPONENT_STATE_ATTRIBUTE = "componentState";

  public final static DataModel EMPTY_MODEL = new ListDataModel(
      Collections.EMPTY_LIST);
 
  private static final Log _log = LogFactory.getLog(UIDataAdaptor.class);

  /**
   * Base class for visit data model at phases decode, validation and update
   * model
   *
   * @author shura
   *
   */
  protected abstract class ComponentVisitor implements DataVisitor {

    public void process(FacesContext context, Object rowKey, Object argument)
        throws IOException {
      setRowKey(context, rowKey);
      if (isRowAvailable()) {
        Iterator<UIComponent> childIterator = dataChildren();
        while (childIterator.hasNext()) {
          UIComponent component = childIterator.next();
          if (UIColumn.class.equals(component.getClass())) {
            for (UIComponent children : component.getChildren()) {
              processComponent(context, children, argument);
            }
          } else {
            processComponent(context, component, argument);
          }
        }

      }
    }

    public abstract void processComponent(FacesContext context,
        UIComponent c, Object argument) throws IOException;

  }

  /**
   * Visitor for process decode on children components.
   */
  protected ComponentVisitor decodeVisitor = new ComponentVisitor() {

    public void processComponent(FacesContext context, UIComponent c,
        Object argument) {
        c.processDecodes(context);
    }

  };

  /**
   * Visitor for process validation phase
   */
  protected ComponentVisitor validateVisitor = new ComponentVisitor() {

    public void processComponent(FacesContext context, UIComponent c,
        Object argument) {
        c.processValidators(context);
    }

  };

  /**
   * Visitor for process update model phase.
   */
  protected ComponentVisitor updateVisitor = new ComponentVisitor() {

    public void processComponent(FacesContext context, UIComponent c,
        Object argument) {
        c.processUpdates(context);
    }

  };

  /**
   * Base client id's of this component, for wich invoked encode... methods.
   * Component will save state and serialisable models for this keys only.
   */
  private Set<String> _encoded;

  /**
   * Storage for data model instances with different client id's of this
   * component. In case of child for UIData component, this map will keep data
   * models for different iterations between phases.
   */
  private Map<String, ExtendedDataModel> _modelsMap = new HashMap<String, ExtendedDataModel>();

  /**
   * Reference for curent data model
   */
  private ExtendedDataModel _currentModel = null;

  /**
   * States of this component for diferent iterations, same as for models.
   */
  private Map<String, DataComponentState> _statesMap = new HashMap<String, DataComponentState>();

  /**
   * Reference for current component state.
   */
  private DataComponentState _currentState = null;

  /**
   * Name of EL variable for current component state.
   */
  private String _stateVar;

  private String _rowKeyVar;

  /**
   * Key for current value in model.
   */
  private Object _rowKey = null;
 
 
  private Converter _rowKeyConverter = null;

  /**
   * Values of row keys, encoded on ajax response rendering.
   */
  private Set<Object> _ajaxKeys = null;

  /**
   * Internal set of row keys, encoded on ajax response rendering and cleared after response complete
   */
  private Set<Object> _ajaxRequestKeys = null;

  private Object _ajaxRowKey = null;

  private Map<String, Object> _ajaxRowKeysMap = new HashMap<String, Object>();

  /**
   * Get name of EL variable for component state.
   *
   * @return the varState
   */
  public String getStateVar() {
    return _stateVar;
  }

  /**
   * @param varStatus
   *            the varStatus to set
   */
  public void setStateVar(String varStatus) {
    this._stateVar = varStatus;
  }

  /**
   * @return the rowKeyVar
   */
  public String getRowKeyVar() {
    return this._rowKeyVar;
  }

  /**
   * @param rowKeyVar
   *            the rowKeyVar to set
   */
  public void setRowKeyVar(String rowKeyVar) {
    this._rowKeyVar = rowKeyVar;
  }

  /*
   * (non-Javadoc)
   *
   * @see javax.faces.component.UIData#getRowCount()
   */
  public int getRowCount() {
    return getExtendedDataModel().getRowCount();
  }

  /*
   * (non-Javadoc)
   *
   * @see javax.faces.component.UIData#getRowData()
   */
  public Object getRowData() {
    return getExtendedDataModel().getRowData();
  }

  /*
   * (non-Javadoc)
   *
   * @see javax.faces.component.UIData#isRowAvailable()
   */
  public boolean isRowAvailable() {
    return this.getExtendedDataModel().isRowAvailable();
  }

  /*
   * (non-Javadoc)
   *
   * @see javax.faces.component.UIData#setRowIndex(int)
   */
  public void setRowIndex(int index) {
    FacesContext faces = FacesContext.getCurrentInstance();
    ExtendedDataModel localModel = getExtendedDataModel();
   
    boolean rowAvailable = isRowAvailable();
   
   
//    if (rowAvailable) {
      // save child state
      this.saveChildState(faces);
//    }

    // Set current model row by int, but immediately get value from model.
    // for compability, complex models must provide values map between
    // integer and key value.
    localModel.setRowIndex(index);
   
    rowAvailable = isRowAvailable();
    this._rowKey = localModel.getRowKey();
    this._clientId = null;
   
    boolean rowSelected = this._rowKey != null && rowAvailable;

    setupVariable(faces, localModel, rowSelected);
   
//    if (rowAvailable ) {
      // restore child state
      this.restoreChildState(faces);
//    }
  }

  /*
   * (non-Javadoc)
   *
   * @see javax.faces.component.UIData#getRowIndex()
   */
  public int getRowIndex() {
    return getExtendedDataModel().getRowIndex();
  }

  /**
   * Same as for int index, but for complex model key.
   *
   * @return
   */
  public Object getRowKey() {
    return this._rowKey;
  }

  public void setRowKey(Object key) {
    setRowKey(FacesContext.getCurrentInstance(), key);
  }

  /**
   * Setup current row by key. Perform same functionality as
   * {@link UIData#setRowIndex(int)}, but for key object - it may be not only
   * row number in sequence data, but, for example - path to current node in
   * tree.
   *
   * @param faces -
   *            current FacesContext
   * @param key
   *            new key value.
   */
  public void setRowKey(FacesContext faces, Object key) {
    ExtendedDataModel localModel = getExtendedDataModel();
   
    boolean rowAvailable = isRowAvailable();
   
//    if (rowAvailable) {
      // save child state
      this.saveChildState(faces);
//    }

    this._rowKey = key;
    this._clientId = null;
   
    localModel.setRowKey(key);

    rowAvailable = isRowAvailable();
    boolean rowSelected = key != null && rowAvailable;

    //XXX check for row availability
    setupVariable(faces, localModel, rowSelected);
   
//    if (rowAvailable ) {
      // restore child state
      this.restoreChildState(faces);
//    }
  }

  /**
   * @return the rowKeyConverter
   */
  public Converter getRowKeyConverter() {
    Converter converter = _rowKeyConverter;
    if (null == converter) {
      ValueExpression ve = getValueExpression("rowKeyConverter");
      if (null != ve) {
        converter = (Converter) ve.getValue(getFacesContext().getELContext());
      }
    }
    return converter;
  }

  /**
   * @param rowKeyConverter the rowKeyConverter to set
   */
  public void setRowKeyConverter(Converter rowKeyConverter) {
    _rowKeyConverter = rowKeyConverter;
  }

  /*
   * (non-Javadoc)
   *
   * @see org.ajax4jsf.ajax.repeat.AjaxDataEncoder#getAjaxKeys()
   */
  @SuppressWarnings("unchecked")
  public Set<Object> getAjaxKeys() {
    Set<Object> keys = null;
    if (this._ajaxKeys != null) {
      keys = (this._ajaxKeys);
    } else {
      ValueExpression vb = getValueExpression("ajaxKeys");
      if (vb != null) {
        keys = (Set<Object>) (vb.getValue(getFacesContext().getELContext()));
      } else if (null != _ajaxRowKey) {
        // If none of above exist , use row with submitted AjaxComponent
        keys = new HashSet<Object>(1);
        keys.add(_ajaxRowKey);
      }
    }
    return keys;
  }
 
  public Set<Object> getAllAjaxKeys() {
    Set<Object> ajaxKeys = getAjaxKeys();
   
    Set<Object> allAjaxKeys = null;
    if (ajaxKeys != null) {
      allAjaxKeys = new HashSet<Object>();
      allAjaxKeys.addAll(ajaxKeys);
    }
   
    if (_ajaxRequestKeys != null) {
      if (allAjaxKeys == null) {
        allAjaxKeys = new HashSet<Object>();
      }
     
      allAjaxKeys.addAll(_ajaxRequestKeys);
    }

    return allAjaxKeys;
  }

  /*
   * (non-Javadoc)
   *
   * @see org.ajax4jsf.ajax.repeat.AjaxDataEncoder#setAjaxKeys(java.util.Set)
   */
  public void setAjaxKeys(Set<Object> ajaxKeys) {
    this._ajaxKeys = ajaxKeys;
  }

  /*
   * (non-Javadoc)
   *
   * @see org.ajax4jsf.framework.ajax.AjaxChildrenEncoder#encodeAjaxChild(javax.faces.context.FacesContext,
   *      java.lang.String, java.util.Set, java.util.Set)
   */
  public void encodeAjaxChild(FacesContext context, String path,
      final Set<String> ids, final Set<String> renderedAreas) throws IOException {

    Renderer renderer = getRenderer(context);
    if (null != renderer && renderer instanceof AjaxChildrenRenderer) {
      // If renderer support partial encoding - call them.
      if(_log.isDebugEnabled()){
        _log.debug("Component "+getClientId(context)+" has delegated Encode children components by AjaxChildrenRenderer for path "+path);
      }
      AjaxChildrenRenderer childrenRenderer = (AjaxChildrenRenderer) renderer;
      childrenRenderer.encodeAjaxChildren(context, this, path, ids,
          renderedAreas);
    } else {
      if(_log.isDebugEnabled()){
        _log.debug("Component "+getClientId(context)+"  do Encode children components  for path "+path);
      }
      // Use simple ajax children encoding for iterate other keys.
      final AjaxChildrenRenderer childrenRenderer = getChildrenRenderer();
      final String childrenPath = path + getId()
          + NamingContainer.SEPARATOR_CHAR;
      ComponentVisitor ajaxVisitor = new ComponentVisitor() {

        public void processComponent(FacesContext context,
            UIComponent c, Object argument) throws IOException {
          childrenRenderer.encodeAjaxComponent(context, c,
              childrenPath, ids, renderedAreas);
        }

      };
      Set<Object> ajaxKeys = getAllAjaxKeys();
      if (null != ajaxKeys) {
        if(_log.isDebugEnabled()){
          _log.debug("Component "+getClientId(context)+"  Encode children components for a keys "+ajaxKeys);
        }
        captureOrigValue();
        Object savedKey = getRowKey();
        setRowKey(context, null);
        Iterator<UIComponent> fixedChildren = fixedChildren();
        while (fixedChildren.hasNext()) {
          UIComponent component = fixedChildren.next();
          ajaxVisitor.processComponent(context, component, null);
        }
        for (Iterator<Object> iter = ajaxKeys.iterator(); iter.hasNext();) {
          Object key = iter.next();
          ajaxVisitor.process(context, key, null);
        }
        setRowKey(context,savedKey);
        restoreOrigValue(context);
      } else {
        if(_log.isDebugEnabled()){
          _log.debug("Component "+getClientId(context)+" children components  for all rows");
        }
        iterate(context, ajaxVisitor, null);
      }
    }
  }

  /**
   * Instance of default renderer in ajax responses.
   */
  private static final AjaxChildrenRenderer _childrenRenderer = new AjaxChildrenRenderer() {

    protected Class<? extends UIComponent> getComponentClass() {
      return UIDataAdaptor.class;
    }

  };

  /**
   * getter for simple {@link AjaxChildrenRenderer} instance in case of ajax
   * responses. If default renderer not support search of children for encode
   * in ajax response, component will use this instance by default.
   *
   * @return
   */
  protected AjaxChildrenRenderer getChildrenRenderer() {
    return _childrenRenderer;
  }

  /**
   * @return Set of values for clientId's of this component, for wich was
   *         invoked "encode" methods.
   */
  protected Set<String> getEncodedIds() {
    if (_encoded == null) {
      _encoded = new HashSet<String>();
    }

    return _encoded;
  }

  /**
   * Setup EL variable for different iteration. Value of row data and
   * component state will be put into request scope attributes with names
   * given by "var" and "varState" bean properties.
   *
   * Changed: does not check for row availability now
   *
   * @param faces
   *            current faces context
   * @param localModel
   * @param rowSelected
   */
  protected void setupVariable(FacesContext faces, DataModel localModel,
      boolean rowSelected) {
    Map<String, Object> attrs = faces.getExternalContext().getRequestMap();
    if (rowSelected/*&& isRowAvailable()*/) {
      // Current row data.
      setupVariable(getVar(), attrs, localModel.getRowData());
      // Component state variable.
      setupVariable(getStateVar(), attrs, getComponentState());
      // Row key Data variable.
      setupVariable(getRowKeyVar(), attrs, getRowKey());

    } else {
      removeVariable(getVar(), attrs);
      removeVariable(getStateVar(), attrs);
      removeVariable(getRowKeyVar(), attrs);
    }
  }

  /**
   * @param var
   * @param attrs
   * @param rowData
   */
  private void setupVariable(String var, Map<String, Object> attrs, Object rowData) {
    if (var != null) {
      attrs.put(var, rowData);
    }
  }

  /**
   * @param var
   * @param attrs
   * @param rowData
   */
  private void removeVariable(String var, Map<String, Object> attrs) {
    if (var != null) {
      attrs.remove(var);
    }
  }

  /**
   * Reset data model. this method must be called twice per request - before
   * decode phase and before component encoding.
   */
  protected void resetDataModel() {
    this._currentModel = null;
    _modelsMap.clear();
  }

  /**
   * Set data model. Model value will be stored in Map with key as current
   * clientId for this component, to keep models between phases for same
   * iteration in case if this component child for other UIData
   *
   * @param model
   */
  protected void setExtendedDataModel(ExtendedDataModel model) {
    this._currentModel = model;
    this._modelsMap.put(getBaseClientId(getFacesContext()), model);
  }

  /**
   * Get current data model, or create it by {@link #createDataModel()}
   * method. For different iterations in ancestor UIData ( if present ) will
   * be returned different models.
   *
   * @return current data model.
   */
  protected ExtendedDataModel getExtendedDataModel() {
    if (this._currentModel == null) {
      String baseClientId = getBaseClientId(getFacesContext());
      ExtendedDataModel model;
      model = (ExtendedDataModel) this._modelsMap.get(baseClientId);
      if (null == model) {
        model = createDataModel();
        this._modelsMap.put(baseClientId, model);
      }
      this._currentModel = model;
    }
    return this._currentModel;
  }

  /**
   * Hook mathod for create data model in concrete implementations.
   *
   * @return
   */
  protected abstract ExtendedDataModel createDataModel();

  /**
   * Set current state ( at most cases, visual representation ) of this
   * component. Same as for DataModel, component will keep states for
   * different iterations.
   *
   * @param state
   */
  public void setComponentState(DataComponentState state) {
    this._currentState = state;
    this._statesMap.put(getBaseClientId(getFacesContext()),
        this._currentState);
  }

  /**
   * @return current state of this component.
   */
  public DataComponentState getComponentState() {
    DataComponentState state = null;
    if (this._currentState == null) {
      // Check for binding state to user bean.
      ValueExpression valueBinding = getValueExpression(UIDataAdaptor.COMPONENT_STATE_ATTRIBUTE);
      FacesContext facesContext = getFacesContext();
      ELContext elContext = facesContext.getELContext();
      if (null != valueBinding) {
        state = (DataComponentState) valueBinding
            .getValue(elContext);
        if (null == state) {
          // Create default state
          state = createComponentState();
          if (!valueBinding.isReadOnly(elContext)) {
            // Store created state in user bean.
            valueBinding.setValue(elContext, state);
          }
        }
      } else {
        // Check for stored state in map for parent iterations
        String baseClientId = getBaseClientId(facesContext);
        state = (DataComponentState) this._statesMap.get(baseClientId);
        if (null == state) {
          // Create default component state
          state = createComponentState();
          this._statesMap.put(baseClientId, state);
        }
        this._currentState = state;
      }
    } else {
      state = this._currentState;
    }
    return state;
  }

  /**
   * Hook method for create default state in concrete implementations.
   *
   * @return
   */
  protected abstract DataComponentState createComponentState();

  private String _clientId = null;

  public String getClientId(FacesContext faces) {
    if (null == _clientId) {
      StringBuilder id = new StringBuilder(getBaseClientId(faces));
      Object rowKey = getRowKey();
      if (rowKey != null) {
        // Use converter to get String representation ot the row key.
        Converter rowKeyConverter = getRowKeyConverter();
        if(null == rowKeyConverter){
          // Create default converter for a row key.
          rowKeyConverter = faces.getApplication().createConverter(rowKey.getClass());
          // Store converter for a invokeOnComponents call.
          if(null != rowKeyConverter){
            setRowKeyConverter(rowKeyConverter);
          }
        }
        String rowKeyString;
        if (null !=rowKeyConverter) {
          // Temporary set clientId, to avoid infinite calls from converter.
          _clientId = id.toString();
          rowKeyString = rowKeyConverter.getAsString(faces, this, rowKey);
        } else {
          rowKeyString = rowKey.toString();
        }
        id.append(NamingContainer.SEPARATOR_CHAR).append(
            rowKeyString);
      }
      Renderer renderer;
      if (null != (renderer = getRenderer(faces))) {
        _clientId = renderer.convertClientId(faces, id.toString());
      } else {
        _clientId = id.toString();
      }

    }
    return _clientId;
  }

  private String _baseClientId = null;

  /**
   * Get base clietntId of this component ( withowt iteration part )
   *
   * @param faces
   * @return
   */
  public String getBaseClientId(FacesContext faces) {
    // Return any previously cached client identifier
    if (_baseClientId == null) {

      // Search for an ancestor that is a naming container
      UIComponent ancestorContainer = this;
      StringBuilder parentIds = new StringBuilder();
      while (null != (ancestorContainer = ancestorContainer.getParent())) {
        if (ancestorContainer instanceof NamingContainer) {
              String containerClientId = ancestorContainer.getContainerClientId(faces);
              // skip case when clientId of ancestor container is null
              if(containerClientId != null) {
                  parentIds.append(containerClientId).append(NamingContainer.SEPARATOR_CHAR);
              }
          break;
        }
      }
      String id = getId();
      if (null != id) {
        _baseClientId = parentIds.append(id).toString();
      } else {
        id = faces.getViewRoot().createUniqueId();
        super.setId(id);
        _baseClientId = parentIds.append(
            getId()).toString();
      }
    }
    return (_baseClientId);

  }

  /*
   * (non-Javadoc)
   *
   * @see javax.faces.component.UIComponentBase#setId(java.lang.String)
   */
  public void setId(String id) {
    // If component created by restoring tree or JSP, initial Id is null.
    boolean haveId = null != super.getId();
    String baseClientId;
//    baseClientId = haveId ? getBaseClientId(getFacesContext())
//        : null;
    super.setId(id);
    _baseClientId = null;
    _clientId = null;
    if (haveId) {
      // parent UIData ( if present ) will be set same Id at iteration
      // -
      // we use it for
      // switch to different model and state.
      // Step one - save old values.
//      this._statesMap.put(baseClientId, this._currentState);
//      this._modelsMap.put(baseClientId, this._currentModel);
//      this._ajaxRowKeysMap.put(baseClientId, this._ajaxRowKey);
      // Step two - restore values for a new clientId.
      baseClientId = getBaseClientId(getFacesContext());
      this._currentState = (DataComponentState) this._statesMap
          .get(baseClientId);
      this._currentModel = (ExtendedDataModel) this._modelsMap
          .get(baseClientId);
      if (null != this._currentModel) {
        this._rowKey = this._currentModel.getRowKey();
        // restoreChildState();
      }
      // Restore value for row with submitted AjaxComponent.
      this._ajaxRowKey = _ajaxRowKeysMap.get(baseClientId);
    }
  }

  private Object origValue;

  /**
   * Save current state of data variable.
   */
  public void captureOrigValue() {
    captureOrigValue(FacesContext.getCurrentInstance());
  }

  /**
   * Save current state of data variable.
   *
   * @param faces
   *            current faces context
   */
  public void captureOrigValue(FacesContext faces) {
    String var = getVar();
    if (var != null) {
      Map<String, Object> attrs = faces.getExternalContext().getRequestMap();
      this.origValue = attrs.get(var);
    }
  }

  /**
   * Restore value of data variable after processing phase.
   */
  public void restoreOrigValue() {
    restoreOrigValue(FacesContext.getCurrentInstance());
  }

  /**
   * Restore value of data variable after processing phase.
   *
   * @param faces
   *            current faces context
   */
  public void restoreOrigValue(FacesContext faces) {
    String var = getVar();
    if (var != null) {
      Map<String, Object> attrs = faces.getExternalContext().getRequestMap();
      if (this.origValue != null) {
        attrs.put(var, this.origValue);
      } else {
        attrs.remove(var);
      }
    }
  }

  /**
   * Saved values of {@link EditableValueHolder} fields per iterations.
   */
  private Map<String, Map<String, SavedState>> childState;

  /**
   * @param faces
   * @return Saved values of {@link EditableValueHolder} fields per
   *         iterations.
   */
  protected Map<String, SavedState> getChildState(FacesContext faces) {
    if (this.childState == null) {
      this.childState = new HashMap<String, Map<String,SavedState>>();
    }
    String baseClientId = getBaseClientId(faces);
    Map<String, SavedState> currentChildState = childState.get(baseClientId);
    if (null == currentChildState) {
      currentChildState = new HashMap<String, SavedState>();
      childState.put(baseClientId, currentChildState);
    }
    return currentChildState;
  }

  /**
   * Save values of {@link EditableValueHolder} fields before change current
   * row.
   *
   * @param faces
   */
  protected void saveChildState(FacesContext faces) {

    Iterator<UIComponent> itr = dataChildren();
    Map<String, SavedState> childState = this.getChildState(faces);
    while (itr.hasNext()) {
      this.saveChildState(faces, (UIComponent) itr.next(), childState);
    }
  }

  /**
   * Recursive method for Iterate on children for save
   * {@link EditableValueHolder} fields states.
   *
   * @param faces
   * @param c
   * @param childState
   */
  private void saveChildState(FacesContext faces, UIComponent c,
      Map<String, SavedState> childState) {

    if (!c.isTransient() && (c instanceof EditableValueHolder||c instanceof IterationStateHolder)) {
      String clientId = c.getClientId(faces);
      SavedState ss = childState.get(clientId);
      if (ss == null) {
        ss = new SavedState();
        childState.put(clientId, ss);
      }
      if (c instanceof EditableValueHolder) {
        ss.populate((EditableValueHolder) c);
      }
      if(c instanceof IterationStateHolder){
        ss.populate((IterationStateHolder) c);
      }
    }
    // continue hack
    Iterator<UIComponent> itr = c.getChildren().iterator();
    while (itr.hasNext()) {
      saveChildState(faces, (UIComponent) itr.next(), childState);
    }
      itr = c.getFacets().values().iterator();
      while (itr.hasNext()) {
        saveChildState(faces, (UIComponent) itr.next(), childState);
      }
  }

  /**
   * Restore values of {@link EditableValueHolder} fields after change current
   * row.
   *
   * @param faces
   */
  protected void restoreChildState(FacesContext faces) {

    Iterator<UIComponent> itr = dataChildren();
    Map<String, SavedState> childState = this.getChildState(faces);
    while (itr.hasNext()) {
      this.restoreChildState(faces, (UIComponent) itr.next(), childState);
    }
  }

  /**
   * Recursive part of
   * {@link #restoreChildState(FacesContext, UIComponent, Map)}
   *
   * @param faces
   * @param c
   * @param childState
   *
   */
  private void restoreChildState(FacesContext faces, UIComponent c,
      Map<String, SavedState> childState) {
    // reset id
    String id = c.getId();
    c.setId(id);

    // hack
    if (c instanceof EditableValueHolder || c instanceof IterationStateHolder) {
      String clientId = c.getClientId(faces);
      SavedState ss = childState.get(clientId);
      if (ss == null) {
        ss=NullState;
      }
      if (c instanceof EditableValueHolder) {
        EditableValueHolder evh = (EditableValueHolder) c;
        ss.apply(evh);
      }
      if (c instanceof IterationStateHolder) {
        IterationStateHolder ish = (IterationStateHolder) c;
        ss.apply(ish);
      }
    }

    // continue hack
    Iterator<UIComponent> itr = c.getChildren().iterator();
    while (itr.hasNext()) {
      restoreChildState(faces, (UIComponent) itr.next(), childState);
    }
    itr = c.getFacets().values().iterator();
      while (itr.hasNext()) {
        restoreChildState(faces, (UIComponent) itr.next(), childState);
      }
  }

  /**
   * Check for validation errors on children components. If true, saved values
   * must be keep on render phase
   *
   * @param context
   * @return
   */
  protected boolean keepSaved(FacesContext context) {
    // For an any validation errors, children components state should be preserved
        FacesMessage.Severity sev = context.getMaximumSeverity();
    return (sev != null && (FacesMessage.SEVERITY_ERROR.compareTo(sev) >= 0));


// Iterator<String> clientIds = this.getChildState(context).keySet().iterator();
//    while (clientIds.hasNext()) {
//      String clientId = clientIds.next();
//      Iterator<FacesMessage> messages = context.getMessages(clientId);
//      while (messages.hasNext()) {
//        FacesMessage message = messages.next();
//        if (message.getSeverity()
//            .compareTo(FacesMessage.SEVERITY_ERROR) >= 0) {
//          return (true);
//        }
//      }
//    }
//    return false;
  }

  /**
   * Perform iteration on all children components and all data rows with given
   * visitor.
   *
   * @param faces
   * @param visitor
   */
  protected void iterate(FacesContext faces, ComponentVisitor visitor,
      Object argument) {

    // stop if not rendered
    if (!this.isRendered()) {
      return;
    }
    // reset rowIndex
    this.captureOrigValue(faces);
    this.setRowKey(faces, null);
    try {
      Iterator<UIComponent> fixedChildren = fixedChildren();
      while (fixedChildren.hasNext()) {
        UIComponent component = fixedChildren.next();
        visitor.processComponent(faces, component, argument);
      }

      walk(faces, visitor, argument);
    } catch (Exception e) {
      throw new FacesException(e);
    } finally {
      this.setRowKey(faces, null);
      this.restoreOrigValue(faces);
    }
  }
 
  /**
   * Extracts segment of component client identifier containing row key
   *
   * @param context current faces context
   * @param tailId substring of component client identifier with base client identifier removed
   * @return segment containing row key or <code>null</code>
   */
  protected String extractKeySegment(FacesContext context, String tailId) {
    int indexOfSecondColon = tailId.indexOf(
        NamingContainer.SEPARATOR_CHAR);
   
    return (indexOfSecondColon > 0 ? tailId.substring(0, indexOfSecondColon) : null);
  }
 
  /**
   * Returns iterator of components to search through
   * in {@link #invokeOnComponent(FacesContext, String, ContextCallback)}.
   *
   * @return
   */
  protected Iterator<UIComponent> invocableChildren() {
    return getFacetsAndChildren();
  }
 
  @Override
  public boolean invokeOnComponent(FacesContext context, String clientId,
      ContextCallback callback) throws FacesException {
    if( null == context || null == clientId || null == callback){
      throw new NullPointerException();
    }
    boolean found = false;
      Object oldRowKey = getRowKey();
    String baseClientId = getBaseClientId(context);
        if (clientId.equals(baseClientId)) {
          // This is call for a same data component.
      try {
        if (null != oldRowKey) {
          captureOrigValue(context);
          setRowKey(context,null);
        }
        callback.invokeContextCallback(context, this);
        found = true;
      } catch (Exception e) {
        throw new FacesException(e);
      } finally {
        if (null != oldRowKey) {
          try {
            setRowKey(context,oldRowKey);
            restoreOrigValue(context);
          } catch (Exception e) {
            context.getExternalContext().log(e.getMessage(), e);
          }
        }       
      }
        } else {
      String baseId = baseClientId+NamingContainer.SEPARATOR_CHAR;
      if (clientId.startsWith(baseId)) {
        Object newRowKey = null;
        // Call for a child component - try to detect row key
        String rowKeyString = extractKeySegment(context,
            clientId.substring(baseId.length()));
        if (rowKeyString != null) {
          Converter keyConverter = getRowKeyConverter();
          if (null != keyConverter) {
            try {
              newRowKey = keyConverter.getAsObject(context, this,
                  rowKeyString);
            } catch (ConverterException e) {
              // TODO: log error
            }
          }
        }
        if( null != oldRowKey || null != newRowKey){
          captureOrigValue(context);
          setRowKey(newRowKey);
        }
              Iterator<UIComponent> itr = invocableChildren();             
              while (itr.hasNext() && !found) {
                  found = itr.next().invokeOnComponent(context, clientId,
                          callback);
              }
        if( null != oldRowKey || null != newRowKey){
          setRowKey(oldRowKey);
          restoreOrigValue(context);
        }
      }
    }
        //
    return found;
  }

  /**
   * Walk ( visit ) this component on all data-avare children for each row.
   *
   * @param faces
   * @param visitor
   * @throws IOException
   */
  public void walk(FacesContext faces, DataVisitor visitor, Object argument)
      throws IOException {
    getExtendedDataModel().walk(faces, visitor,
        getComponentState().getRange(), argument);
  }

  protected void processDecodes(FacesContext faces, Object argument) {
    if (!this.isRendered())
      return;
    this.iterate(faces, decodeVisitor, argument);
    this.decode(faces);
  }

  /*
   * (non-Javadoc)
   *
   * @see javax.faces.component.UIData#processDecodes(javax.faces.context.FacesContext)
   */
  public void processDecodes(FacesContext faces) {
    processDecodes(faces, null);
  }

  /**
   * Reset per-request fields in component.
   *
   * @param faces
   *
   */
  protected void resetComponent(FacesContext faces) {
    // resetDataModel();
    if (null != this.childState) {
      childState.remove(getBaseClientId(faces));
    }
    this._encoded = null;
  }

  protected void processUpdates(FacesContext faces, Object argument) {
    if (!this.isRendered())
      return;
    this.iterate(faces, updateVisitor, argument);
    ExtendedDataModel dataModel = getExtendedDataModel();
    // If no validation errors, update values for serializable model,
    // restored from view.
    if (dataModel instanceof SerializableDataModel && (!keepSaved(faces))) {
      SerializableDataModel serializableModel = (SerializableDataModel) dataModel;
      serializableModel.update();
    }

  }

  public void processUpdates(FacesContext faces) {
    processUpdates(faces, null);
    // resetComponent(faces);
  }

  protected void processValidators(FacesContext faces, Object argument) {
    if (!this.isRendered())
      return;
    this.iterate(faces, validateVisitor, argument);
  }

  public void processValidators(FacesContext faces) {
    processValidators(faces, null);
  }

  public void encodeBegin(FacesContext context) throws IOException {
    // Mark component as used, if parent UIData change own range states not
    // accessed at
    // encode phase must be unsaved.
    getEncodedIds().add(getBaseClientId(context));
    // getComponentState().setUsed(true);
    super.encodeBegin(context);
  }

  /**
   * This method must create iterator for all non-data avare children of this
   * component ( header/footer facets for components and columns in dataTable,
   * facets for tree etc.
   *
   * @return iterator for all components not sensitive for row data.
   */
  protected abstract Iterator<UIComponent> fixedChildren();

  /**
   * This method must create iterator for all children components, processed
   * "per row" It can be children of UIColumn in dataTable, nodes in tree
   *
   * @return iterator for all components processed per row.
   */
  protected abstract Iterator<UIComponent> dataChildren();

  private final static SavedState NullState = new SavedState();

  // from RI
  /**
   * This class keep values of {@link EditableValueHolder} row-sensitive
   * fields.
   *
   * @author shura
   *
   */
  private final static class SavedState implements Serializable {

    private Object submittedValue;
   
    private Object iterationState;

    private static final long serialVersionUID = 2920252657338389849L;

    Object getSubmittedValue() {
      return (this.submittedValue);
    }

    void setSubmittedValue(Object submittedValue) {
      this.submittedValue = submittedValue;
    }

    private boolean valid = true;

    boolean isValid() {
      return (this.valid);
    }

    void setValid(boolean valid) {
      this.valid = valid;
    }

    private Object value;

    Object getValue() {
      return (this.value);
    }

    public void setValue(Object value) {
      this.value = value;
    }

    private boolean localValueSet;

    boolean isLocalValueSet() {
      return (this.localValueSet);
    }

    public void setLocalValueSet(boolean localValueSet) {
      this.localValueSet = localValueSet;
    }

    public Object getIterationState() {
      return iterationState;
    }

    public void setIterationState(Object iterationState) {
      this.iterationState = iterationState;
    }

    public String toString() {
      return ("submittedValue: " + submittedValue + " value: " + value
          + " localValueSet: " + localValueSet);
    }

    public void populate(EditableValueHolder evh) {
      this.value = evh.getLocalValue();
      this.valid = evh.isValid();
      this.submittedValue = evh.getSubmittedValue();
      this.localValueSet = evh.isLocalValueSet();
    }

   
    public void populate(IterationStateHolder ish) {
      this.iterationState = ish.getIterationState();
    }
   
    public void apply(EditableValueHolder evh) {
      evh.setValue(this.value);
      evh.setValid(this.valid);
      evh.setSubmittedValue(this.submittedValue);
      evh.setLocalValueSet(this.localValueSet);
    }
   
    public void apply(IterationStateHolder ish) {
      ish.setIterationState(this.iterationState);
    }

  }

  protected void addAjaxKeyEvent(FacesEvent event) {
    Object eventRowKey = getRowKey();
    if (null != eventRowKey) {
      this._ajaxRowKey = eventRowKey;
      this._ajaxRowKeysMap.put(getBaseClientId(getFacesContext()),
      eventRowKey);
    }
  }
 
  /*
   * (non-Javadoc)
   *
   * @see javax.faces.component.UIData#queueEvent(javax.faces.event.FacesEvent)
   */
  public void queueEvent(FacesEvent event) {
    if (event.getComponent() != this) {
      // For Ajax events, keep row value.
      if (event.getPhaseId() == PhaseId.RENDER_RESPONSE) {
        addAjaxKeyEvent(event);
      }
      event = new IndexedEvent(this, event, getRowKey());
    }
    // Send event directly to parent, to avoid wrapping in superclass.
    UIComponent parent = getParent();
    if (parent == null) {
      throw new IllegalStateException(
          "No parent component for queue event");
    } else {
      parent.queueEvent(event);
    }
  }

  public void broadcast(FacesEvent event) throws AbortProcessingException {

    if (!(event instanceof IndexedEvent)) {
      if (!broadcastLocal(event)) {
        super.broadcast(event);
      }
      return;
    }

    // Set up the correct context and fire our wrapped event
    IndexedEvent revent = (IndexedEvent) event;
    Object oldRowKey = getRowKey();
    FacesContext faces = FacesContext.getCurrentInstance();
    captureOrigValue(faces);
    Object eventRowKey = revent.getKey();
    setRowKey(faces, eventRowKey);
    FacesEvent rowEvent = revent.getTarget();
    rowEvent.getComponent().broadcast(rowEvent);
    setRowKey(faces, oldRowKey);
    restoreOrigValue(faces);
    // }
    return;
  }

  /**
   * Process events targetted for concrete implementation. Hook method called
   * from {@link #broadcast(FacesEvent)}
   *
   * @param event -
   *            processed event.
   * @return true if event processed, false if component must continue
   *         processing.
   */
  protected boolean broadcastLocal(FacesEvent event) {
    return false;
  }

  /**
   * Wrapper for event from child component, with value of current row key.
   *
   * @author shura
   *
   */
  protected static final class IndexedEvent extends FacesEvent {

    private static final long serialVersionUID = -8318895390232552385L;

    private final FacesEvent target;

    private final Object key;

    public IndexedEvent(UIDataAdaptor owner, FacesEvent target, Object key) {
      super(owner);
      this.target = target;
      this.key = key;
    }

    public PhaseId getPhaseId() {
      return (this.target.getPhaseId());
    }

    public void setPhaseId(PhaseId phaseId) {
      this.target.setPhaseId(phaseId);
    }

    public boolean isAppropriateListener(FacesListener listener) {
      return this.target.isAppropriateListener(listener);
    }

    public void processListener(FacesListener listener) {
      UIDataAdaptor owner = (UIDataAdaptor) this.getComponent();
      Object prevIndex = owner._rowKey;
      try {
        owner.setRowKey(this.key);
        this.target.processListener(listener);
      } finally {
        owner.setRowKey(prevIndex);
      }
    }

    public Object getKey() {
      return key;
    }

    public FacesEvent getTarget() {
      return target;
    }

  }

  /**
   * "memento" pattern class for state of component.
   *
   * @author shura
   *
   */
  private static class DataState implements Serializable {

    /**
     *
     */
    private static final long serialVersionUID = 17070532L;

    private Object superState;

    private Map<String, PerIdState> componentStates = new HashMap<String, PerIdState>();

    private Set<Object> ajaxKeys;

    public String rowKeyVar;

    public String stateVar;

    private Map<String, Map<String, SavedState>> childStates;

    public Object rowKeyConverter;

  }

  /**
   * Serialisable model and component state per iteration of parent UIData.
   *
   * @author shura
   *
   */
  private static class PerIdState implements Serializable {
    /**
     *
     */
    private static final long serialVersionUID = 9037454770537726418L;

    /**
     * Flag setted to true if componentState implements StateHolder
     */
    private boolean stateInHolder = false;

    /**
     * Serializable componentState or
     */
    private Object componentState;

    private SerializableDataModel model;
  }

  public void restoreState(FacesContext faces, Object object) {
    DataState state = (DataState) object;
    super.restoreState(faces, state.superState);
    this._ajaxKeys = state.ajaxKeys;
    this._statesMap = new HashMap<String, DataComponentState>();
    this._rowKeyVar = state.rowKeyVar;
    this._stateVar = state.stateVar;
    this.childState = state.childStates;
    if (null != state.rowKeyConverter) {
      this._rowKeyConverter = (Converter) restoreAttachedState(faces,
          state.rowKeyConverter);
    }
    // Restore serializable models and component states for all rows of
    // parent UIData ( single if this
    // component not child of iterable )
    for (Iterator<Entry<String, PerIdState>> iter = state.componentStates.entrySet().iterator(); iter
        .hasNext();) {
      Entry<String, PerIdState> stateEntry = iter.next();
      PerIdState idState = stateEntry.getValue();
      DataComponentState compState;
      if (idState.stateInHolder) {
        // TODO - change RichFaces Tree component, for remove reference
        // to component from state.
        compState = createComponentState();
        ((StateHolder) compState).restoreState(faces,
            idState.componentState);
      } else {
        compState = (DataComponentState) idState.componentState;
      }
      String key = stateEntry.getKey();
      this._statesMap.put(key, compState);
      this._modelsMap.put(key, idState.model);
    }
  }

  public Object saveState(FacesContext faces) {
    DataState state = new DataState();
    state.superState = super.saveState(faces);
    state.ajaxKeys = this._ajaxKeys;
    state.rowKeyVar = this._rowKeyVar;
    state.stateVar = this._stateVar;
    state.childStates = this.childState;
    if (null != this._rowKeyConverter) {
      state.rowKeyConverter = saveAttachedState(faces,this._rowKeyConverter);
    }
    Set<String> encodedIds = getEncodedIds();
    // Save all states of component and data model for all valies of
    // clientId, encoded in this request.
//    this._statesMap.put(getBaseClientId(faces), this._currentState);
//    this._modelsMap.put(getBaseClientId(faces), this._currentModel);
    for (Iterator<Entry<String, DataComponentState>> iter = this._statesMap.entrySet().iterator(); iter
        .hasNext();) {
      Entry<String, DataComponentState> stateEntry = iter.next();
      DataComponentState dataComponentState = stateEntry.getValue();
      String stateKey = stateEntry.getKey();
      if (encodedIds.isEmpty() || encodedIds.contains(stateKey)) {
        PerIdState idState = new PerIdState();
        // Save component state , depended if implemented interfaces.
        if (null == dataComponentState) {
          idState.componentState = null;
        } else {
          if (dataComponentState instanceof Serializable) {
            idState.componentState = dataComponentState;
          } else if (dataComponentState instanceof StateHolder) {
            idState.componentState = ((StateHolder) dataComponentState)
                .saveState(faces);
            idState.stateInHolder = true;
          }
          ExtendedDataModel extendedDataModel = (ExtendedDataModel) this._modelsMap
              .get(stateKey);
          if (null != extendedDataModel) {
            idState.model = extendedDataModel
                .getSerializableModel(dataComponentState
                    .getRange());

          }
        }
        if (null != idState.model || null != idState.componentState) {
          state.componentStates.put(stateKey, idState);
        }
      }
    }
    return state;
  }

  public void setParent(UIComponent parent) {
    super.setParent(parent);
    this._clientId = null;
    this._baseClientId = null;
  }

  /**
   * Adds argument key to AJAX internal request keys set
   * @param key key to add
   */
  public void addRequestKey(Object key) {
    if (_ajaxRequestKeys == null) {
      _ajaxRequestKeys = new HashSet<Object>();
    }

    _ajaxRequestKeys.add(key);
  }

  /**
   * Removes argument key from AJAX internal request keys set
   * @param key key to remove
   */
  public void removeRequestKey(Object key) {
    if (_ajaxRequestKeys != null && key != null) {
      _ajaxRequestKeys.remove(key);
    }
  }
 
  /**
   * Checks whether AJAX internal request keys set contains argument key
   * @param key key to check
   * @return <code>true</code> if set contains key, <code>false</code> - otherwise
   */
  public boolean containsRequestKey(Object key) {
    if (_ajaxRequestKeys != null && key != null) {
      return _ajaxRequestKeys.contains(key);
    }
   
    return false;
  }
 
  /**
   * Clears AJAX internal request keys set
   */
  public void clearRequestKeysSet() {
    _ajaxRequestKeys = null;
  }
 
  public Object getValue() {
    return super.getValue();
  }
 
  public void setValue(Object value) {
    setExtendedDataModel(null);
    super.setValue(value);
  }
 
  public void beforeRenderResponse(FacesContext context) {
    resetDataModel();
    this._encoded = null;
    if (null != childState && !keepSaved(context)) {
      childState.clear();
    }
  }
}
TOP

Related Classes of org.ajax4jsf.component.UIDataAdaptor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.