blob: dd262b9cabd0a0a33294a02cda9c6330f5a7d7db [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.trinidadinternal.context;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.faces.context.FacesContext;
import org.apache.myfaces.trinidad.logging.TrinidadLogger;
import org.apache.myfaces.trinidadinternal.util.SubKeyMap;
import org.apache.myfaces.trinidadinternal.util.TokenCache;
/**
*
*/
class PageFlowScopeMap implements Map<String, Object>, Serializable
{
/**
* Return a PageFlowScopeMap stored with a token.
*/
static public PageFlowScopeMap getPageFlowScopeMap(
FacesContext context, String token, int lifetime)
{
TokenCache cache = _getRootTokenCache(context, lifetime);
PageFlowScopeMap map = _getPageFlowScopeMap(context, cache, token);
if (_LOG.isFine())
{
_LOG.fine("pageFlowScope: found map {0} at token {1}",
new Object[]{(map == null) ? (Object) "null" : map, token});
}
if (map == null)
{
return null;
}
else
{
// Don't return the same instance of PageFlowScopeMap as was used
// on the previous page; otherwise, for instance, we'll overwrite
// its token as we mutate. Instead, create a new PageFlowScopeMap,
// but reuse the _map; we'll clone the _map itself if we mutate
return new PageFlowScopeMap(map._map,
token,
map._sharedData);
}
}
static private PageFlowScopeMap _getPageFlowScopeMap(
FacesContext context, TokenCache cache, String token)
{
if (token == null)
throw new NullPointerException();
int lastSeparator = token.lastIndexOf(TokenCache.SEPARATOR_CHAR);
String parentToken;
String childToken;
if (lastSeparator < 0)
{
parentToken = null;
childToken = token;
}
else
{
parentToken = token.substring(0, lastSeparator);
childToken = token.substring(lastSeparator + 1);
}
Map<String, Object> storeMap = _createMapToStore(context, parentToken);
return (PageFlowScopeMap) storeMap.get(childToken);
}
/**
* Only for serialization
*/
public PageFlowScopeMap()
{
}
public PageFlowScopeMap(int lifetime)
{
this(new HashMap<String, Object>(13), null, new SharedData(lifetime));
}
private PageFlowScopeMap(
HashMap<String, Object> map,
String token,
SharedData sharedData)
{
_map = map;
_sharedData = sharedData;
_token = token;
}
//
// Create a PageFlowScopeMap pointing at a parent
//
private PageFlowScopeMap(PageFlowScopeMap parent, boolean copyParent)
{
assert(parent != null);
_sharedData = new SharedData(parent._sharedData._lifetime);
_sharedData._parent = parent;
_map = new HashMap<String, Object>();
if (copyParent)
_map.putAll(parent._map);
}
public PageFlowScopeMap getParent()
{
return _sharedData._parent;
}
synchronized public String getToken(FacesContext context)
{
if (_token != null)
return _token;
// Don't need a token when nothing's in the map, and we
// don't have a parent
if (isEmpty() &&
(_sharedData._children == null) &&
(_sharedData._parent == null))
return null;
String parentToken;
TokenCache cache;
if (_sharedData._parent != null)
{
parentToken = _sharedData._parent.getToken(context);
cache = _sharedData._parent._getTokenCache();
}
else
{
parentToken = null;
cache = _getRootTokenCache(context, _sharedData._lifetime);
}
Map<String, Object> store = _createMapToStore(context, parentToken);
String token = cache.addNewEntry(this, store);
if (parentToken != null)
token = parentToken + TokenCache.SEPARATOR_CHAR + token;
_token = token;
// With a new token, there cannot be any shared children
// with a prior request.
if (_sharedData._children != null)
{
// =-=AEW NEED TO CLONE SHARED DATA
_LOG.fine("Discarding child PageFlowScopes; new token is {0}", token);
_sharedData._children = null;
}
return _token;
}
@SuppressWarnings("unchecked")
static private Map<String, Object> _createMapToStore(
FacesContext context,
String parentToken)
{
String fullToken;
if (parentToken == null)
{
fullToken = _PAGE_FLOW_SCOPE_CACHE + TokenCache.SEPARATOR_CHAR;
}
else
{
fullToken = (_PAGE_FLOW_SCOPE_CACHE + TokenCache.SEPARATOR_CHAR +
parentToken + TokenCache.SEPARATOR_CHAR);
}
return new SubKeyMap(context.getExternalContext().getSessionMap(),
fullToken);
}
@Override
public boolean equals(Object o)
{
if (o instanceof PageFlowScopeMap)
o = ((PageFlowScopeMap) o)._map;
return _map.equals(o);
}
@Override
public int hashCode()
{
return _map.hashCode();
}
public int size()
{
return _map.size();
}
public boolean isEmpty()
{
return _map.isEmpty();
}
public boolean containsKey(Object key)
{
return _map.containsKey(key);
}
public boolean containsValue(Object value)
{
return _map.containsValue(value);
}
public Collection<Object> values()
{
// Use an unmodifiableCollection to save me the headache
// of catching mutations
return Collections.unmodifiableCollection(_map.values());
}
public Set<Map.Entry<String, Object>> entrySet()
{
// Use an unmodifiableSet to save me the headache
// of catching mutations
return Collections.unmodifiableSet(_map.entrySet());
}
public Set<String> keySet()
{
// Use an unmodifiableSet to save me the headache
// of catching mutations
return Collections.unmodifiableSet(_map.keySet());
}
public Object get(Object key)
{
return _map.get(key);
}
public Object put(String key, Object value)
{
_detachIfNeeded();
if (_LOG.isFine())
{
_LOG.fine("pageFlowScope: put({0}, {1})", new Object[]{key, value});
}
return _map.put(key, value);
}
public Object remove(Object key)
{
_detachIfNeeded();
if (_LOG.isFine())
{
_LOG.fine("pageFlowScope: remove({0})", key);
}
return _map.remove(key);
}
public void putAll(Map<? extends String, ? extends Object> t)
{
_detachIfNeeded();
if (_LOG.isFine())
{
_LOG.fine("pageFlowScope: putAll({0})", t);
}
_map.putAll(t);
}
public void clear()
{
_detachIfNeeded();
if (_LOG.isFine())
{
_LOG.fine("pageFlowScope: clear()");
}
_map.clear();
}
public PageFlowScopeMap createChild(boolean copyParent)
{
return new PageFlowScopeMap(this, copyParent);
}
public void discard()
{
FacesContext context = FacesContext.getCurrentInstance();
String token = getToken(context);
int lastSeparator = token.lastIndexOf(TokenCache.SEPARATOR_CHAR);
String parentToken;
String childToken;
if (lastSeparator < 0)
{
parentToken = null;
childToken = token;
}
else
{
parentToken = token.substring(0, lastSeparator);
childToken = token.substring(lastSeparator + 1);
}
// Remove ourselves
if (_sharedData._parent != null)
{
Map<String, Object> storeMap = _createMapToStore(context, parentToken);
_sharedData._parent._sharedData._children.removeOldEntry(childToken,
storeMap);
}
// And clean up all of our children
_removeAllChildren(context, token);
}
private void _removeAllChildren(FacesContext context, String token)
{
// Clear everything - note that because of naming conventions,
// this will in fact automatically recurse through all children
// grandchildren etc. - which is kind of a design flaw of SubKeyMap,
// but one we're relying on
Map<String, Object> store = _createMapToStore(context, token);
store.clear();
_sharedData._children = null;
}
@Override
public String toString()
{
return "PageFlowScopeMap@" + System.identityHashCode(this) +
"[_map=" + _map + ", _token=" + _token +
",_children=" + _sharedData._children + "]";
}
static private TokenCache _getRootTokenCache(FacesContext context,
int lifetime)
{
return TokenCache.getTokenCacheFromSession(context.getExternalContext(),
_PAGE_FLOW_SCOPE_CACHE,
true,
lifetime);
}
private TokenCache _getTokenCache()
{
if (_sharedData._children == null)
_sharedData._children = new TokenCache(_sharedData._lifetime);
return _sharedData._children;
}
// =-=AEW This strategy assumes that the PageFlowScopeMap
// will be inherited from a prior request, have things
// added and removed prior to Render Response *without
// the token being requested*, then have the token used
// repeatedly during Render Response *without further
// mutations*. Both of these assumptions seem very
// dubious!
@SuppressWarnings("unchecked")
private void _detachIfNeeded()
{
if (_token != null)
{
_map = (HashMap<String, Object>) _map.clone();
_token = null;
// =-=AEW When do we discard children?
}
}
static public class SharedData implements Serializable
{
public SharedData()
{
}
public SharedData(int lifetime)
{
_lifetime = lifetime;
}
private int _lifetime;
// =-=AEW Make transient for efficiency
private PageFlowScopeMap _parent;
private TokenCache _children;
private static final long serialVersionUID = 1L;
}
// DELETE AFTER DIALOG SERVICE IS CLEANED UP
boolean __invalid;
private SharedData _sharedData;
private String _token;
private HashMap<String, Object> _map;
private static final String _PAGE_FLOW_SCOPE_CACHE =
"org.apache.myfaces.trinidadinternal.application.PageFlowScope";
private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(PageFlowScopeMap.class);
private static final long serialVersionUID = 1L;
}