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 javax.faces.FacesException;
import javax.faces.application.FacesMessage;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.NamingContainer;
import javax.faces.component.StateHolder;
import javax.faces.component.UIComponent;
import javax.faces.component.UIData;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
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 childIterator = dataChildren();
        while (childIterator.hasNext()) {
          UIComponent component = (UIComponent) childIterator.next();
          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 _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 _modelsMap = new HashMap();

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

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

  /**
   * 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;

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

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

  private Object _ajaxRowKey = null;

  private Map _ajaxRowKeysMap = new HashMap();

  /**
   * 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(key == localModel.getRowIndex()){
    // return;
    // }
   
    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 roy 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);
    }
  }

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

    return allAjaxKeys;
  }

  /*
   * (non-Javadoc)
   *
   * @see org.ajax4jsf.ajax.repeat.AjaxDataEncoder#setAjaxKeys(java.util.Set)
   */
  public void setAjaxKeys(Set 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 ids, final Set renderedAreas) throws IOException {
    resetDataModel();

    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 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 fixedChildren = fixedChildren();
        while (fixedChildren.hasNext()) {
          UIComponent component = (UIComponent) fixedChildren.next();
          ajaxVisitor.processComponent(context, component, null);
        }
        for (Iterator iter = ajaxKeys.iterator(); iter.hasNext();) {
          Object key = (Object) 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 AjaxChildrenRenderer _childrenRenderer = null;

  /**
   * 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() {
    if (_childrenRenderer == null) {
      _childrenRenderer = new AjaxChildrenRenderer() {

        protected Class getComponentClass() {
          return UIDataAdaptor.class;
        }

      };

    }

    return _childrenRenderer;
  }

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

    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 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 attrs, Object rowData) {
    if (var != null) {
      attrs.put(var, rowData);
    }
  }

  /**
   * @param var
   * @param attrs
   * @param rowData
   */
  private void removeVariable(String var, Map 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.setExtendedDataModel(null);
  }

  /**
   * 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.
      ValueBinding valueBinding = getValueBinding(UIDataAdaptor.COMPONENT_STATE_ATTRIBUTE);
      FacesContext facesContext = getFacesContext();
      if (null != valueBinding) {
        state = (DataComponentState) valueBinding
            .getValue(facesContext);
        if (null == state) {
          // Create default state
          state = createComponentState();
          if (!valueBinding.isReadOnly(facesContext)) {
            // Store created state in user bean.
            valueBinding.setValue(facesContext, 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) {
      StringBuffer id = new StringBuffer(getBaseClientId(faces));
      Object rowKey = getRowKey();
      if (rowKey != null) {
        id.append(NamingContainer.SEPARATOR_CHAR).append(
            rowKey.toString());
      }
      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;
      StringBuffer parentIds = new StringBuffer();
      while (null != (ancestorContainer = ancestorContainer.getParent())) {
        if (ancestorContainer instanceof NamingContainer) {
          parentIds.append(ancestorContainer.getClientId(faces))
              .append(NamingContainer.SEPARATOR_CHAR);
          break;
        }
      }
      String id = getId();
      if (null != id) {
        _baseClientId = parentIds.append(id).toString();
      } else {
        _baseClientId = parentIds.append(
            faces.getViewRoot().createUniqueId()).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 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 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 childState;

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

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

    Iterator itr = dataChildren();
    while (itr.hasNext()) {
      Map childState = this.getChildState(faces);
      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 childState) {

    if (c instanceof EditableValueHolder && !c.isTransient()) {
      String clientId = c.getClientId(faces);
      SavedState ss = (SavedState) childState.get(clientId);
      if (ss == null) {
        ss = new SavedState();
        childState.put(clientId, ss);
      }
      ss.populate((EditableValueHolder) c);
    }

    // continue hack
    Iterator 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 itr = dataChildren();
    while (itr.hasNext()) {
      Map childState = this.getChildState(faces);
      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 childState) {
    // reset id
    String id = c.getId();
    c.setId(id);

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

    // continue hack
    Iterator 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
   */
  private boolean keepSaved(FacesContext context) {

    Iterator clientIds = this.getChildState(context).keySet().iterator();
    while (clientIds.hasNext()) {
      String clientId = (String) clientIds.next();
      Iterator messages = context.getMessages(clientId);
      while (messages.hasNext()) {
        FacesMessage message = (FacesMessage) 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 fixedChildren = fixedChildren();
      while (fixedChildren.hasNext()) {
        UIComponent component = (UIComponent) 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);
    }
  }

  /**
   * 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 {
    resetDataModel();
    if (!keepSaved(context)) {
      childState.remove(getBaseClientId(context));
    }
    // 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 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 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 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 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 apply(EditableValueHolder evh) {
      evh.setValue(this.value);
      evh.setValid(this.valid);
      evh.setSubmittedValue(this.submittedValue);
      evh.setLocalValueSet(this.localValueSet);
    }

  }

  /*
   * (non-Javadoc)
   *
   * @see javax.faces.component.UIData#queueEvent(javax.faces.event.FacesEvent)
   */
  public void queueEvent(FacesEvent event) {
    if (event.getComponent() != this) {
      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);
    // For Ajax events, keep row value.
    if (!(rowEvent.getPhaseId() == PhaseId.RENDER_RESPONSE)) {
      this._ajaxRowKey = eventRowKey;
      // this._ajaxRowKeysMap.put(getBaseClientId(faces), eventRowKey);
    }
    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 componentStates = new HashMap();

    private Set ajaxKeys;

    public String rowKeyVar;

    public String stateVar;

    private Map childStates;

  }

  /**
   * 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();
    this._rowKeyVar = state.rowKeyVar;
    this._stateVar = state.stateVar;
    this.childState = state.childStates;
    // Restore serializable models and component states for all rows of
    // parent UIData ( single if this
    // component not child of iterable )
    for (Iterator iter = state.componentStates.entrySet().iterator(); iter
        .hasNext();) {
      Map.Entry stateEntry = (Map.Entry) iter.next();
      PerIdState idState = (PerIdState) 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;
      }
      Object 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;
    Set 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 iter = this._statesMap.entrySet().iterator(); iter
        .hasNext();) {
      Map.Entry stateEntry = (Map.Entry) iter.next();
      DataComponentState dataComponentState = ((DataComponentState) stateEntry
          .getValue());
      Object 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();
    }

    _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;
  }

  /**
   * Getter for value
   */
  public Object getValue() {
    return super.getValue();
  }
 
  /**
   * Setter for value resetting local model
   */
  public void setValue(Object value) {
    setExtendedDataModel(null);
    super.setValue(value);
  }
}
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.