blob: a019f89eac05e35d0e46a21e902f84e173c377bb [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 org.apache.myfaces.tobago.internal.behavior;
import org.apache.myfaces.tobago.exception.TobagoException;
import javax.el.ValueExpression;
import javax.faces.component.StateHelper;
import javax.faces.component.StateHolder;
import javax.faces.component.UIComponentBase;
import javax.faces.context.FacesContext;
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;
// todo: clean up (is a copy of MyFaces, but not all stuff is refactored)
/**
* 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 code from this class comes from a refactor of
* org.apache.myfaces.trinidad.bean.util.PropertyHashMap
* </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.
* <p>
* </p>
*
* @since 3.0.0
*/
class DeltaStateHelper<A extends EventBehavior> implements StateHelper {
/**
* We need to hold a component instance because:
* <p>
* - 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;
/**
* This map keep track of StateHolder keys, to be saved when
* saveState is called.
*/
//private Set<Serializable> _stateHolderKeys;
private boolean transientBoolean = false;
DeltaStateHelper(final A target) {
super();
this.target = target;
fullState = new HashMap<>();
deltas = null;
//_stateHolderKeys = new HashSet<Serializable>();
}
/**
* Used to create delta map on demand
*
* @return
*/
private boolean createDeltas() {
if (isInitialStateMarked()) {
if (deltas == null) {
deltas = new HashMap<>(2);
}
return true;
}
return false;
}
protected boolean isInitialStateMarked() {
return target.initialStateMarked();
}
@Override
public void add(final Serializable key, final Object value) {
if (createDeltas()) {
//Track delta case
Map<Object, Boolean> deltaListMapValues = (Map<Object, Boolean>) deltas
.get(key);
if (deltaListMapValues == null) {
deltaListMapValues = new DeltaStateHelper.InternalDeltaListMap<>(
3);
deltas.put(key, deltaListMapValues);
}
deltaListMapValues.put(value, Boolean.TRUE);
}
//Handle change on full map
List<Object> fullListValues = (List<Object>) fullState.get(key);
if (fullListValues == null) {
fullListValues = new DeltaStateHelper.InternalList<>(3);
fullState.put(key, fullListValues);
}
fullListValues.add(value);
}
@Override
public Object eval(final Serializable key) {
final Object returnValue = fullState.get(key);
if (returnValue != null) {
return returnValue;
}
final ValueExpression expression = target.getValueExpression(key
.toString());
if (expression != null) {
return expression.getValue(FacesContext.getCurrentInstance()
.getELContext());
}
return null;
}
@Override
public Object eval(final Serializable key, final Object defaultValue) {
final Object returnValue = fullState.get(key);
if (returnValue != null) {
return returnValue;
}
final ValueExpression expression = target.getValueExpression(key
.toString());
if (expression != null) {
return expression.getValue(FacesContext.getCurrentInstance()
.getELContext());
}
return defaultValue;
}
@Override
public Object get(final Serializable key) {
return fullState.get(key);
}
@Override
public Object put(final Serializable key, final 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 {
/*
if (value instanceof StateHolder)
{
_stateHolderKeys.add(key);
}
*/
returnValue = fullState.put(key, value);
}
return returnValue;
}
@Override
public Object put(final Serializable key, final String mapKey, final Object value) {
boolean returnSet = false;
Object returnValue = null;
if (createDeltas()) {
//Track delta case
Map<String, Object> mapValues = (Map<String, Object>) deltas
.get(key);
if (mapValues == null) {
mapValues = new DeltaStateHelper.InternalMap<>();
deltas.put(key, mapValues);
}
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
.get(key);
if (mapValues == null) {
mapValues = new DeltaStateHelper.InternalMap<>();
fullState.put(key, mapValues);
}
if (returnSet) {
mapValues.put(mapKey, value);
} else {
returnValue = mapValues.put(mapKey, value);
}
return returnValue;
}
@Override
public Object remove(final 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(final Serializable key, final 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.
final Object collectionOrMap = fullState.get(key);
Object returnValue = null;
if (collectionOrMap instanceof DeltaStateHelper.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 DeltaStateHelper.InternalList) {
if (createDeltas()) {
returnValue = removeValueOrKeyFromCollectionDelta(deltas,
key, valueOrKey);
removeValueOrKeyFromCollection(fullState, key, valueOrKey);
} else {
returnValue = removeValueOrKeyFromCollection(fullState, key,
valueOrKey);
}
}
return returnValue;
}
private static Object removeValueOrKeyFromCollectionDelta(
final Map<Serializable, Object> stateMap, final Serializable key,
final Object valueOrKey) {
Object returnValue = null;
final 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(
final Map<Serializable, Object> stateMap, final Serializable key,
final Object valueOrKey) {
Object returnValue = null;
final 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(
final Map<Serializable, Object> stateMap, final Serializable key,
final Object valueOrKey, final boolean delta) {
if (valueOrKey == null) {
return null;
}
Object returnValue = null;
final 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.remove(key);
stateMap.put(key, null);
}
}
return returnValue;
}
@Override
public boolean isTransient() {
return transientBoolean;
}
/**
* 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
* <p>
* the internal Array is then mapped to another array
* <p>
* the internal Map again is then mapped to a map with key value pairs
*/
@Override
public Object saveState(final FacesContext context) {
final Map serializableMap = (isInitialStateMarked()) ? deltas : fullState;
if (serializableMap == null || serializableMap.size() == 0) {
return null;
}
/*
int stateHolderKeyCount = 0;
if (isInitalStateMarked())
{
for (Iterator<Serializable> it = _stateHolderKeys.iterator(); it.hasNext();)
{
Serializable key = it.next();
if (!deltas.containsKey(key))
{
stateHolderKeyCount++;
}
}
}*/
Map.Entry<Serializable, Object> entry;
//entry == key, value, key, value
final Object[] retArr = new Object[serializableMap.entrySet().size() * 2];
//Object[] retArr = new Object[serializableMap.entrySet().size() * 2 + stateHolderKeyCount];
final Iterator<Map.Entry<Serializable, Object>> it = serializableMap.entrySet().iterator();
int cnt = 0;
while (it.hasNext()) {
entry = it.next();
retArr[cnt] = entry.getKey();
final 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)) {
final Object savedValue = saveAttachedState(context, value);
retArr[cnt + 1] = savedValue;
} else {
retArr[cnt + 1] = value;
}
cnt += 2;
}
/*
if (isInitalStateMarked())
{
for (Iterator<Serializable> it2 = _stateHolderKeys.iterator(); it.hasNext();)
{
Serializable key = it2.next();
if (!deltas.containsKey(key))
{
retArr[cnt] = key;
Object value = fullState.get(key);
if (value instanceof PartialStateHolder)
{
//Could contain delta, save it as _AttachedDeltaState
PartialStateHolder holder = (PartialStateHolder) value;
if (holder.isTransient())
{
retArr[cnt + 1] = null;
}
else
{
retArr[cnt + 1] = new _AttachedDeltaWrapper(value.getClass(), holder.saveState(context));
}
}
else
{
//Save everything
retArr[cnt + 1] = saveAttachedState(context, fullState.get(key));
}
cnt += 2;
}
}
}
*/
return retArr;
}
@Override
public void restoreState(final FacesContext context, final Object state) {
if (state == null) {
return;
}
final Object[] serializedState = (Object[]) state;
if (!isInitialStateMarked() && !fullState.isEmpty()) {
fullState.clear();
if (deltas != null) {
deltas.clear();
}
}
for (int cnt = 0; cnt < serializedState.length; cnt += 2) {
final Serializable key = (Serializable) serializedState[cnt];
final Object savedValue = restoreAttachedState(context,
serializedState[cnt + 1]);
if (isInitialStateMarked()) {
if (savedValue instanceof DeltaStateHelper.InternalDeltaListMap) {
for (final Map.Entry<Object, Boolean> mapEntry : ((Map<Object, Boolean>) savedValue)
.entrySet()) {
final boolean addOrRemove = mapEntry.getValue();
if (addOrRemove) {
//add
this.add(key, mapEntry.getKey());
} else {
//remove
this.remove(key, mapEntry.getKey());
}
}
} else if (savedValue instanceof DeltaStateHelper.InternalMap) {
for (final 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(final boolean transientValue) {
transientBoolean = 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 {
InternalMap() {
super();
}
InternalMap(final int initialCapacity, final float loadFactor) {
super(initialCapacity, loadFactor);
}
InternalMap(final Map<? extends K, ? extends V> m) {
super(m);
}
InternalMap(final int initialSize) {
super(initialSize);
}
@Override
public boolean isTransient() {
return false;
}
@Override
public void setTransient(final boolean newTransientValue) {
// No op
}
@Override
public void restoreState(final FacesContext context, final Object state) {
final 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(final FacesContext context) {
int cnt = 0;
final Object[] mapArr = new Object[this.size() * 2];
for (final Map.Entry<K, V> entry : this.entrySet()) {
mapArr[cnt] = entry.getKey();
final 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 DeltaStateHelper.InternalMap<K, V> {
InternalDeltaListMap() {
super();
}
InternalDeltaListMap(final int initialCapacity, final float loadFactor) {
super(initialCapacity, loadFactor);
}
InternalDeltaListMap(final int initialSize) {
super(initialSize);
}
InternalDeltaListMap(final Map<? extends K, ? extends V> m) {
super(m);
}
}
static class InternalList<T> extends ArrayList<T> implements StateHolder {
InternalList() {
super();
}
InternalList(final Collection<? extends T> c) {
super(c);
}
InternalList(final int initialSize) {
super(initialSize);
}
@Override
public boolean isTransient() {
return false;
}
@Override
public void setTransient(final boolean newTransientValue) {
}
@Override
public void restoreState(final FacesContext context, final Object state) {
final Object[] listAsArr = (Object[]) state;
//since all other options would mean dual iteration
//we have to do it the hard way
for (final Object elem : listAsArr) {
add((T) restoreAttachedState(context, elem));
}
}
@Override
public Object saveState(final FacesContext context) {
final Object[] values = new Object[size()];
for (int i = 0; i < size(); i++) {
final 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(final FacesContext context, final 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) {
final StateHolder holder = (StateHolder) attachedObject;
if (holder.isTransient()) {
return null;
}
return new AttachedStateWrapper(attachedObject.getClass(), holder.saveState(context));
} else if (attachedObject instanceof List) {
final List<Object> lst = new ArrayList<>(((List<?>) attachedObject).size());
for (final 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(final FacesContext context, final Object stateObj)
throws IllegalStateException {
if (context == null) {
throw new NullPointerException("context");
}
if (stateObj == null) {
return null;
}
if (stateObj instanceof AttachedListStateWrapper) {
final List<Object> lst = ((AttachedListStateWrapper) stateObj).getWrappedStateList();
final List<Object> restoredList = new ArrayList<>(lst.size());
for (final Object item : lst) {
restoredList.add(restoreAttachedState(context, item));
}
return restoredList;
} else if (stateObj instanceof AttachedStateWrapper) {
final Class<?> clazz = ((AttachedStateWrapper) stateObj).getClazz();
final Object restoredObject;
try {
restoredObject = clazz.newInstance();
} catch (final InstantiationException e) {
throw new TobagoException("Could not restore StateHolder of type " + clazz.getName()
+ " (missing no-args constructor?)", e);
} catch (final IllegalAccessException e) {
throw new TobagoException(e);
}
if (restoredObject instanceof StateHolder) {
final AttachedStateWrapper wrapper = (AttachedStateWrapper) stateObj;
final Object wrappedState = wrapper.getWrappedStateObject();
final StateHolder holder = (StateHolder) restoredObject;
holder.restoreState(context, wrappedState);
}
return restoredObject;
} else {
return stateObj;
}
}
}