All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.myfaces.trinidad.component.UIXCollection Maven / Gradle / Ivy

There is a newer version: 2.2.1
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.myfaces.trinidad.component;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;

import java.util.AbstractMap;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.faces.FacesException;
import javax.faces.application.StateManager;
import javax.faces.component.ContextCallback;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIComponent;
import javax.faces.component.visit.VisitCallback;
import javax.faces.component.visit.VisitContext;
import javax.faces.component.visit.VisitContextWrapper;
import javax.faces.component.visit.VisitResult;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ComponentSystemEvent;
import javax.faces.event.FacesEvent;
import javax.faces.event.PhaseId;
import javax.faces.render.Renderer;

import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFComponent;
import org.apache.myfaces.trinidad.bean.FacesBean;
import org.apache.myfaces.trinidad.bean.PropertyKey;
import org.apache.myfaces.trinidad.context.ComponentContextChange;
import org.apache.myfaces.trinidad.context.ComponentContextManager;
import org.apache.myfaces.trinidad.context.RequestContext;
import org.apache.myfaces.trinidad.event.SelectionEvent;
import org.apache.myfaces.trinidad.logging.TrinidadLogger;
import org.apache.myfaces.trinidad.model.CollectionModel;
import org.apache.myfaces.trinidad.model.LocalRowKeyIndex;
import org.apache.myfaces.trinidad.model.RowKeyChangeEvent;
import org.apache.myfaces.trinidad.model.RowKeyChangeListener;
import org.apache.myfaces.trinidad.model.SortCriterion;
import org.apache.myfaces.trinidad.render.ClientRowKeyManager;
import org.apache.myfaces.trinidad.render.ClientRowKeyManagerFactory;
import org.apache.myfaces.trinidad.util.Args;
import org.apache.myfaces.trinidad.util.ComponentUtils;


/**
 * Base class for components that do stamping.
 * This class set the EL 'var' variable correctly when the rowData changes.
 * And it wraps events that are queued, so that the correct rowData can be
 * restored on this component when the event is broadcast.
 */
@JSFComponent
public abstract class UIXCollection extends UIXComponentBase
  implements NamingContainer
{
  static public final FacesBean.Type TYPE = new FacesBean.Type(
    UIXComponentBase.TYPE);
  static public final PropertyKey VAR_KEY =
    TYPE.registerKey("var", String.class, PropertyKey.CAP_NOT_BOUND);

  protected UIXCollection(String rendererType)
  {
    super(rendererType);
  }

  protected UIXCollection()
  {
    this(null);
  }

  /**
   * Gets the name of the EL variable used to reference each element of
   * this collection.  Once this component has completed rendering, this
   * variable is removed (or reverted back to its previous value).
   */
  final public String getVar()
  {
    return ComponentUtils.resolveString(getProperty(VAR_KEY));
  }

  /**
   * Sets the name of the EL variable used to reference each element of
   * this collection.  Once this component has completed rendering, this
   * variable is removed (or reverted back to its previous value).
   */
  final public void setVar(String var)
  {
    setProperty(VAR_KEY, (var));
    InternalState iState = _getInternalState(false);
    if (iState != null)
    {
      iState._var = var;
    }
  }

  /**
   * Queues an event. If there is a currency set on this table, then
   * the event will be wrapped so that when it is finally delivered, the correct
   * currency will be restored.
   * @param event a FacesEvent
   */
  @Override
  public void queueEvent(FacesEvent event)
  {
    if (event.getSource() == this)
    {
      // Remember non-SelectionEvents on ourselves.  This
      // is a hack to support validation in tableSelectXyz.
      if (!(event instanceof SelectionEvent))
      {
        InternalState iState = _getInternalState(true);
        iState._hasEvent = true;
      }
    }

    // we want to wrap up
    // the event so we can execute it in the correct context (with the correct
    // rowKey/rowData):
    Object currencyKey = getRowKey();
    event = new TableRowEvent(this, event, currencyKey);

    // Queue a CollectionContextEvent in order to allow this class to setup the component change
    // before sub-classes attempt to process the table row event instance.
    super.queueEvent(new CollectionContextEvent(this, event));
  }

  /**
   * Delivers a wrapped event to the appropriate component.
   * If the event is a special wrapped event, it is unwrapped.
   * @param event a FacesEvent
   * @throws javax.faces.event.AbortProcessingException
   */
  @Override
  public void broadcast(FacesEvent event)
    throws AbortProcessingException
  {
    FacesContext context = getFacesContext();

    // Unwrap CollectionContextEvent events so that the original event is broadcast
    // within a component change event context.
    if (event instanceof CollectionContextEvent)
    {
      boolean inContextAtMethodInvocation = _inContext;
      if (!inContextAtMethodInvocation)
      {
        _setupContextChange();
      }

      try
      {
        CollectionContextEvent wrapperEvent = (CollectionContextEvent) event;
        wrapperEvent.broadcastWrappedEvent(context);
      }
      finally
      {
        if (!inContextAtMethodInvocation)
        {
          _tearDownContextChange();
        }
      }
    }
    else
    {
      // For "TableRowEvents", set up the data before firing the
      // event to the actual component.
      if (event instanceof TableRowEvent)
      {
        TableRowEvent rowEvent = (TableRowEvent) event;
        Object old = getRowKey();
        setRowKey(rowEvent.getCurrencyKey());
        rowEvent.broadcastWrappedEvent(context);
        setRowKey(old);
      }
      else
      {
        super.broadcast(event);
      }
    }
  }

  /**
   * Decodes this component before decoding the facets.
   * Decodes the children as many times as they are stamped.
   * @param context the FacesContext
   */
  @Override
  public final void processDecodes(FacesContext context)
  {
    if (context == null)
      throw new NullPointerException();

    boolean inContextAtMethodInvocation = _inContext;
    if (!inContextAtMethodInvocation)
    {
      _setupContextChange();
    }

    try
    {
      _init();

      InternalState iState = _getInternalState(true);
      iState._isFirstRender = false;

      if (!isRendered())
        return;

      __flushCachedModel();

      // Make sure _hasEvent is false.
      iState._hasEvent = false;

      // =-=AEW Because I'm getting the state in decode(), I need
      // to do it before iterating over the children - otherwise,
      // they'll be working off the wrong startIndex.  When state
      // management is integrated, I can likely put this back in the
      // usual order

      // Process this component itself
      decode(context);

      // Process all facets and children of this component
      decodeChildren(context);
    }
    finally
    {
      if (!inContextAtMethodInvocation)
      {
        _tearDownContextChange();
      }
    }
  }

  @Override
  protected void decodeChildrenImpl(FacesContext context)
  {
    processFacetsAndChildren(context, PhaseId.APPLY_REQUEST_VALUES);
  }

  @Override
  protected void validateChildrenImpl(FacesContext context)
  {
    processFacetsAndChildren(context, PhaseId.PROCESS_VALIDATIONS);
  }

  @Override
  protected void updateChildrenImpl(FacesContext context)
  {
    processFacetsAndChildren(context, PhaseId.UPDATE_MODEL_VALUES);
  }

  /**
   * Resets this component's stamps to their
   * uninitialized state. This is useful when the user wants to
   * undo any edits made to an editable table.
   */
  public void resetStampState()
  {
    InternalState iState = _getInternalState(true);
    // TODO: this is over kill. for eg, It clears out any toggled showDetails.
    Object initKey = _getCurrencyKeyForInitialStampState();
    // do not clear the initial stamp state: a subtle bug could
    // result where the initial state of each component is gone, so we
    // fail to roll back to the initial default values
    if (iState._stampState != null)
      iState._stampState.clear(initKey);
  }

  @Override
  public Object processSaveState(FacesContext context)
  {
    boolean inContextAtMethodInvocation = _inContext;
    if (!inContextAtMethodInvocation)
    {
      _setupContextChange();
    }

    try
    {
      _stateSavingCurrencyKey = _resetCurrencyKeyForStateSaving(context);

      Object savedState = super.processSaveState(context);

      _restoreCurrencyKeyForStateSaving(_stateSavingCurrencyKey);
      _resetInternalState();

      return savedState;
    }
    finally
    {
      if (!inContextAtMethodInvocation)
      {
        _tearDownContextChange();
      }
    }
  }

  @Override
  public Object saveState(FacesContext context)
  {
    // _stampState is stored as an instance variable, so it isn't
    // automatically saved
    Object superState = super.saveState(context);
    final Object stampState, clientKeyMgr, idToIndexMap;

    // becareful not to create the internal state too early:
    // otherwise, the internal state will be shared between
    // nested table stamps:
    InternalState iState = _getInternalState(false);
    if (iState != null)
    {
      stampState = iState._stampState;
      clientKeyMgr = iState._clientKeyMgr;
      idToIndexMap = iState._idToIndexMap;
    }
    else
    {
      stampState = null;
      clientKeyMgr = null;
      idToIndexMap = null;
    }

    if ((superState != null) || (stampState != null) ||
        (clientKeyMgr != null) || (idToIndexMap != null))
      return new Object[]{superState, stampState, clientKeyMgr, idToIndexMap};
    return null;
  }


  @SuppressWarnings("unchecked")
  @Override
  public void restoreState(FacesContext context, Object state)
  {
    final Object superState, stampState, clientKeyMgr, idToIndexMap;
    Object[] array = (Object[]) state;
    if (array != null)
    {
      superState = array[0];
      stampState = array[1];
      clientKeyMgr = array[2];
      idToIndexMap = array[3];
    }
    else
    {
      superState = null;
      stampState = null;
      clientKeyMgr = null;
      idToIndexMap = null;
    }
    super.restoreState(context, superState);

    if ((stampState != null) || (clientKeyMgr != null) || (idToIndexMap != null))
    {
      InternalState iState = _getInternalState(true);
      iState._stampState = (StampState) stampState;
      iState._clientKeyMgr = (ClientRowKeyManager) clientKeyMgr;
      iState._idToIndexMap = (Map) idToIndexMap;
    }
    else
    {
      // becareful not to force creation of the internal state
      // too early:
      InternalState iState = _getInternalState(false);
      if (iState != null)
      {
        iState._stampState = null;
        iState._clientKeyMgr = null;
        iState._idToIndexMap = null;
      }
    }
  }

  /**
   * Checks to see if the current row is available. This is useful when the
   * total number of rows is not known.
   * @see CollectionModel#isRowAvailable()
   * @return true iff the current row is available.
   */
  public final boolean isRowAvailable()
  {
    return getCollectionModel().isRowAvailable();
  }

  /**
   * Check for an available row by row key.
   * @param rowKey the row key for the row to check.
   * @return true if a value exists; false otherwise.
   */
  public final boolean isRowAvailable(Object rowKey)
  {
    return getCollectionModel().isRowAvailable(rowKey);
  }

  /**
   * Get row data by row key.
   * @param rowKey the row key for the row to get data.
   * @return row data
   */
  public final Object getRowData(Object rowKey)
  {
    return getCollectionModel().getRowData(rowKey);
  }

  /**
   * Check if a range of rows is available starting from the current position
   * @param rowCount number of rows to check
   * @return true if all rows in range are available
   */
  public final boolean areRowsAvailable(int rowCount)
  {
    return getCollectionModel().areRowsAvailable(rowCount);
  }

  /**
   * Check if a range of rows is available from a starting index without
   * requiring the client to iterate over the rows
   * @param startIndex the starting index for the range
   * @param rowCount number of rows to check
   * @return true if all rows in range are available
   */
  public final boolean areRowsAvailable(int startIndex, int rowCount)
  {
    return getCollectionModel().areRowsAvailable(startIndex, rowCount);
  }

  /**
   * Check if a range of rows is available from a starting row key without
   * requiring the client to iterate over the rows
   * @param startRowKey the starting row key for the range
   * @param rowCount number of rows to check
   * @return true if all rows in range are available
   */
  public final boolean areRowsAvailable(Object startRowKey, int rowCount)
  {
    return getCollectionModel().areRowsAvailable(startRowKey, rowCount);
  }



  /**
   * Gets the total number of rows in this table.
   * @see CollectionModel#getRowCount
   * @return -1 if the total number is not known.
   */
  public final int getRowCount()
  {
    return getCollectionModel().getRowCount();
  }

  /**
   * Gets the index of the current row.
   * @see CollectionModel#getRowIndex
   * @return -1 if the current row is unavailable.
   */
  public final int getRowIndex()
  {
    return getCollectionModel().getRowIndex();
  }

  /**
   * Gets the rowKey of the current row.
   * @see CollectionModel#getRowKey
   * @return null if the current row is unavailable.
   */
  public final Object getRowKey()
  {
    InternalState iState = _getInternalState(true);
    if (iState._currentRowKey == _NULL)
    {
      // See bug 4534104.
      // Sometimes the rowKey for a particular row changes during update model
      // (this happens in ADFM if you edit the primary key of a row).
      // It is bad if the rowKey changes after _restoreStampState() and
      // before _saveStampState(). Therefore, we cache it:
      iState._currentRowKey = getCollectionModel().getRowKey();
      iState._model.addRowKeyChangeListener(iState);
    }

    return iState._currentRowKey;
  }

  /**
   * Gets the data for the current row.
   * @see CollectionModel#getRowData(int)
   * @return null if the current row is unavailable
   */
  public final Object getRowData()
  {
    CollectionModel model = getCollectionModel();
    // we need to call isRowAvailable() here because the 1.0 sun RI was
    // throwing exceptions when getRowData() was called with rowIndex=-1
    return model.isRowAvailable() ? model.getRowData() : null;
  }

  /**
   * Checks to see if the row at the given index is available.
   * @see CollectionModel#isRowAvailable(int)
   * @param rowIndex the index of the row to check.
   * @return true if data for the row exists.
   */
  public boolean isRowAvailable(int rowIndex)
  {
    return getCollectionModel().isRowAvailable(rowIndex);
  }

  /**
   * Gets the rowData at the given index.
   * @see CollectionModel#getRowData(int)
   * @param rowIndex the index of the row to get data from.
   * @return the data for the given row.
   */
  public Object getRowData(int rowIndex)
  {
    return getCollectionModel().getRowData(rowIndex);
  }

  /**
   * Gets the EL variable name to use to expose the varStatusMap.
   * @see #createVarStatusMap()
   */
  public abstract String getVarStatus();

  /**
   * Makes a row current.
   * This method calls {@link #preRowDataChange} and
   * {@link #postRowDataChange} as appropriate.
   * @see CollectionModel#setRowKey
   * @param rowKey The rowKey of the row that should be made current. Use null
   * to clear the current row.
   */
  public void setRowKey(Object rowKey)
  {
    _verifyComponentInContext();

    preRowDataChange();
    getCollectionModel().setRowKey(rowKey);
    postRowDataChange();
    if (_LOG.isFine() && (rowKey != null) && (!isRowAvailable()))
      _LOG.fine("no row available for rowKey:"+rowKey);
  }

  /**
   * Makes a row current.
   * This method calls {@link #preRowDataChange} and
   * {@link #postRowDataChange} as appropriate.
   * @see CollectionModel#setRowIndex
   * @param rowIndex The rowIndex of the row that should be made current. Use -1
   * to clear the current row.
   */
  public void setRowIndex(int rowIndex)
  {
    _verifyComponentInContext();

    preRowDataChange();
    getCollectionModel().setRowIndex(rowIndex);
    postRowDataChange();
    if (_LOG.isFine() && (rowIndex != -1) && (!isRowAvailable()))
      _LOG.fine("no row available for rowIndex:"+rowIndex);
  }

  /**
   * @param property a property name in the model
   * @return  true if the model is sortable by the given property.
   * @see CollectionModel#isSortable
   */
  public final boolean isSortable(String property)
  {
    return getCollectionModel().isSortable(property);
  }

  /**
   * Sorts this collection by the given criteria.
   * @param criteria Each element in this List must be of type SortCriterion.
   * @see org.apache.myfaces.trinidad.model.SortCriterion
   * @see CollectionModel#setSortCriteria
   */
  public void setSortCriteria(List criteria)
  {
    getCollectionModel().setSortCriteria(criteria);
  }

  /**
   * Gets the criteria that this collection is sorted by.
   * @return each element in this List is of type SortCriterion.
   * An empty list is returned if this collection is not sorted.
   * @see org.apache.myfaces.trinidad.model.SortCriterion
   * @see CollectionModel#getSortCriteria
   */
  public final List getSortCriteria()
  {
    return getCollectionModel().getSortCriteria();
  }

  /**
   * Clear the rowKey-to-currencyString cache.
   * The cache is not cleared immediately; instead it will be cleared
   * when {@link #encodeBegin(FacesContext)} is called.
   * @deprecated Have your Renderer implement {@link ClientRowKeyManagerFactory}
   * and create your own {@link ClientRowKeyManager} instances. Then you can
   * manage the lifecycle of each key inside your ClientRowKeyManager.
   */
  @Deprecated
  protected void clearCurrencyStringCache()
  {
    _getInternalState(true)._clearTokenCache = true;
  }

  /**
   * Clears all the currency strings.
   */
  @Override
  public final void encodeBegin(FacesContext context) throws IOException
  {
    _setupContextChange();
    boolean teardown = true;
    try
    {
      _init();

      InternalState istate = _getInternalState(true);
      // we must not clear the currency cache everytime. only clear
      // it in response to specific events: bug 4773659

      // TODO all this code should be removed and moved into the renderer:
      if (istate._clearTokenCache)
      {
        istate._clearTokenCache = false;
        ClientRowKeyManager keyMgr = getClientRowKeyManager();
        if (keyMgr instanceof DefaultClientKeyManager)
          ((DefaultClientKeyManager) keyMgr).clear();
      }
      __flushCachedModel();

      Object assertKey = null;
      assert ((assertKey = getRowKey()) != null) || true;
      __encodeBegin(context);
      // make sure that the rendering code preserves the currency:
      assert _assertKeyPreserved(assertKey) : "CurrencyKey not preserved";

      teardown = false;
    }
    finally
    {
      if (teardown)
      {
        // Tear down on errors & exceptions
        _tearDownContextChange();
      }
    }
  }

  @Override
  public void encodeEnd(FacesContext context) throws IOException
  {
    try
    {
      Object assertKey = null;
      assert ((assertKey = getRowKey()) != null) || true;
      super.encodeEnd(context);
      // make sure that the rendering code preserves the currency:
      assert _assertKeyPreserved(assertKey) : "CurrencyKey not preserved";
    }
    finally
    {
      _tearDownContextChange();
    }
  }

  @Override
  protected void setupVisitingContext(FacesContext context)
  {
    super.setupVisitingContext(context);
    _setupContextChange();

    if (Boolean.TRUE.equals(context.getAttributes().get(StateManager.IS_SAVING_STATE)))
    {
      _stateSavingCurrencyKey = _resetCurrencyKeyForStateSaving(context);
    }
  }

  @Override
  protected void tearDownVisitingContext(FacesContext context)
  {
    if (Boolean.TRUE.equals(context.getAttributes().get(StateManager.IS_SAVING_STATE)))
    {
      _restoreCurrencyKeyForStateSaving(_stateSavingCurrencyKey);
      _resetInternalState();
    }

    _tearDownContextChange();
    super.tearDownVisitingContext(context);
  }

  private boolean _assertKeyPreserved(Object oldKey)
  {
    Object newKey = getRowKey();
    return (oldKey != null) ? oldKey.equals(newKey) : (newKey == null);
  }

  void __encodeBegin(FacesContext context) throws IOException
  {
    super.encodeBegin(context);
  }

  /**
   * Checks to see if processDecodes was called. If this returns true
   * then this is the initial request, and processDecodes has not been called.
   */
  boolean __isFirstRender()
  {
    InternalState iState = _getInternalState(true);
    return iState._isFirstRender;
  }

  /**
   * @deprecated use getClientRowKey
   * @see #getClientRowKey
   */
  @Deprecated
  public String getCurrencyString()
  {
    return getClientRowKey();
  }

  /**
   * @deprecated use setClientRowKey
   * @see #setClientRowKey
   */
  @Deprecated
  public void setCurrencyString(String currency)
  {
    setClientRowKey(currency);
  }


  /**
   * Gets a String version of the current rowkey.
   * The contents of the String are controlled by the current
   * {@link ClientRowKeyManager}.
   * This String can be passed into the {@link #setClientRowKey} method
   * to restore the current rowData.
   * The lifetime of this String is short and it may not be valid during
   * future requests; however, it is guaranteed to be valid
   * for the next subsequent request.
   * @see UIXCollection#setClientRowKey(java.lang.String)
   * @see UIXCollection#getClientRowKeyManager()
   * @see ClientRowKeyManager#getClientRowKey
   */
  public String getClientRowKey()
  {
    // only call getCurrencyKey if we already have a dataModel.
    // otherwise behave as though no currency was set.
    // we need to do this because we don't want dataModel created for components
    // that are not rendered. The faces RI calls getClientId even on components
    // that are not rendered and this in turn was calling this method:
    Object currencyObject = _getCurrencyKey();
    if (currencyObject == null)
      return null;

    Object initKey = _getCurrencyKeyForInitialStampState();
    if (_equals(currencyObject, initKey))
      return null;

    FacesContext fc = FacesContext.getCurrentInstance();
    String key = getClientRowKeyManager().getClientRowKey(fc, this, currencyObject);
    return key;
  }

  /**
   * This is a safe way of getting currency keys and not accidentally forcing
   * the model to execute. When rendered="false" we should never execute the model.
   * However, the JSF engine calls certain methods when rendered="false" such as
   * processSaveState and getClientId.
   * Those methods, in turn, get the CurrencyKey.
   */
  private Object _getCurrencyKey()
  {
    // use false so that we don't create an internal state.
    // if the internal state is created too soon, then the same internal
    // state will get shared across all nested table instances.
    // this was causing bug 4616844:
    InternalState iState = _getInternalState(false);
    if (iState == null)
      return null;

    return (iState._model != null)
      ? getRowKey()
      : _getCurrencyKeyForInitialStampState();
  }

  /**
     * Restores this component's rowData to be what it was when the given
     * client rowKey string was created.
     * @see UIXCollection#getClientRowKey()
     */
  public void setClientRowKey(String clientRowKey)
  {
    if (clientRowKey == null)
    {
      setRowKey(_getCurrencyKeyForInitialStampState());
      return;
    }

    FacesContext fc = FacesContext.getCurrentInstance();
    Object rowkey = getClientRowKeyManager().getRowKey(fc, this, clientRowKey);

    if (rowkey == null)
    {
      _LOG.severe("CANNOT_FIND_ROWKEY",clientRowKey);
    }
    else
      setRowKey(rowkey);
  }

  public void processRestoreState(
    FacesContext context,
    Object       state)
  {
    boolean inContextAtMethodInvocation = _inContext;
    if (!inContextAtMethodInvocation)
    {
      _setupContextChange();
    }

    try
    {
      super.processRestoreState(context, state);
    }
    finally
    {
      if (!inContextAtMethodInvocation)
      {
        _tearDownContextChange();
      }
    }
  }

  public void processUpdates(FacesContext context)
  {
    boolean inContextAtMethodInvocation = _inContext;
    if (!inContextAtMethodInvocation)
    {
      _setupContextChange();
    }

    try
    {
      super.processUpdates(context);
    }
    finally
    {
      if (!inContextAtMethodInvocation)
      {
        _tearDownContextChange();
      }
    }
  }

  public void processValidators(FacesContext context)
  {
    boolean inContextAtMethodInvocation = _inContext;
    if (!inContextAtMethodInvocation)
    {
      _setupContextChange();
    }

    try
    {
      super.processValidators(context);
    }
    finally
    {
      if (!inContextAtMethodInvocation)
      {
        _tearDownContextChange();
      }
    }
  }

  public void processEvent(ComponentSystemEvent event)
    throws AbortProcessingException
  {
    boolean inContextAtMethodInvocation = _inContext;
    if (!inContextAtMethodInvocation)
    {
      _setupContextChange();
    }

    try
    {
      super.processEvent(event);
    }
    finally
    {
      if (!inContextAtMethodInvocation)
      {
        _tearDownContextChange();
      }
    }
  }

  /**
     * Gets the client-id of this component, without any NamingContainers.
     * This id changes depending on the currency Object.
     * Because this implementation uses currency strings, the local client ID is
     * not stable for very long. Its lifetime is the same as that of a
     * currency string.
     * @see UIXCollection#getClientRowKey()
     * @return the local clientId
     */
  @Override
  public final String getContainerClientId(FacesContext context)
  {
    String id = getClientId(context);
    String key = getClientRowKey();
    if (key != null)
    {
      StringBuilder bld = __getSharedStringBuilder();
      bld.append(id).append(NamingContainer.SEPARATOR_CHAR).append(key);
      id = bld.toString();
    }

    return id;
  }

  /**
   * Prepares this component for a change in the rowData.
   * This method should be called right before the rowData changes.
   * It saves the internal states of all the stamps of this component
   * so that they can be restored when the rowData is reverted.
   */
  protected final void preRowDataChange()
  {
    _saveStampState();
    InternalState iState = _getInternalState(true);
    // mark the cached rowKey as invalid:
    iState._currentRowKey = _NULL;
    // we don't have cached rowkey, thus remove rowkeychangelistener
    if (iState._model != null)
      iState._model.removeRowKeyChangeListener(iState);
  }

  /**
   * Sets up this component to use the new rowData.
   * This method should be called right after the rowData changes.
   * It sets up the var EL variable to be the current rowData.
   * It also sets up the internal states of all the stamps of this component
   * to match this new rowData.
   */
  protected final void postRowDataChange()
  {
    Object rowData = getRowData();
    if (_LOG.isFinest() && (rowData == null))
    {
      _LOG.finest("rowData is null at rowIndex:"+getRowIndex()+
                  " and currencyKey:"+getRowKey());
    }

    InternalState iState = _getInternalState(true);
    if (rowData == null)
    {
      // if the rowData is null, then we will restore the EL 'var' variable
      // to be whatever the value was, before this component started rendering:
      if (iState._prevVarValue != _NULL)
      {
        _setELVar(iState._var, iState._prevVarValue);
        iState._prevVarValue = _NULL;
      }
      if (iState._prevVarStatus != _NULL)
      {
        _setELVar(iState._varStatus, iState._prevVarStatus);
        iState._prevVarStatus = _NULL;
      }
    }
    else
    {
      if (iState._var != null)
      {
        Object oldData = _setELVar(iState._var, rowData);
        if (iState._prevVarValue == _NULL)
          iState._prevVarValue = oldData;
      }

      // varStatus is not set per row. It is only set once.
      // if _PrevVarStatus has not been assigned, then we have not set the
      // varStatus yet:
      if ((iState._varStatus != null) && (iState._prevVarStatus == _NULL))
      {
        Map varStatusMap = createVarStatusMap();
        iState._prevVarStatus = _setELVar(iState._varStatus, varStatusMap);
      }
    }

    _restoreStampState();

    // ensure the client IDs are reset on the component, otherwise they will not get the
    // proper stamped IDs. This mirrors the behavior in UIData and follows the JSF specification
    // on when client IDs are allowed to be cached and when they must be reset
    List stamps = getStamps();

    for (UIComponent stamp : stamps)
      UIXComponent.clearCachedClientIds(stamp);
  }

  /**
   * Gets the UIComponents that are considered stamps.
   * This implementation simply returns the children of this component.
   * @return each element must be of type UIComponent.
   */
  @SuppressWarnings("unchecked")
  protected List getStamps()
  {
    return getChildren();
  }

  /**
   * Gets the currencyObject to setup the rowData to use to build initial
   * stamp state.
   * 

* This allows the collection model to have an initial row key outside of the UIComponent. * Should the model be at a row that is not the first row, the component will restore the row * back to the initial row key instead of a null row key once stamping is done. *

*/ private Object _getCurrencyKeyForInitialStampState() { InternalState iState = _getInternalState(false); if (iState == null) return null; Object rowKey = iState._initialStampStateKey; return (rowKey == _NULL) ? null : rowKey; } /** * Saves the state of a stamp. This method is called when the currency of this * component is changed so that the state of this stamp can be preserved, before * the stamp is updated with the state corresponding to the new currency. * This method recurses for the children and facets of the stamp. * @return this object must be Serializable if client-side state saving is * used. */ @SuppressWarnings("unchecked") protected Object saveStampState(FacesContext context, UIComponent stamp) { if (stamp.isTransient()) return null; boolean needsTearDownContext = false; if(stamp instanceof FlattenedComponent && stamp instanceof UIXComponent) { ((UIXComponent)stamp).setupVisitingContext(context); needsTearDownContext = true; } Object[] state = null; try { // The structure we will use is: // 0: state of the stamp // 1: state of the children (a map from child's id to its state) // 2: state of the facets (a map from facet name to its state) // If there is no facet state, we have a two-element array // If there is no facet state or child state, we have a one-elment array // If there is no state at all, we return null Object stampState = StampState.saveStampState(context, stamp); // StampState can never EVER be an Object array, as if we do, // we have no possible way of identifying the difference between // just having stamp state, and having stamp state + child/facet state assert(!(stampState instanceof Object[])); int facetCount = stamp.getFacetCount(); if (facetCount > 0) { Map facetState = null; Map facetMap = stamp.getFacets(); for(Map.Entry entry : facetMap.entrySet()) { Object singleFacetState = saveStampState(context, entry.getValue()); if (singleFacetState == null) continue; // Don't bother allocating anything until we have some non-null // facet state if (facetState == null) { facetState = new HashMap(facetCount); } facetState.put(entry.getKey(), singleFacetState); } // OK, we had something: allocate the state array to three // entries, and insert the facet state at position 2 if (facetState != null) { state = new Object[3]; state[2] = facetState; } } // If we have any children, iterate through the array, // saving state Object childState = StampState.saveChildStampState(context, stamp, this); if (childState != null) { // If the state hasn't been allocated yet, we only // need a two-element array if (state == null) state = new Object[2]; state[1] = childState; } // If we don't have an array, just return the stamp // state if (state == null) return stampState; // Otherwise, store the stamp state at index 0, and return state[0] = stampState; } finally { if(needsTearDownContext) ((UIXComponent)stamp).tearDownVisitingContext(context); } return state; } /** * Restores the state of a stamp. This method is called after the currency of this * component is changed so that the state of this stamp can be changed * to match the new currency. * This method recurses for the children and facets of the stamp. */ @SuppressWarnings("unchecked") protected void restoreStampState(FacesContext context, UIComponent stamp, Object stampState) { // No state for the component - return if (stampState == null) { return; } // If this isn't an Object array, then it's a component with state // of its own, but no child/facet state - so restore and be done if (!(stampState instanceof Object[])) { StampState.restoreStampState(context, stamp, stampState); // NOTE early return return; } Object[] state = (Object[]) stampState; int stateSize = state.length; // We always have at least one element if we get to here assert(stateSize >= 1); StampState.restoreStampState(context, stamp, state[0]); // If there's any facet state, restore it if (stateSize >= 3 && (state[2] instanceof Map)) { Map facetStateMap = (Map) state[2]; // This had better be non-null, otherwise we never // should have allocated a three-element map! assert(facetStateMap != null); for (String facetName : facetStateMap.keySet()) { Object facetState = facetStateMap.get(facetName); if (facetState != null) restoreStampState(context, stamp.getFacet(facetName), facetState); } } // If there's any child state, restore it if (stateSize >= 2) { StampState.restoreChildStampState(context, stamp, this, state[1]); } } /** * Process a component. * This method calls {@link #processDecodes(FacesContext)}, * {@link #processValidators} or * {@link #processUpdates} * depending on the {#link PhaseId}. */ protected final void processComponent( FacesContext context, UIComponent component, PhaseId phaseId) { if (component != null) { if (phaseId == PhaseId.APPLY_REQUEST_VALUES) component.processDecodes(context); else if (phaseId == PhaseId.PROCESS_VALIDATIONS) component.processValidators(context); else if (phaseId == PhaseId.UPDATE_MODEL_VALUES) component.processUpdates(context); else throw new IllegalArgumentException(_LOG.getMessage( "BAD_PHASEID",phaseId)); } } /** * Process this component's facets and children. * This method should call {@link #processComponent} * as many times as necessary for each facet and child. * {@link #processComponent} * may be called repeatedly for the same child if that child is * being stamped. */ protected abstract void processFacetsAndChildren( FacesContext context, PhaseId phaseId); /** * Gets the CollectionModel to use with this component. */ protected final CollectionModel getCollectionModel() { return getCollectionModel(true); } /** * Gets the ClientRowKeyManager that is used to handle the * {@link #getClientRowKey} and * {@link #setClientRowKey} methods. * If the manager does not already exist a new one is created. * In order to create your own manager, your Renderer (for this component) * must implement * {@link ClientRowKeyManagerFactory} */ public final ClientRowKeyManager getClientRowKeyManager() { // this method must be public, because specific renderers // need access to the ClientRowKeyManager so that they might prune it. InternalState iState = _getInternalState(true); if (iState._clientKeyMgr == null) { FacesContext fc = FacesContext.getCurrentInstance(); Renderer r = getRenderer(fc); iState._clientKeyMgr = (r instanceof ClientRowKeyManagerFactory) ? ((ClientRowKeyManagerFactory) r).createClientRowKeyManager(fc, this) : new DefaultClientKeyManager(); } return iState._clientKeyMgr; } public boolean invokeOnComponent(FacesContext context, String clientId, ContextCallback callback) throws FacesException { boolean invokedComponent; setupVisitingContext(context); try { String thisClientId = getClientId(context); if (clientId.equals(thisClientId)) { if (!_getAndMarkFirstInvokeForRequest(context, clientId)) { // Call _init() since __flushCachedModel() assumes that // selectedRowKeys and disclosedRowKeys are initialized to be non-null _init(); __flushCachedModel(); } RequestContext requestContext = RequestContext.getCurrentInstance(); requestContext.pushCurrentComponent(context, this); pushComponentToEL(context, null); try { callback.invokeContextCallback(context, this); } finally { popComponentFromEL(context); requestContext.popCurrentComponent(context, this); } invokedComponent = true; } else { // If we're on a row, set the currency, and invoke // inside int thisClientIdLength = thisClientId.length(); if (clientId.startsWith(thisClientId) && (clientId.charAt(thisClientIdLength) == NamingContainer.SEPARATOR_CHAR)) { if (!_getAndMarkFirstInvokeForRequest(context, thisClientId)) { // Call _init() since __flushCachedModel() assumes that // selectedRowKeys and disclosedRowKeys are initialized to be non-null _init(); __flushCachedModel(); } String postId = clientId.substring(thisClientIdLength + 1); int sepIndex = postId.indexOf(NamingContainer.SEPARATOR_CHAR); // If there's no separator character afterwards, then this // isn't a row key if (sepIndex < 0) return invokeOnChildrenComponents(context, clientId, callback); else { String currencyString = postId.substring(0, sepIndex); Object rowKey = getClientRowKeyManager().getRowKey(context, this, currencyString); // A non-null rowKey here means we are on a row and we should set currency, otherwise // the client id is for a non-stamped child component in the table/column header/footer. if (rowKey != null) { Object oldRowKey = getRowKey(); try { setRowKey(rowKey); invokedComponent = invokeOnChildrenComponents(context, clientId, callback); } finally { // And restore the currency setRowKey(oldRowKey); } } else { invokedComponent = invokeOnChildrenComponents(context, clientId, callback); } } } else { // clientId isn't in this subtree invokedComponent = false; } } } finally { tearDownVisitingContext(context); } return invokedComponent; } /** *

* Override default children visiting code to visit the facets and facets of the columns * before delegating to the visitData to visit the individual rows of data. *

* Subclasses should override this method if they wish to change the way in which the non-stamped * children are visited. If they wish to change the wash the the stamped children are visited, * they should override visitData instead. *

* @param visitContext * @param callback * @return true if all of the children to visit have been visited * @see #visitData */ @Override protected boolean visitChildren( VisitContext visitContext, VisitCallback callback) { return defaultVisitChildren(visitContext, callback); } /** * Performs a non-iterating visit of the children. The default implementation visits all * of the children. If the UIXCollection subclass doesn't visit some of its children in * certain cases, it needs to override this method. * @param visitContext * @param callback * @return */ protected boolean visitChildrenWithoutIterating( VisitContext visitContext, VisitCallback callback) { return visitAllChildren(visitContext, callback); } /** * Default implementation of child visiting of UIXCollection subclasses for cases where a * UIXCollection subclass wants to restore the default implementation that one of its * superclasses have overridden. * @param visitContext * @param callback * @return */ protected final boolean defaultVisitChildren( VisitContext visitContext, VisitCallback callback) { if (ComponentUtils.isSkipIterationVisit(visitContext)) { return visitChildrenWithoutIterating(visitContext, callback); } else { boolean doneVisiting; // Clear out the row index if one is set so that // we start from a clean slate. int oldRowIndex = getRowIndex(); setRowIndex(-1); try { // visit the unstamped children doneVisiting = visitUnstampedFacets(visitContext, callback); if (!doneVisiting) { doneVisiting = _visitStampedColumnFacets(visitContext, callback); // visit the stamped children if (!doneVisiting) { doneVisiting = visitData(visitContext, callback); } } } finally { // restore the original rowIndex setRowIndex(oldRowIndex); } return doneVisiting; } } /** * Hook method for subclasses to override to change the behavior * of how unstamped facets of the UIXCollection are visited. The * Default implementation visits all of the facets of the * UIXCollection. */ protected boolean visitUnstampedFacets( VisitContext visitContext, VisitCallback callback) { // Visit the facets with no row if (getFacetCount() > 0) { for (UIComponent facet : getFacets().values()) { if (UIXComponent.visitTree(visitContext, facet, callback)) { return true; } } } return false; } /** * VistiContext that visits the facets of the UIXColumn children, including * nested UIXColumn childrem */ private static class ColumnFacetsOnlyVisitContext extends VisitContextWrapper { public ColumnFacetsOnlyVisitContext(VisitContext wrappedContext) { _wrapped = wrappedContext; } @Override public VisitContext getWrapped() { return _wrapped; } @Override public VisitResult invokeVisitCallback(UIComponent component, VisitCallback callback) { if (component instanceof UIXColumn) { if (component.getFacetCount() > 0) { // visit the facet children without filtering for just UIXColumn children for (UIComponent facetChild : component.getFacets().values()) { if (UIXComponent.visitTree(getWrapped(), facetChild, callback)) return VisitResult.COMPLETE; } // visit the indexed children, recursively looking for more columns for (UIComponent child : component.getChildren()) { if (UIXComponent.visitTree(this, child, callback)) return VisitResult.COMPLETE; } } } // at this point, we either have already manually processed the UIXColumn's children, or // the component wasn't a UIXColumn and shouldn't be processed return VisitResult.REJECT; } private final VisitContext _wrapped; } /** * VisitContext implementation that doesn't visit any of the Facets of * UIXColumn children. This is used when stamping children */ protected static final class NoColumnFacetsVisitContext extends VisitContextWrapper { NoColumnFacetsVisitContext(VisitContext wrapped) { _wrapped = wrapped; } @Override public VisitContext getWrapped() { return _wrapped; } @Override public VisitResult invokeVisitCallback(UIComponent component, VisitCallback callback) { if (component instanceof UIXColumn) { if (component.getChildCount() > 0) { // visit only the indexed children of the columns for (UIComponent child : component.getChildren()) { if (UIXComponent.visitTree(this, child, callback)) return VisitResult.COMPLETE; } } return VisitResult.REJECT; } else { // Components do not expect to be visited twice, in fact with UIXComponent, it is illegal. // This is due to the fact that UIXComponent has setup and tearDown methods for visiting. // In order to avoid having the setup method called for the current visit context and // the wrapped context we invoke the visit on the component and then separately on the // children of the component VisitContext wrappedContext = getWrapped(); VisitResult visitResult = wrappedContext.invokeVisitCallback(component, callback); if (visitResult == VisitResult.ACCEPT) { // Let the visitation continue with the wrapped context return (UIXComponent.visitChildren(wrappedContext, component, callback)) ? VisitResult.COMPLETE : VisitResult.REJECT; } else { return visitResult; } } } private final VisitContext _wrapped; } /** * Implementation used to visit each stamped row */ private boolean _visitStampedColumnFacets( VisitContext visitContext, VisitCallback callback) { // visit the facets of the stamped columns List stamps = getStamps(); if (!stamps.isEmpty()) { VisitContext columnVisitingContext = new ColumnFacetsOnlyVisitContext(visitContext); for (UIComponent stamp : stamps) { if (UIXComponent.visitTree(columnVisitingContext, stamp, callback)) { return true; } } } return false; } /** * Visit the rows and children of the columns of the collection per row-index. This should * not visit row index -1 (it will be perfomed in the visitTree method). The columns * themselves should not be visited, only their children in this function. * * @param visitContext The visiting context * @param callback The visit callback * @return true if the visiting should stop * @see #visitChildren(VisitContext, VisitCallback) */ protected abstract boolean visitData( VisitContext visitContext, VisitCallback callback); /** * Gets the CollectionModel to use with this component. * * @param createIfNull creates the collection model if necessary */ protected final CollectionModel getCollectionModel( boolean createIfNull) { InternalState iState = _getInternalState(true); if (iState._model == null && createIfNull) { // _init() is usually called from either processDecodes or encodeBegin. // Sometimes both processDecodes and encodeBegin may not be called, // but processSaveState is called (this happens when // component's rendered attr is set to false). We need to make sure that // _init() is called in that case as well. Otherwise we get nasty NPEs. // safest place is to call it here: _init(); iState._value = getValue(); iState._model = createCollectionModel(null, iState._value); postCreateCollectionModel(iState._model); assert iState._model != null; } // model might not have been created if createIfNull is false: if ((iState._initialStampStateKey == _NULL) && (iState._model != null)) { // if we have not already initialized the initialStampStateKey // that means that we don't have any stamp-state to use as the default // state for rows that we have not seen yet. So... // we will use any stamp-state for the initial rowKey on the model // as the default stamp-state for all rows: iState._initialStampStateKey = iState._model.getRowKey(); } return iState._model; } /** * Creates the CollectionModel to use with this component. * The state of the UIComponent with the new model instance is not fully initialized until * after this method returns. As a result, other component attributes that need * a fully initialized model should not be initialized in this method. Instead, * model-dependent initialization should be done in postCreateCollectionModel * @see #postCreateCollectionModel * @param current the current CollectionModel, or null if there is none. * @param value this is the value returned from {@link #getValue()} */ protected abstract CollectionModel createCollectionModel( CollectionModel current, Object value); /** * Hook called with the result of createCollectionModel. * Subclasses can use this method to perform initialization after the CollectionModel * is fully initialized. * Subclassers should call super before accessing any component state to ensure * that superclass initialization has been performed. * @see #createCollectionModel * @param model The model instance returned by */ protected void postCreateCollectionModel(CollectionModel model) { // do nothing } /** * Gets the value that must be converted into a CollectionModel */ protected abstract Object getValue(); /** * Gets the Map to use as the "varStatus". * This implementation supports the following keys:
    *
  • model - returns the CollectionModel *
  • index - returns the current rowIndex *
  • rowKey - returns the current rowKey *
  • current - returns the current rowData *
  • "hierarchicalIndex" - returns an array containing zero based row index.
  • *
  • "hierarchicalLabel" - returns a string label representing 1 based index of this row.
  • *
*/ protected Map createVarStatusMap() { return new AbstractMap() { @Override public Object get(Object key) { // some of these keys are from , ie: // javax.servlet.jsp.jstl.core.LoopTagStatus if ("model".equals(key)) return getCollectionModel(); if ("rowKey".equals(key)) return getRowKey(); if ("index".equals(key)) // from jstl return Integer.valueOf(getRowIndex()); if("hierarchicalIndex".equals(key)) { int rowIndex = getRowIndex(); return rowIndex>=0 ? new Integer[]{rowIndex}: new Integer[]{}; } if("hierarchicalLabel".equals(key)) { int rowIndex = getRowIndex(); return rowIndex>=0 ? Integer.toString(rowIndex+1): ""; } if ("current".equals(key)) // from jstl return getRowData(); return null; } @Override public Set> entrySet() { return Collections.emptySet(); } }; } // // LocalRowKeyIndex implementation // /** * Given a row index, check if a row is locally available * @param rowIndex index of row to check * @return true if row is locally available */ public boolean isRowLocallyAvailable(int rowIndex) { return getCollectionModel().isRowLocallyAvailable(rowIndex); } /** * Given a row key, check if a row is locally available * @param rowKey row key for the row to check * @return true if row is locally available */ public boolean isRowLocallyAvailable(Object rowKey) { return getCollectionModel().isRowLocallyAvailable(rowKey); } /** * Check if a range of rows is locally available starting from current position * @param rowCount number of rows in the range * @return true if range of rows is locally available */ public boolean areRowsLocallyAvailable(int rowCount) { return getCollectionModel().areRowsLocallyAvailable(rowCount); } /** * Check if a range of rows is locally available starting from a row index * @param startIndex staring index for the range * @param rowCount number of rows in the range * @return true if range of rows is locally available */ public boolean areRowsLocallyAvailable(int startIndex, int rowCount) { return getCollectionModel().areRowsLocallyAvailable(startIndex, rowCount); } /** * Check if a range of rows is locally available starting from a row key * @param startRowKey staring row key for the range * @param rowCount number of rows in the range * @return true if range of rows is locally available */ public boolean areRowsLocallyAvailable(Object startRowKey, int rowCount) { return getCollectionModel().areRowsLocallyAvailable(startRowKey, rowCount); } /** * Convenient API to return a row count estimate. This method can be optimized * to avoid a data fetch which may be required to return an exact row count * @return estimated row count */ public int getEstimatedRowCount() { return getCollectionModel().getEstimatedRowCount(); } /** * Helper API to determine if the row count returned from {@link #getEstimatedRowCount} * is EXACT, or an ESTIMATE */ public LocalRowKeyIndex.Confidence getEstimatedRowCountConfidence() { return getCollectionModel().getEstimatedRowCountConfidence(); } /** * clear all rows from the local cache */ public void clearLocalCache() { getCollectionModel().clearLocalCache(); } /** * Clear the requested range of rows from the local cache * @param startingIndex starting row index for the range to clear * @param rowsToClear number of rows to clear from the cache */ public void clearCachedRows(int startingIndex, int rowsToClear) { getCollectionModel().clearCachedRows(startingIndex, rowsToClear); } /** * Clear the requested range of rows from the local cache * @param startingRowKey starting row key for the range to clear * @param rowsToClear number of rows to clear from the cache */ public void clearCachedRows(Object startingRowKey, int rowsToClear) { getCollectionModel().clearCachedRows(startingRowKey, rowsToClear); } /** * Clear a row from the local cache by row index * @param index row index for the row to clear from the cache */ public void clearCachedRow(int index) { getCollectionModel().clearCachedRow(index); } /** * Clear a row from the local cache by row key * @param rowKey row key for the row to clear from the cache */ public void clearCachedRow(Object rowKey) { getCollectionModel().clearCachedRow(rowKey); } /** * Indicates the caching strategy supported by the model * @see LocalRowKeyIndex.LocalCachingStrategy * @return caching strategy supported by the model */ public LocalRowKeyIndex.LocalCachingStrategy getCachingStrategy() { return getCollectionModel().getCachingStrategy(); } /** * Ensure that the model has at least rowCount number of rows. * * @param rowCount the number of rows the model should hold. */ public void ensureRowsAvailable(int rowCount) { getCollectionModel().ensureRowsAvailable(rowCount); } /** * override this method to place initialization code that must run * once this component is created and the jsp engine has finished setting * attributes on it. */ void __init() { InternalState iState = _getInternalState(true); iState._var = getVar(); if (_LOG.isFine() && (iState._var == null)) { _LOG.fine("'var' attribute is null."); } iState._varStatus = getVarStatus(); if (_LOG.isFinest() && (iState._varStatus == null)) { _LOG.finest("'varStatus' attribute is null."); } } /** * Hook for subclasses like UIXIterator to initialize and flush the cache when visting flattened * children when parented by a renderer that needs to use * UIXComponent.processFlattenedChildren(). * This is to mimic what happens in the non flattening case where similar logic is invoked * during encodeBegin(). */ protected void processFlattenedChildrenBegin(ComponentProcessingContext cpContext) { // Call _init() since __flushCachedModel() assumes that // selectedRowKeys and disclosedRowKeys are initialized to be non-null. _init(); __flushCachedModel(); } private void _init() { InternalState iState = _getInternalState(true); if (!iState._isInitialized) { assert iState._model == null; iState._isInitialized = true; __init(); } } void __flushCachedModel() { InternalState iState = _getInternalState(true); Object value = getValue(); if (iState._value != value) { CollectionModel oldModel = iState._model; iState._value = value; iState._model = createCollectionModel(iState._model, value); postCreateCollectionModel(iState._model); // if the underlying model is changed, we need to remove // the listener from the old model. And if we still have cached // rowkey, we need to add the listener back to the new model. if (oldModel != iState._model) { if (oldModel != null) { oldModel.removeRowKeyChangeListener(iState); } if (iState._currentRowKey != _NULL) { iState._model.addRowKeyChangeListener(iState); } } } } // // Returns true if this is the first request to invokeOnComponent() // static private boolean _getAndMarkFirstInvokeForRequest( FacesContext context, String clientId) { // See if the request contains a marker that we've hit this // method already for this clientId Map requestMap = context.getExternalContext().getRequestMap(); String key = _INVOKE_KEY + clientId; // Yep, we have, so return true if (requestMap.containsKey(key)) return true; // Stash TRUE for next time, and return false requestMap.put(key, Boolean.TRUE); return false; } /** * Gets the internal state of this component. * This is to support table within table. */ Object __getMyStampState() { return _state; } /** * Sets the internal state of this component. * This is to support table within table. * @param stampState the internal state is obtained from this object. */ void __setMyStampState(Object stampState) { InternalState iState = (InternalState) stampState; _state = iState; } /** * reset the stamp state to pristine state. This pristine state when saved to the outer collection for null currency * will allow stamp state for UIXCollection with individual rows to be created * * This is to support iteration of children(column stamping) within the table. */ void __resetMyStampState() { _state = null; } /** * Returns true if an event (other than a selection event) * has been queued for this component. This is a hack * to support validation in the tableSelectXyz components. */ boolean __hasEvent() { InternalState iState = _getInternalState(true); return iState._hasEvent; } /** * Saves the state of all the stamps of this component. * This method should be called before the rowData of this component * changes. This method gets all the stamps using {@link #getStamps} and * saves their states by calling {@link #saveStampState}. */ private void _saveStampState() { // Never read and created by _getStampState //InternalState iState = _getInternalState(true); StampState stampState = _getStampState(); Map idToIndexMap = _getIdToIndexMap(); FacesContext context = getFacesContext(); Object currencyObj = getRowKey(); // Note: even though the currencyObj may be null, we still need to save the state. The reason // is that the code does not clear out the state when it is saved, instead, the un-stamped // state is saved. Once the row key is set back to null, this un-stamped state is restored // onto the children components. This restoration allows editable value holders, show detail // items and nested UIXCollections to clear their state. // For nested UIXCollections, this un-stamped state is required to set the nested collection's // _state (internal state containing the stamp state) to null when not on a row key. Without // that call, the nested UIXCollection components would end up sharing the same stamp state // across parent rows. for (UIComponent stamp : getStamps()) { Object state = saveStampState(context, stamp); // String stampId = stamp.getId(); // TODO // temporarily use position. later we need to use ID's to access // stamp state everywhere, and special case NamingContainers: String compId = stamp.getId(); String stampId = idToIndexMap.get(compId); if (stampId == null) { stampId = String.valueOf(idToIndexMap.size()); idToIndexMap.put(compId, stampId); } stampState.put(currencyObj, stampId, state); if (_LOG.isFinest()) _LOG.finest("saving stamp state for currencyObject:"+currencyObj+ " and stampId:"+stampId); } } /** * Restores the state of all the stamps of this component. * This method should be called after the currency of this component * changes. This method gets all the stamps using {@link #getStamps} and * restores their states by calling * {@link #restoreStampState}. */ private void _restoreStampState() { StampState stampState = _getStampState(); Map idToIndexMap = _getIdToIndexMap(); FacesContext context = getFacesContext(); Object currencyObj = getRowKey(); for(UIComponent stamp : getStamps()) { // String stampId = stamp.getId(); // TODO // temporarily use position. later we need to use ID's to access // stamp state everywhere, and special case NamingContainers: String compId = stamp.getId(); String stampId = idToIndexMap.get(compId); Object state = stampState.get(currencyObj, stampId); if (state == null) { Object iniStateObj = _getCurrencyKeyForInitialStampState(); state = stampState.get(iniStateObj, stampId); /* if (state==null) { _LOG.severe("NO_INITIAL_STAMP_STATE", new Object[]{currencyObj,iniStateObj,stampId}); continue; }*/ } restoreStampState(context, stamp, state); } } private Map _getIdToIndexMap() { InternalState iState = _getInternalState(true); if (iState._idToIndexMap == null) iState._idToIndexMap = new HashMap(); return iState._idToIndexMap; } private InternalState _getInternalState(boolean create) { if ((_state == null) && create) { _state = new InternalState(); } return _state; } private StampState _getStampState() { InternalState iState = _getInternalState(true); if (iState._stampState == null) iState._stampState = new StampState(); return iState._stampState; } /** * sets an EL variable. * @param varName the name of the variable * @param newData the value of the variable * @return the old value of the variable, or null if there was no old value. */ private Object _setELVar(String varName, Object newData) { if (varName == null) return null; // we need to place each row at an EL reachable place so that it // can be accessed via the 'var' variable. Let's place it on the // requestMap: return setupELVariable(getFacesContext(), varName, newData); } /** * Called by UIXCollection to set values for the "var" and * "varStatus" EL variables. * * @param context the FacesContext for the current request * @param name the non-null name of the EL variable * @param value the value of the EL variable * @return the previous value of the EL variable, or null if * the value was not previously set. */ protected Object setupELVariable( FacesContext context, String name, Object value ) { Args.notNull(name, "name"); return TableUtils.setupELVariable(context, name, value); } private static boolean _equals(Object a, Object b) { if (b == null) return (a == null); return b.equals(a); } private void _setupContextChange() { if (_inSuspendOrResume) { // This situation will occur when the CollectionComponentChange is currently setting the // row key. return; } ComponentContextManager compCtxMgr = RequestContext.getCurrentInstance().getComponentContextManager(); compCtxMgr.pushChange(new CollectionComponentChange(this)); _inContext = true; } private void _tearDownContextChange() { if (_inSuspendOrResume) { // This situation will occur when the CollectionComponentChange is currently setting the // row key. return; } try { ComponentContextManager compCtxMgr = RequestContext.getCurrentInstance().getComponentContextManager(); ComponentContextChange change = compCtxMgr.peekChange(); if (change instanceof CollectionComponentChange && ((CollectionComponentChange)change)._component == this) { // Remove the component context change if one was added compCtxMgr.popChange(); } else { _LOG.severe("COLLECTION_CHANGE_TEARDOWN", new Object[] { getId(), change }); } } catch (RuntimeException re) { _LOG.severe(re); } finally { _inContext = false; } } private void _verifyComponentInContext() { if (_inSuspendOrResume) { return; } if (!_inContext) { if (_LOG.isWarning()) { _LOG.warning("COLLECTION_NOT_IN_CONTEXT", new Object[] { getParent() == null ? getId() : getClientId() }); if (_LOG.isFine()) { Thread.currentThread().dumpStack(); } } } } /** * during state saving, we want to reset the currency to null, but we want to * remember the current currency, so that after state saving, we can set it back * * @param context faces context * @return the currency key */ private Object _resetCurrencyKeyForStateSaving(FacesContext context) { // If we saved state in the middle of processing a row, // then make sure that we revert to a "null" rowKey while // saving state; this is necessary to ensure that the // current row's state is properly preserved, and that // the children are reset to their default state. Object currencyKey = _getCurrencyKey(); // since this is the end of the request, we expect the row currency to be reset back to null // setting it and leaving it there might introduce multiple issues, so log a warning here if (currencyKey != null) { if (_LOG.isWarning()) { String scopedId = ComponentUtils.getScopedIdForComponent(this, context.getViewRoot()); String viewId = context.getViewRoot() == null? null: context.getViewRoot().getViewId(); _LOG.warning("ROWKEY_NOT_RESET", new Object[] { scopedId, viewId }); } } Object initKey = _getCurrencyKeyForInitialStampState(); if (currencyKey != initKey) // beware of null currencyKeys if equals() is used { setRowKey(initKey); } return currencyKey; } /** * restore the currency key after state saving * * @param key the currency key */ private void _restoreCurrencyKeyForStateSaving(Object key) { Object currencyKey = key; Object initKey = _getCurrencyKeyForInitialStampState(); if (currencyKey != initKey) // beware of null currencyKeys if equals() is used { setRowKey(currencyKey); } } /** * clean up any internal model state that we might be holding on to. */ private void _resetInternalState() { InternalState iState = _getInternalState(false); if (iState != null) { iState._value = null; if (iState._model != null) { iState._model.removeRowKeyChangeListener(iState); iState._model = null; } } } private static final class DefaultClientKeyManager extends ClientRowKeyManager { public void clear() { _currencyCache.clear(); } /** * {@inheritDoc} */ @Override public Object getRowKey(FacesContext context, UIComponent component, String clientRowKey) { ValueMap currencyCache = _currencyCache; Object rowkey = currencyCache.getKey(clientRowKey); return rowkey; } /** * {@inheritDoc} */ @Override public String getClientRowKey(FacesContext context, UIComponent component, Object rowKey) { assert rowKey != null; ValueMap currencyCache = _currencyCache; String key = currencyCache.get(rowKey); // check to see if we already have a string key: if (key == null) { // we don't have a string-key, so create a new one. key = _createToken(currencyCache); if (_LOG.isFiner()) _LOG.finer("Storing token:"+key+ " for rowKey:"+rowKey); currencyCache.put(rowKey, key); } return key; } /** * {@inheritDoc} */ @Override public boolean replaceRowKey(FacesContext context, UIComponent component, Object oldRowKey, Object newRowKey) { assert oldRowKey != null && newRowKey != null; ValueMap currencyCache = _currencyCache; String key = currencyCache.remove(oldRowKey); // check to see if we already have a string key: if (key != null) { currencyCache.put(newRowKey, key); } return key != null; } private static String _createToken(ValueMap currencyCache) { String key = String.valueOf(currencyCache.size()); return key; } private ValueMap _currencyCache = new ValueMap(); private static final long serialVersionUID = 1L; } // this component's internal state is stored in an inner class // rather than in individual fields, because we want to make it // easy to quickly suck out or restore its internal state, // when this component is itself used as a stamp inside some other // stamping container, eg: nested tables. private static final class InternalState implements RowKeyChangeListener, Serializable { private transient boolean _hasEvent = false; private transient Object _prevVarValue = _NULL; private transient Object _prevVarStatus = _NULL; private transient String _var = null; private transient String _varStatus = null; private transient Object _value = null; private transient CollectionModel _model = null; private transient Object _currentRowKey = _NULL; private transient boolean _clearTokenCache = false; // this is true if this is the first request for this viewID and processDecodes // was not called: private transient boolean _isFirstRender = true; private transient boolean _isInitialized = false; // this is the rowKey used to retrieve the default stamp-state for all rows: private transient Object _initialStampStateKey = _NULL; private ClientRowKeyManager _clientKeyMgr = null; private StampState _stampState = null; // map from column id to the index within the collection. The index is used to // save/look up for column's stamp state. The long term goal is to use id as key // to the stamp state map, but for this release, we add this id-to-index map // to indirectly loop up a stamp state for a column, so if the position of the column // changes in the middle, we'll still be able to find the right stamp state. private Map _idToIndexMap = null; public void onRowKeyChange(RowKeyChangeEvent rowKeyChangeEvent) { Object newKey = rowKeyChangeEvent.getNewRowKey(); Object oldKey = rowKeyChangeEvent.getOldRowKey(); if (newKey != null && oldKey != null && !newKey.equals(oldKey)) { // first replace the cached row key if (oldKey.equals(_currentRowKey)) _currentRowKey = newKey; // then update stamp state for the affected entries. if (_stampState == null || _idToIndexMap == null) return; int stampCompCount = _idToIndexMap.size(); for (int index = 0; index < stampCompCount; index++) { String stampId = String.valueOf(index); Object state = _stampState.get(oldKey, stampId); if (state == null) continue; _stampState.put(oldKey, stampId, null); _stampState.put(newKey, stampId, state); } } } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); // Set values of all transients to their defaults _prevVarValue = _NULL; _prevVarStatus = _NULL; _currentRowKey = _NULL; _initialStampStateKey = _NULL; // However, leave _isFirstRender set to false - since that's // necessarily the state we'd be in if we're reconstituting this _isFirstRender = false; } private static final long serialVersionUID = 1L; } /** * Class to be able to suspend the context of the collection. *

Current implementation removes the var and varStatus from the request while the * collection is suspended.

*/ private static class CollectionComponentChange extends ComponentContextChange { private CollectionComponentChange( UIXCollection component) { _component = component; } public void suspend( FacesContext facesContext) { _component._inSuspendOrResume = true; try { InternalState iState = _component._getInternalState(false); if (iState == null || iState._model == null || iState._currentRowKey == _NULL) { // If we were to try to call getRowKey() here, this would call getCollectionModel(). // The get collection model may result in EL being evaluated, which is undesirable // and will cause bugs when called while we are suspending. This is because evaluating // EL may need to suspend or resume other component context changes, and we do not want // re-entrant calls to the component context stack while we are already suspending. // Note that this code will fail if someone has set the _model to null while on a rowKey // (Should not happen, would be considered a bug if that were to be done). _rowKey = null; } else { _rowKey = _component.getRowKey(); // Set the row key back to null to force the collection into the un-stamped state. This // will ensure that the collection is not in a row key while the component context is // not setup. Only do this if the row key is not already on the null row key. if (_rowKey != null) { _component.setRowKey(null); } } _component._inContext = false; } finally { _component._inSuspendOrResume = false; } } public void resume( FacesContext facesContext) { _component._inSuspendOrResume = true; try { // Only set the row key if one was stored during the suspend. if (_rowKey != null) { _component.setRowKey(_rowKey); } _component._inContext = true; } finally { _component._inSuspendOrResume = false; } } @Override public String toString() { String className = _component.getClass().getName(); String componentId = _component.getId(); return new StringBuilder(58 + className.length() + componentId.length()) .append("UIXCollection.CollectionComponentChange[Component class: ") .append(className) .append(", component ID: ") .append(componentId) .append("]") .toString(); } private final UIXCollection _component; private CollectionModel _collectionModel; private Object _rowKey; } private static class CollectionContextEvent extends WrapperEvent { public CollectionContextEvent( UIComponent source, FacesEvent event) { super(source, event); } @SuppressWarnings("compatibility:-7639429485707197863") private static final long serialVersionUID = 1L; } // do not assign a non-null value. values should be assigned lazily. this is // because this value is preserved by stampStateSaving, when table-within-table // is used. And if a non-null value is used, then all nested tables will // end up sharing this stampState. see bug 4279735: private InternalState _state = null; private boolean _inSuspendOrResume = false; private boolean _inContext = false; // use this key to indicate uninitialized state. // all the variables that use this are transient so this object need not // be Serializable: private static final Object _NULL = new Object(); private static final String _INVOKE_KEY = UIXCollection.class.getName() + ".INVOKE"; private transient Object _stateSavingCurrencyKey = null; private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(UIXCollection.class); // An enum to throw into state-saving so that we get a nice // instance-equality to test against for noticing transient components // (and better serialization results) // We need this instead of just using null - because transient components // are specially handled, since they may or may not actually still // be there when you go to restore state later (e.g., on the next request!) enum Transient { TRUE }; }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy