org.apache.myfaces.shared.context.flash.FlashImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of myfaces-impl Show documentation
Show all versions of myfaces-impl Show documentation
The private implementation classes of the Apache MyFaces Core JSF-2.0 Implementation
/*
* 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.shared.context.flash;
import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFWebConfigParam;
import org.apache.myfaces.shared.util.ExternalContextUtils;
import javax.faces.application.FacesMessage;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.context.Flash;
import javax.faces.event.PhaseId;
import javax.servlet.ServletRequest;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.io.Serializable;
import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Logger;
/**
* Implementation of Flash object
*
* @author Leonardo Uribe (latest modification by $Author: lu4242 $)
* @author Jakob Korherr
* @version $Revision: 904316 $ $Date: 2010-01-28 17:14:41 -0700 (Thu, 28 Jan 2010) $
*/
public class FlashImpl extends Flash
{
// ~ static fields --------------------------------------------------------
private static final Logger logger = Logger.getLogger(FlashImpl.class.getName());
/**
* Defines whether flash scope is disabled.
*/
@JSFWebConfigParam(defaultValue="false",since="2.0.5")
private static final String FLASH_SCOPE_DISABLED_PARAM = "org.apache.myfaces.FLASH_SCOPE_DISABLED";
/**
* Use this prefix instead of the whole class name, because
* this makes the Cookies and the SubKeyMap operations (actually
* every String based operation where this is used as a key) faster.
*/
private static final String FLASH_PREFIX = "oam.Flash";
/**
* Key on application map to keep current instance
*/
static final String FLASH_INSTANCE = FLASH_PREFIX + ".INSTANCE";
/**
* Key to store if this setRedirect(true) was called on this request,
* and to store the redirect Cookie.
*/
static final String FLASH_REDIRECT = FLASH_PREFIX + ".REDIRECT";
/**
* Key to store the value of the redirect cookie
*/
static final String FLASH_PREVIOUS_REQUEST_REDIRECT
= FLASH_PREFIX + ".REDIRECT.PREVIOUSREQUEST";
/**
* Key used to check if this request should keep messages
*/
static final String FLASH_KEEP_MESSAGES = FLASH_PREFIX + ".KEEP_MESSAGES";
/**
* Key used to store the messages list in the render FlashMap.
*/
static final String FLASH_KEEP_MESSAGES_LIST = "KEEPMESSAGESLIST";
/**
* Session map prefix to flash maps
*/
static final String FLASH_SESSION_MAP_SUBKEY_PREFIX = FLASH_PREFIX + ".SCOPE";
/**
* Key for the cached render FlashMap instance on the request map.
*/
static final String FLASH_RENDER_MAP = FLASH_PREFIX + ".RENDERMAP";
/**
* Key for the current render FlashMap token.
*/
static final String FLASH_RENDER_MAP_TOKEN = FLASH_PREFIX + ".RENDERMAP.TOKEN";
/**
* Key for the cached execute FlashMap instance on the request map.
*/
static final String FLASH_EXECUTE_MAP = FLASH_PREFIX + ".EXECUTEMAP";
/**
* Key for the current execute FlashMap token.
*/
static final String FLASH_EXECUTE_MAP_TOKEN = FLASH_PREFIX + ".EXECUTEMAP.TOKEN";
/**
* Token separator.
*/
static final char SEPARATOR_CHAR = '.';
// ~ static methods -----------------------------------------------------
/**
* Return a Flash instance from the application map
*
* @param context
* @return
*/
public static Flash getCurrentInstance(ExternalContext context)
{
Map applicationMap = context.getApplicationMap();
Flash flash = (Flash) applicationMap.get(FLASH_INSTANCE);
if (flash == null)
{
// synchronize the ApplicationMap to ensure that only
// once instance of FlashImpl is created and stored in it.
synchronized (applicationMap)
{
// check again, because first try was un-synchronized
flash = (Flash) applicationMap.get(FLASH_INSTANCE);
if (flash == null)
{
flash = new FlashImpl(context);
applicationMap.put(FLASH_INSTANCE, flash);
}
}
}
return flash;
}
/**
* Returns a cryptographically secure random number to use as the _count seed
*/
private static long _getSeed()
{
SecureRandom rng;
try
{
// try SHA1 first
rng = SecureRandom.getInstance("SHA1PRNG");
}
catch (NoSuchAlgorithmException e)
{
// SHA1 not present, so try the default (which could potentially not be
// cryptographically secure)
rng = new SecureRandom();
}
// use 48 bits for strength and fill them in
byte[] randomBytes = new byte[6];
rng.nextBytes(randomBytes);
// convert to a long
return new BigInteger(randomBytes).longValue();
}
// ~ private fields and constructor ---------------------------------------
// the current token value
private final AtomicLong _count;
private boolean _flashScopeDisabled;
public FlashImpl(ExternalContext externalContext)
{
_count = new AtomicLong(_getSeed());
// Read whether flash scope is disabled.
_flashScopeDisabled = "true".equalsIgnoreCase(externalContext.getInitParameter(FLASH_SCOPE_DISABLED_PARAM));
}
// ~ methods from javax.faces.context.Flash -------------------------------
/**
* Used to restore the redirect value and the FacesMessages of the previous
* request and to manage the flashMap tokens for this request before phase
* restore view starts.
*/
@Override
public void doPrePhaseActions(FacesContext facesContext)
{
if (!_flashScopeDisabled)
{
final PhaseId currentPhaseId = facesContext.getCurrentPhaseId();
if (PhaseId.RESTORE_VIEW.equals(currentPhaseId))
{
// restore the redirect value
// note that the result of this method is used in many places,
// thus it has to be the first thing to do
_restoreRedirectValue(facesContext);
// restore the FlashMap token from the previous request
// and create a new token for this request
_manageFlashMapTokens(facesContext);
// try to restore any saved messages
_restoreMessages(facesContext);
}
}
}
/**
* Used to destroy the executeMap and to save all FacesMessages for the
* next request, but only if this is the last invocation of this method
* in the current lifecycle (if redirect phase 5, otherwise phase 6).
*/
@Override
public void doPostPhaseActions(FacesContext facesContext)
{
if (!_flashScopeDisabled)
{
// do the actions only if this is the last time
// doPostPhaseActions() is called on this request
if (_isLastPhaseInRequest(facesContext))
{
if (_isRedirectTrueOnThisRequest(facesContext))
{
// copy entries from executeMap to renderMap, if they do not exist
Map renderMap = _getRenderFlashMap(facesContext);
for (Map.Entry entry
: _getExecuteFlashMap(facesContext).entrySet())
{
if (!renderMap.containsKey(entry.getKey()))
{
renderMap.put(entry.getKey(), entry.getValue());
}
}
}
// remove execute Map entries from session (--> "destroy" executeMap)
_clearExecuteFlashMap(facesContext);
// save the current FacesMessages in the renderMap, if wanted
// Note that this also works on a redirect even though the redirect
// was already performed and the response has already been committed,
// because the renderMap is stored in the session.
_saveMessages(facesContext);
}
}
}
/**
* Return the value of this property for the flash for this session.
*
* This must be false unless:
* - setRedirect(boolean) was called for the current lifecycle traversal
* with true as the argument.
* - The current lifecycle traversal for this session is in the "execute"
* phase and the previous traversal had setRedirect(boolean) called with
* true as the argument.
*/
@Override
public boolean isRedirect()
{
FacesContext facesContext = FacesContext.getCurrentInstance();
boolean thisRedirect = _isRedirectTrueOnThisRequest(facesContext);
boolean prevRedirect = _isRedirectTrueOnPreviousRequest(facesContext);
boolean executePhase = !PhaseId.RENDER_RESPONSE.equals(facesContext.getCurrentPhaseId());
return thisRedirect || (executePhase && prevRedirect);
}
@Override
public void setRedirect(boolean redirect)
{
// FIXME this method has a design flaw, because the only valid value
// is true and it should only be called by the NavigationHandler
// in a redirect case RIGHT BEFORE ExternalContext.redirect().
// Maybe a PreRedirectEvent issued by the ExternalContext would be a good
// choice for JSF 2.1.
FacesContext facesContext = FacesContext.getCurrentInstance();
ExternalContext externalContext = facesContext.getExternalContext();
Map requestMap = externalContext.getRequestMap();
// save the value on the requestMap for this request
Boolean alreadySet = (Boolean) requestMap.get(FLASH_REDIRECT);
alreadySet = (alreadySet == null ? Boolean.FALSE : Boolean.TRUE);
// if true and not already set, store it for the following request
if (!alreadySet && redirect)
{
requestMap.put(FLASH_REDIRECT, Boolean.TRUE);
// save redirect=true for the next request
_saveRedirectValue(facesContext);
}
else
{
if (alreadySet)
{
logger.warning("Multiple call to setRedirect() ignored.");
}
else // redirect = false
{
logger.warning("Ignored call to setRedirect(false), because this "
+ "should only be set to true by the NavigationHandler. "
+ "No one else should change it.");
}
}
}
/**
* Take a value from the requestMap, or if it does not exist from the
* execute FlashMap, and put it on the render FlashMap, so it is visible on
* the next request.
*/
@Override
public void keep(String key)
{
_checkFlashScopeDisabled();
FacesContext facesContext = FacesContext.getCurrentInstance();
Map requestMap = facesContext.getExternalContext().getRequestMap();
Object value = requestMap.get(key);
// if the key does not exist in the requestMap,
// try to get it from the execute FlashMap
if (value == null)
{
Map executeMap = _getExecuteFlashMap(facesContext);
// Null-check, because in the GET request of a POST-REDIRECT-GET
// pattern there is no execute map
if (executeMap != null)
{
value = executeMap.get(key);
// Store it on request map so we can get it later. For example,
// this is used by org.apache.myfaces.el.FlashELResolver to return
// the value that has been promoted.
requestMap.put(key, value);
}
}
// put it in the render FlashMap
_getRenderFlashMap(facesContext).put(key, value);
}
/**
* This is just an alias for the request scope map.
*/
@Override
public void putNow(String key, Object value)
{
_checkFlashScopeDisabled();
FacesContext.getCurrentInstance().getExternalContext()
.getRequestMap().put(key, value);
}
/**
* Returns the value of a previous call to setKeepMessages() from this
* request. If there was no call yet, false is returned.
*/
@Override
public boolean isKeepMessages()
{
FacesContext facesContext = FacesContext.getCurrentInstance();
ExternalContext externalContext = facesContext.getExternalContext();
Map requestMap = externalContext.getRequestMap();
Boolean keepMessages = (Boolean) requestMap.get(FLASH_KEEP_MESSAGES);
return (keepMessages == null ? Boolean.FALSE : keepMessages);
}
/**
* If this property is true, the messages should be kept for the next
* request, no matter if it is a normal postback case or a POST-
* REDIRECT-GET case.
*
* Note that we don't have to store this value for the next request
* (like setRedirect()), because we will know if it was true on the
* next request, if we can find any stored messages in the FlashMap.
* (also see _saveMessages() and _restoreMessages()).
*/
@Override
public void setKeepMessages(boolean keepMessages)
{
FacesContext facesContext = FacesContext.getCurrentInstance();
ExternalContext externalContext = facesContext.getExternalContext();
Map requestMap = externalContext.getRequestMap();
requestMap.put(FLASH_KEEP_MESSAGES, keepMessages);
}
// ~ Methods from Map interface -------------------------------------------
// NOTE that all these methods do not necessarily delegate to the same Map,
// because we differentiate between reading and writing operations.
public void clear()
{
_checkFlashScopeDisabled();
_getFlashMapForWriting().clear();
}
public boolean containsKey(Object key)
{
_checkFlashScopeDisabled();
return _getFlashMapForReading().containsKey(key);
}
public boolean containsValue(Object value)
{
_checkFlashScopeDisabled();
return _getFlashMapForReading().containsValue(value);
}
public Set> entrySet()
{
_checkFlashScopeDisabled();
return _getFlashMapForReading().entrySet();
}
public Object get(Object key)
{
_checkFlashScopeDisabled();
if (key == null)
{
return null;
}
if ("keepMessages".equals(key))
{
return isKeepMessages();
}
else if ("redirect".equals(key))
{
return isRedirect();
}
return _getFlashMapForReading().get(key);
}
public boolean isEmpty()
{
_checkFlashScopeDisabled();
return _getFlashMapForReading().isEmpty();
}
public Set keySet()
{
_checkFlashScopeDisabled();
return _getFlashMapForReading().keySet();
}
public Object put(String key, Object value)
{
_checkFlashScopeDisabled();
if (key == null)
{
return null;
}
if ("keepMessages".equals(key))
{
Boolean booleanValue = _convertToBoolean(value);
this.setKeepMessages(booleanValue);
return booleanValue;
}
else if ("redirect".equals(key))
{
Boolean booleanValue = _convertToBoolean(value);
this.setRedirect(booleanValue);
return booleanValue;
}
else
{
return _getFlashMapForWriting().put(key, value);
}
}
public void putAll(Map m)
{
_checkFlashScopeDisabled();
_getFlashMapForWriting().putAll(m);
}
public Object remove(Object key)
{
_checkFlashScopeDisabled();
return _getFlashMapForWriting().remove(key);
}
public int size()
{
_checkFlashScopeDisabled();
return _getFlashMapForReading().size();
}
public Collection