blob: c82b36bfc409980c72305d6b1826232b4d941a12 [file] [log] [blame]
/*
* 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 javax.faces.component.behavior;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.el.ValueExpression;
import javax.faces.component.StateHelper;
import javax.faces.component.StateHolder;
import javax.faces.component.UIComponentBase;
import javax.faces.context.FacesContext;
/**
* A delta enabled state holder implementing the StateHolder Interface.
* <p>
* Components implementing the PartalStateHolder interface have an initial state
* and delta states, the initial state is the one holding all root values
* and deltas store differences to the initial states
* </p>
* <p>
* For components not implementing partial state saving only the initial states are
* of importance, everything is stored and restored continously there
* </p>
* <p>
* The state helper seems to have three internal storage mechanisms:
* one being a list which stores plain values,
* one being a key value pair which stores key values in maps
* add serves the plain list type while put serves the
* key value type,
* the third is the value which has to be stored plainly as is!
* </p>
* In other words, this map can be seen as a composite map. It has two maps:
* initial state map and delta map.
* <p>
* If delta map is used (method component.initialStateMarked() ),
* base or initial state map cannot be changed, since all changes
* should be tracked on delta map.
* </p>
* <p>
* The intention of this class is just hold property values
* and do a clean separation between initial state and delta.
* </p>
* <p>
* The context from this class comes and that should be taken into account
* is this:
* </p>
* <p>
* First request:
* </p>
* <ul>
* <li> A new template is created (using
* javax.faces.view.ViewDeclarationLanguage.buildView method)
* and component.markInitialState is called from its related TagHandler classes
* (see javax.faces.view.facelets.ComponentHandler ).
* When this method is executed, the component tree was populated from the values
* set in the facelet abstract syntax tree (or in other words composition of
* facelets templates). </li>
* <li> From this point all updates on the variables are considered "delta". </li>
* <li> SaveState, if initialStateMarked is true, only delta is saved. </li>
* </ul>
* <p>
* Second request (and next ones)
* </p>
* <ul>
* <li> A new template is created and component.markInitialState is called from
* its related TagHandler classes again. In this way, components like c:forEach
* or c:if, that add or remove components could notify about this and handle
* them properly (see javax.faces.view.StateManagementStrategy). Note that a
* component restored using this method is no different as the same component
* at the first request at the same time. </li>
* <li> A call for restoreState is done, passing the delta as object value. If no
* delta, the state is complete and no call is triggered. </li>
* <li> Lifecycle occur, changing the necessary stuff. </li>
* <li> SaveState, if initialStateMarked is true, only delta is saved. </li>
* </ul>
* <p>
* From the previous analysis, the following conclusions arise:
* <ul>
* <li>This class only needs to keep track of delta changes, so when
* restoreState/saveState is called, the right objects are passed.</li>
* <li>UIComponent.clearInitialState is used to reset the partial
* state holder to a non delta state, so the state to be saved by
* saveState is no longer a delta instead is a full state. If a call
* to clearInitialState occur it is not expected a call for
* UIComponent.markInitialState occur on the current request.</li>
* <li>The state is handled in the same way on UIData, so components
* inside UIData share its state on all rows. There is no way to save
* delta per row.</li>
* <li>The map backed by method put(Serializable,String,Object) is
* a replacement of UIComponentBase.attributesMap and UIComponent.bindings map.
* Note that on jsf 1.2, instances saved on attributesMap should not be
* StateHolder, but on jsf 2.0 it is possible to have it. PartialStateHolder
* instances are not handled in this map, or in other words delta state is not
* handled in this classes (markInitialState and clearInitialState is not propagated).</li>
* <li>The list backed by method add(Serializable,Object) should be (is not) a
* replacement of UIComponentBase.facesListeners, but note that StateHelper
* does not implement PartialStateHolder, and facesListener could have instances
* of that class that needs to be notified when UIComponent.markInitialState or
* UIComponent.clearInitialState is called, or in other words facesListeners
* should deal with PartialStateHolder instances.</li>
* <li>The list backed by method add(Serializable,Object) is
* a replacement of UIViewRoot.phaseListeners list. Note that instances of
* PhaseListener are not expected to implement StateHolder or PartialStateHolder.</li>
* </ul>
* </p>
* <p>
* NOTE: The current implementation of StateHelper on RI does not handle
* stateHolder values internally. To prevent problems when developers create
* custom components we should do this too. But anyway, the code that
* handle this case should be let here as comment, if some day this feature
* is provided. Note than stateHolder aware properties like converter,
* validator or listeners should deal with StateHolder or PartialStateHolder
* on component classes.
*/
class _DeltaStateHelper <A extends AjaxBehavior> implements StateHelper
{
/**
* We need to hold a component instance because:
*
* - The component is the one who knows if we are on initial or delta mode
* - eval assume calls to component.ValueExpression
*/
private A _target;
/**
* This map holds the full current state
*/
private Map<Serializable, Object> _fullState;
/**
* This map only keep track of delta changes to be saved
*/
private Map<Serializable, Object> _deltas;
private boolean _transient = false;
public _DeltaStateHelper(A target)
{
super();
this._target = target;
this._fullState = new HashMap<>();
this._deltas = null;
}
/**
* Used to create delta map on demand
*
* @return
*/
private boolean _createDeltas()
{
if (isInitialStateMarked())
{
if (_deltas == null)
{
_deltas = new HashMap<>(2, 1f);
}
return true;
}
return false;
}
protected boolean isInitialStateMarked()
{
return _target.initialStateMarked();
}
@Override
public void add(Serializable key, Object value)
{
if (_createDeltas())
{
//Track delta case
Map<Object, Boolean> deltaListMapValues = (Map<Object, Boolean>) _deltas.computeIfAbsent(key,
k -> new InternalDeltaListMap<>(3, 1f));
deltaListMapValues.put(value, Boolean.TRUE);
}
//Handle change on full map
List<Object> fullListValues = (List<Object>) _fullState.computeIfAbsent(key,
k -> new InternalList<>(3));
fullListValues.add(value);
}
@Override
public Object eval(Serializable key)
{
Object returnValue = _fullState.get(key);
if (returnValue != null)
{
return returnValue;
}
ValueExpression expression = _target.getValueExpression(key.toString());
if (expression != null)
{
return expression.getValue(_target.getFacesContext().getELContext());
}
return null;
}
@Override
public Object eval(Serializable key, Object defaultValue)
{
Object returnValue = _fullState.get(key);
if (returnValue != null)
{
return returnValue;
}
ValueExpression expression = _target.getValueExpression(key.toString());
if (expression != null)
{
return expression.getValue(_target.getFacesContext().getELContext());
}
return defaultValue;
}
@Override
public Object get(Serializable key)
{
return _fullState.get(key);
}
@Override
public Object put(Serializable key, Object value)
{
Object returnValue = null;
if (_createDeltas())
{
if (_deltas.containsKey(key))
{
returnValue = _deltas.put(key, value);
_fullState.put(key, value);
}
else if (value == null && !_fullState.containsKey(key))
{
returnValue = null;
}
else
{
_deltas.put(key, value);
returnValue = _fullState.put(key, value);
}
}
else
{
returnValue = _fullState.put(key, value);
}
return returnValue;
}
public Object put(Serializable key, String mapKey, Object value)
{
boolean returnSet = false;
Object returnValue = null;
if (_createDeltas())
{
//Track delta case
Map<String, Object> mapValues = (Map<String, Object>) _deltas.computeIfAbsent(key,
k -> new InternalMap<>());
if (mapValues.containsKey(mapKey))
{
returnValue = mapValues.put(mapKey, value);
returnSet = true;
}
else
{
mapValues.put(mapKey, value);
}
}
//Handle change on full map
Map<String, Object> mapValues = (Map<String, Object>) _fullState.computeIfAbsent(key,
k -> new InternalMap<>());
if (returnSet)
{
mapValues.put(mapKey, value);
}
else
{
returnValue = mapValues.put(mapKey, value);
}
return returnValue;
}
@Override
public Object remove(Serializable key)
{
Object returnValue = null;
if (_createDeltas())
{
if (_deltas.containsKey(key))
{
// Keep track of the removed values using key/null pair on the delta map
returnValue = _deltas.put(key, null);
_fullState.remove(key);
}
else
{
// Keep track of the removed values using key/null pair on the delta map
_deltas.put(key, null);
returnValue = _fullState.remove(key);
}
}
else
{
returnValue = _fullState.remove(key);
}
return returnValue;
}
@Override
public Object remove(Serializable key, Object valueOrKey)
{
// Comment by lu4242 : The spec javadoc says if it is a Collection
// or Map deal with it. But the intention of this method is work
// with add(?,?) and put(?,?,?), this ones return instances of
// InternalMap and InternalList to prevent mixing, so to be
// consistent we'll cast to those classes here.
Object collectionOrMap = _fullState.get(key);
Object returnValue = null;
if (collectionOrMap instanceof InternalMap)
{
if (_createDeltas())
{
returnValue = _removeValueOrKeyFromMap(_deltas, key, valueOrKey, true);
_removeValueOrKeyFromMap(_fullState, key, valueOrKey, false);
}
else
{
returnValue = _removeValueOrKeyFromMap(_fullState, key, valueOrKey, false);
}
}
else if (collectionOrMap instanceof InternalList)
{
if (_createDeltas())
{
returnValue = _removeValueOrKeyFromCollectionDelta(_deltas, key, valueOrKey);
_removeValueOrKeyFromCollection(_fullState, key, valueOrKey);
}
else
{
returnValue = _removeValueOrKeyFromCollection(_fullState, key, valueOrKey);
}
}
return returnValue;
}
private static Object _removeValueOrKeyFromCollectionDelta(
Map<Serializable, Object> stateMap, Serializable key, Object valueOrKey)
{
Object returnValue = null;
Map<Object, Boolean> c = (Map<Object, Boolean>) stateMap.get(key);
if (c != null)
{
if (c.containsKey(valueOrKey))
{
returnValue = valueOrKey;
}
c.put(valueOrKey, Boolean.FALSE);
}
return returnValue;
}
private static Object _removeValueOrKeyFromCollection(
Map<Serializable, Object> stateMap, Serializable key, Object valueOrKey)
{
Object returnValue = null;
Collection c = (Collection) stateMap.get(key);
if (c != null)
{
if (c.remove(valueOrKey))
{
returnValue = valueOrKey;
}
if (c.isEmpty())
{
stateMap.remove(key);
}
}
return returnValue;
}
private static Object _removeValueOrKeyFromMap(
Map<Serializable, Object> stateMap, Serializable key, Object valueOrKey, boolean delta)
{
if (valueOrKey == null)
{
return null;
}
Object returnValue = null;
Map<String, Object> map = (Map<String, Object>) stateMap.get(key);
if (map != null)
{
if (delta)
{
// Keep track of the removed values using key/null pair on the delta map
returnValue = map.put((String) valueOrKey, null);
}
else
{
returnValue = map.remove(valueOrKey);
}
if (map.isEmpty())
{
stateMap.put(key, null);
}
}
return returnValue;
}
@Override
public boolean isTransient()
{
return _transient;
}
/**
* Serializing cod
* the serialized data structure consists of key value pairs unless the value itself is an internal array
* or a map in case of an internal array or map the value itself is another array with its initial value
* myfaces.InternalArray, myfaces.internalMap
*
* the internal Array is then mapped to another array
*
* the internal Map again is then mapped to a map with key value pairs
*
*
*/
@Override
public Object saveState(FacesContext context)
{
Map serializableMap = (isInitialStateMarked()) ? _deltas : _fullState;
if (serializableMap == null || serializableMap.isEmpty())
{
return null;
}
Map.Entry<Serializable, Object> entry;
Object[] retArr = new Object[serializableMap.entrySet().size() * 2];
Iterator<Map.Entry<Serializable, Object>> it = serializableMap.entrySet().iterator();
int cnt = 0;
while (it.hasNext())
{
entry = it.next();
retArr[cnt] = entry.getKey();
Object value = entry.getValue();
// The condition in which the call to saveAttachedState
// is to handle List, StateHolder or non Serializable instances.
// we check it here, to prevent unnecessary calls.
if (value instanceof StateHolder ||
value instanceof List ||
!(value instanceof Serializable))
{
Object savedValue = saveAttachedState(context, value);
retArr[cnt + 1] = savedValue;
}
else
{
retArr[cnt + 1] = value;
}
cnt += 2;
}
return retArr;
}
@Override
public void restoreState(FacesContext context, Object state)
{
if (state == null)
{
return;
}
Object[] serializedState = (Object[]) state;
if (!isInitialStateMarked() && !_fullState.isEmpty())
{
_fullState.clear();
if(_deltas != null)
{
_deltas.clear();
}
}
for (int cnt = 0; cnt < serializedState.length; cnt += 2)
{
Serializable key = (Serializable) serializedState[cnt];
Object savedValue = restoreAttachedState(context, serializedState[cnt + 1]);
if (isInitialStateMarked())
{
if (savedValue instanceof InternalDeltaListMap)
{
for (Map.Entry<Object, Boolean> mapEntry : ((Map<Object, Boolean>) savedValue).entrySet())
{
boolean addOrRemove = mapEntry.getValue();
if (addOrRemove)
{
//add
this.add(key, mapEntry.getKey());
}
else
{
//remove
this.remove(key, mapEntry.getKey());
}
}
}
else if (savedValue instanceof InternalMap)
{
for (Map.Entry<String, Object> mapEntry : ((Map<String, Object>) savedValue).entrySet())
{
this.put(key, mapEntry.getKey(), mapEntry.getValue());
}
}
/*
else if (savedValue instanceof _AttachedDeltaWrapper)
{
_AttachedStateWrapper wrapper = (_AttachedStateWrapper) savedValue;
//Restore delta state
((PartialStateHolder)_fullState.get(key)).restoreState(context, wrapper.getWrappedStateObject());
//Add this key as StateHolder key
_stateHolderKeys.add(key);
}
*/
else
{
put(key, savedValue);
}
}
else
{
put(key, savedValue);
}
}
}
@Override
public void setTransient(boolean transientValue)
{
_transient = transientValue;
}
//We use our own data structures just to make sure
//nothing gets mixed up internally
static class InternalMap<K, V> extends HashMap<K, V> implements StateHolder
{
public InternalMap()
{
super();
}
public InternalMap(int initialCapacity, float loadFactor)
{
super(initialCapacity, loadFactor);
}
public InternalMap(Map<? extends K, ? extends V> m)
{
super(m);
}
public InternalMap(int initialSize)
{
super(initialSize);
}
@Override
public boolean isTransient()
{
return false;
}
@Override
public void setTransient(boolean newTransientValue)
{
// No op
}
@Override
public void restoreState(FacesContext context, Object state)
{
Object[] listAsMap = (Object[]) state;
for (int cnt = 0; cnt < listAsMap.length; cnt += 2)
{
this.put((K) listAsMap[cnt], (V) UIComponentBase.restoreAttachedState(context, listAsMap[cnt + 1]));
}
}
@Override
public Object saveState(FacesContext context)
{
int cnt = 0;
Object[] mapArr = new Object[this.size() * 2];
for (Map.Entry<K, V> entry : this.entrySet())
{
mapArr[cnt] = entry.getKey();
Object value = entry.getValue();
if (value instanceof StateHolder ||
value instanceof List ||
!(value instanceof Serializable))
{
mapArr[cnt + 1] = saveAttachedState(context, value);
}
else
{
mapArr[cnt + 1] = value;
}
cnt += 2;
}
return mapArr;
}
}
/**
* Map used to keep track of list changes
*/
static class InternalDeltaListMap<K, V> extends InternalMap<K, V>
{
public InternalDeltaListMap()
{
super();
}
public InternalDeltaListMap(int initialCapacity, float loadFactor)
{
super(initialCapacity, loadFactor);
}
public InternalDeltaListMap(int initialSize)
{
super(initialSize);
}
public InternalDeltaListMap(Map<? extends K, ? extends V> m)
{
super(m);
}
}
static class InternalList<T> extends ArrayList<T> implements StateHolder
{
public InternalList()
{
super();
}
public InternalList(Collection<? extends T> c)
{
super(c);
}
public InternalList(int initialSize)
{
super(initialSize);
}
@Override
public boolean isTransient()
{
return false;
}
@Override
public void setTransient(boolean newTransientValue)
{
}
@Override
public void restoreState(FacesContext context, Object state)
{
Object[] listAsArr = (Object[]) state;
//since all other options would mean dual iteration
//we have to do it the hard way
for (Object elem : listAsArr)
{
add((T) restoreAttachedState(context, elem));
}
}
@Override
public Object saveState(FacesContext context)
{
Object[] values = new Object[size()];
for (int i = 0; i < size(); i++)
{
Object value = get(i);
if (value instanceof StateHolder ||
value instanceof List ||
!(value instanceof Serializable))
{
values[i] = saveAttachedState(context, value);
}
else
{
values[i] = value;
}
}
return values;
}
}
private static Object saveAttachedState(FacesContext context, Object attachedObject)
{
if (context == null)
{
throw new NullPointerException ("context");
}
if (attachedObject == null)
{
return null;
}
// StateHolder interface should take precedence over
// List children
if (attachedObject instanceof StateHolder)
{
StateHolder holder = (StateHolder) attachedObject;
if (holder.isTransient())
{
return null;
}
return new _AttachedStateWrapper(attachedObject.getClass(), holder.saveState(context));
}
else if (attachedObject instanceof List)
{
List<Object> lst = new ArrayList<Object>(((List<?>) attachedObject).size());
for (Object item : (List<?>) attachedObject)
{
if (item != null)
{
lst.add(saveAttachedState(context, item));
}
}
return new _AttachedListStateWrapper(lst);
}
else if (attachedObject instanceof Serializable)
{
return attachedObject;
}
else
{
return new _AttachedStateWrapper(attachedObject.getClass(), null);
}
}
private static Object restoreAttachedState(FacesContext context, Object stateObj) throws IllegalStateException
{
if (context == null)
{
throw new NullPointerException("context");
}
if (stateObj == null)
{
return null;
}
if (stateObj instanceof _AttachedListStateWrapper)
{
List<Object> lst = ((_AttachedListStateWrapper) stateObj).getWrappedStateList();
List<Object> restoredList = new ArrayList<Object>(lst.size());
for (Object item : lst)
{
restoredList.add(restoreAttachedState(context, item));
}
return restoredList;
}
else if (stateObj instanceof _AttachedStateWrapper)
{
Class<?> clazz = ((_AttachedStateWrapper) stateObj).getClazz();
Object restoredObject;
try
{
restoredObject = clazz.newInstance();
}
catch (InstantiationException e)
{
throw new RuntimeException("Could not restore StateHolder of type " + clazz.getName()
+ " (missing no-args constructor?)", e);
}
catch (IllegalAccessException e)
{
throw new RuntimeException(e);
}
if (restoredObject instanceof StateHolder)
{
_AttachedStateWrapper wrapper = (_AttachedStateWrapper) stateObj;
Object wrappedState = wrapper.getWrappedStateObject();
StateHolder holder = (StateHolder) restoredObject;
holder.restoreState(context, wrappedState);
}
return restoredObject;
}
else
{
return stateObj;
}
}
}