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