Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.sun.faces.context.flash;
import static com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter.EnableDistributable;
import static com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter.ForceAlwaysWriteFlashCookie;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIViewRoot;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.context.Flash;
import javax.faces.event.PhaseId;
import javax.faces.event.PostKeepFlashValueEvent;
import javax.faces.event.PostPutFlashValueEvent;
import javax.faces.event.PreClearFlashEvent;
import javax.faces.event.PreRemoveFlashValueEvent;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import com.sun.faces.config.WebConfiguration;
import com.sun.faces.config.WebConfiguration.WebContextInitParameter;
import com.sun.faces.facelets.tag.ui.UIDebug;
import com.sun.faces.util.ByteArrayGuardAESCTR;
import com.sun.faces.util.FacesLogger;
/**
*
How this implementation works
*
This class is an application singleton. It has one ivar,
* innerMap. Entries are added to and removed from this map as needed
* according to how the flash scope is defined in the spec. This
* implementation never touches the session, nor does it cause the
* session to be created.
*
Most of the hairy logic is encapsulated with in the inner class
* PreviousNextFlashInfoManager. An instance of this class is
* obtained by calling one of the variants of getCurrentFlashManager().
* When the instance is no longer needed for this request, call
* releaseCurrentFlashManager().
*
Two very important methods are getPhaseMapForWriting() and
* getPhaseMapForReading(). These methods are the basis for the
* Map implementation methods. Methods that need to write to the map
* use getPhaseMapForWriting(), those that need to read use
* getPhaseMapForReading(). These methods allow for the laziness that
* allows us to only incur a cost when the flash is actually written
* to.
*
The operation of this class is intimately tied to the request
* processing lifecycle. Let's break down every run thru the request
* processing lifecycle into two parts called "previous" and "next". We
* use the names "previous" and "next" to indicate the persistence and
* timing of the data that is stored in the flash. Consider two runs
* through the requset processing lifecle: N and N+1. On request N,
* there is no "previous" request. On Request N, any writes to the
* flash that happen during RENDER RESPONSE go to the "next" flash map.
* This means they are available for the ENTIRE run though the request
* processing lifecycle on request N+1. Any entries put into the "next"
* flash map on request N will be expired at the end of request N+1.
* Now, when we get into request N+1 what was considered "next" on
* request N, is now called "previous" from the perspective of request
* N+1. Any reads from the flash during request N+1 come from the
* "previous" map. Any writes to the flash before RENDER RESPONSE go to
* the "previous" map. Any writes to the flash during RENDER RESPNOSE
* go to the "next" map.
*/
public class ELFlash extends Flash {
//
/**
*
Keys in this map are the string version of sequence numbers
* obtained via calls to {@link #getNewSequenceNumber}. Values are
* the actual Map instances that back the actual Map methods on this
* class. All writes to and reads from this map are done by the
* {@link PreviousNextFlashInfoManager} inner class.
*
*/
private Map> flashInnerMap = null;
private final AtomicLong sequenceNumber = new AtomicLong(0);
private int numberOfConcurentFlashUsers = Integer.
parseInt(WebContextInitParameter.NumberOfConcurrentFlashUsers.getDefaultValue());
private long numberOfFlashesBetweenFlashReapings = Long.
parseLong(WebContextInitParameter.NumberOfFlashesBetweenFlashReapings.getDefaultValue());
private final boolean distributable;
private ByteArrayGuardAESCTR guard;
//
//
private static final String ELEMENT_TYPE_MISMATCH = "element-type-mismatch";
private static final Logger LOGGER = FacesLogger.FLASH.getLogger();
/**
*
These constants are referenced from other source files in this
* package. This one is a disambiguator prefix.
*/
static final String PREFIX = "csfcf";
/**
*
This constant is used as the key in the application map that
* stores the singleton ELFlash instance.
This constant is used as the name of the cookie sent to the
* client. The cookie is used to allow the flash scope to
* be used to support POST REDIRECT GET navigation.
This constant is used as the key the request map used, in the
* FlashELResolver, to convey the name of the property being
* accessed via 'now'.
*/
static final String FLASH_NOW_REQUEST_KEY = FLASH_ATTRIBUTE_NAME + "n";
private enum CONSTANTS {
/**
* The key in the FacesContext attributes map (hereafter
* referred to as contextMap) for the request scoped {@link
* PreviousNextFlashInfoManager}.
*/
RequestFlashManager,
/**
* At the beginning of every phase, we save the value of the
* facesContext.getResponseComplete() into the contextMap under
* this key. We check this value after the phase to see if this
* is the phase where the user called responseComplete(). This
* is important to cover cases when the user does some funny
* lifecycle stuff.
*/
SavedResponseCompleteFlagValue,
/**
* This is used as the key in the flash itself to store the messages
* if they are being tracked.
*/
FacesMessageAttributeName,
/**
* This is used as the key in the flash itself to track whether or not
* messages are being saved across request/response boundaries.
*/
KeepAllMessagesAttributeName,
/**
* This key is used in the contextMap to indicate that the next
* get should be treated as a keep.
*
*/
KeepFlagAttributeName,
/**
* This key is used in the contextMap to prevent setting the cookie
* twice.
*/
DidWriteCookieAttributeName,
/**
* Force setMaxAge(0)
*/
ForceSetMaxAgeZero,
}
//
//
/** Creates a new instance of ELFlash */
private ELFlash(ExternalContext extContext) {
flashInnerMap = new ConcurrentHashMap<>();
WebConfiguration config = WebConfiguration.getInstance(extContext);
String value;
try {
value = config.getOptionValue(WebContextInitParameter.NumberOfConcurrentFlashUsers);
numberOfConcurentFlashUsers = Integer.parseInt(value);
} catch (NumberFormatException nfe) {
if (LOGGER.isLoggable(Level.WARNING)) {
LOGGER.log(Level.WARNING, "Unable to set number of concurrent flash users. Defaulting to {0}", numberOfConcurentFlashUsers);
}
}
try {
value = config.getOptionValue(WebContextInitParameter.NumberOfFlashesBetweenFlashReapings);
numberOfFlashesBetweenFlashReapings = Long.parseLong(value);
} catch (NumberFormatException nfe) {
if (LOGGER.isLoggable(Level.WARNING)) {
LOGGER.log(Level.WARNING, "Unable to set number flashes between flash repaings. Defaulting to {0}", numberOfFlashesBetweenFlashReapings);
}
}
distributable = config.isOptionEnabled(EnableDistributable);
guard = new ByteArrayGuardAESCTR();
}
/**
*
Returns the flash Map for this application. This is
* a convenience method that calls
* FacesContext.getCurrentInstance() and then calls the
* overloaded getFlash() that takes a
* FacesContext with it.
*
* @return The flash Map for this session.
*/
public static Map getFlash() {
FacesContext context = FacesContext.getCurrentInstance();
return getFlash(context.getExternalContext(), true);
}
/**
*
* @param extContext the ExternalContext for this request.
*
* @param create true to create a new instance for this request if
* necessary; false to return null if there's no
* instance in the current session.
*
* @return The flash Map for this session.
*/
static ELFlash getFlash(ExternalContext extContext, boolean create) {
Map appMap = extContext.getApplicationMap();
ELFlash flash = (ELFlash)
appMap.get(FLASH_ATTRIBUTE_NAME);
if (null == flash && create) {
synchronized (extContext.getContext()) {
if (null == (flash = (ELFlash)
appMap.get(FLASH_ATTRIBUTE_NAME))) {
flash = new ELFlash(extContext);
appMap.put(FLASH_ATTRIBUTE_NAME, flash);
}
}
}
/*
* If we are in a clustered environment and a session is active, store
* a helper to ensure our innerMap gets successfully replicated.
*/
if (appMap.get(EnableDistributable.getQualifiedName()) != null) {
synchronized (extContext.getContext()) {
if (extContext.getSession(false) != null) {
SessionHelper sessionHelper = SessionHelper.getInstance(extContext);
if (sessionHelper == null) {
sessionHelper = new SessionHelper();
}
sessionHelper.update(extContext, flash);
}
}
}
return flash;
}
//
//
@Override
public boolean isKeepMessages() {
boolean result = false;
Map phaseMap;
if (null != (phaseMap = loggingGetPhaseMapForReading(false))) {
Object value = phaseMap.get(CONSTANTS.KeepAllMessagesAttributeName.toString());
result = (null != value) ? (Boolean) value : false;
}
return result;
}
@Override
public void setKeepMessages(boolean newValue) {
loggingGetPhaseMapForWriting(false).put(CONSTANTS.KeepAllMessagesAttributeName.toString(),
Boolean.valueOf(newValue));
}
@Override
public boolean isRedirect() {
boolean result = false;
FacesContext context = FacesContext.getCurrentInstance();
Map
//
@SuppressWarnings(ELEMENT_TYPE_MISMATCH)
@Override
public Object get(Object key) {
Object result = null;
FacesContext context = FacesContext.getCurrentInstance();
if (null != key) {
if (key.equals("keepMessages")) {
result = this.isKeepMessages();
} else if (key.equals("redirect")) {
result = this.isRedirect();
} else {
if (isKeepFlagSet(context)) {
result = getPhaseMapForReading().get(key);
keep(key.toString());
clearKeepFlag(context);
return result;
}
}
}
if (null == result) {
result = getPhaseMapForReading().get(key);
}
if (distributable && context.getExternalContext().getSession(false) != null) {
SessionHelper sessionHelper =
SessionHelper.getInstance(context.getExternalContext());
assert(null != sessionHelper);
sessionHelper.update(context.getExternalContext(), this);
}
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.log(Level.FINEST, "get({0}) = {1}", new Object [] { key, result});
}
return result;
}
@Override
public Object put(String key, Object value) {
Boolean b = null;
Object result = null;
boolean wasSpecialPut = false;
if (null != key) {
if (key.equals("keepMessages")) {
this.setKeepMessages(b = Boolean.parseBoolean((String) value));
wasSpecialPut = true;
}
if (key.equals("redirect")) {
this.setRedirect(b = Boolean.parseBoolean((String) value));
wasSpecialPut = true;
}
}
FacesContext context = FacesContext.getCurrentInstance();
if (!wasSpecialPut) {
result = (null == b) ? getPhaseMapForWriting().put(key, value) : b;
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.log(Level.FINEST, "put({0},{1})", new Object [] { key, value});
}
context.getApplication().publishEvent(context, PostPutFlashValueEvent.class, key);
}
if (distributable && context.getExternalContext().getSession(false) != null) {
SessionHelper sessionHelper =
SessionHelper.getInstance(context.getExternalContext());
if (sessionHelper != null) {
sessionHelper.update(context.getExternalContext(), this);
}
}
return result;
}
@SuppressWarnings(ELEMENT_TYPE_MISMATCH)
@Override
public Object remove(Object key) {
Object result = null;
FacesContext context = FacesContext.getCurrentInstance();
context.getApplication().publishEvent(context, PreRemoveFlashValueEvent.class, key);
result = getPhaseMapForWriting().remove(key);
return result;
}
@SuppressWarnings(ELEMENT_TYPE_MISMATCH)
@Override
public boolean containsKey(Object key) {
boolean result = false;
result = getPhaseMapForReading().containsKey(key);
return result;
}
@Override
public boolean containsValue(Object value) {
boolean result = false;
result = getPhaseMapForReading().containsValue(value);
return result;
}
@Override
public void putAll(Map extends String, ?> t) {
getPhaseMapForWriting().putAll(t);
}
@Override
public Collection
//
@Override
public void keep(String key) {
FacesContext context = FacesContext.getCurrentInstance();
Map requestMap = context.getExternalContext().getRequestMap();
Map contextMap = context.getAttributes();
PreviousNextFlashInfoManager flashManager;
if (null != (flashManager = getCurrentFlashManager(contextMap, true))) {
Object toKeep;
if (null == (toKeep = requestMap.remove(key))) {
FlashInfo flashInfo = null;
if (null != (flashInfo = flashManager.getPreviousRequestFlashInfo())) {
toKeep = flashInfo.getFlashMap().get(key);
}
}
if (null != toKeep) {
getPhaseMapForWriting().put(key, toKeep);
context.getApplication().publishEvent(context, PostKeepFlashValueEvent.class, key);
}
}
}
@Override
public void putNow(String key, Object value) {
FacesContext context = FacesContext.getCurrentInstance();
Map contextMap = context.getAttributes();
PreviousNextFlashInfoManager flashManager;
if (null != (flashManager = getCurrentFlashManager(contextMap, true))) {
FlashInfo flashInfo = null;
if (null != (flashInfo = flashManager.getPreviousRequestFlashInfo())) {
flashInfo.getFlashMap().put(key, value);
}
}
}
@Override
public void doPrePhaseActions(FacesContext context) {
PhaseId currentPhase = context.getCurrentPhaseId();
Map contextMap = context.getAttributes();
contextMap.put(CONSTANTS.SavedResponseCompleteFlagValue,
context.getResponseComplete());
Cookie cookie = null;
if (currentPhase.equals(PhaseId.RESTORE_VIEW)) {
if (null != (cookie = getCookie(context.getExternalContext()))) {
getCurrentFlashManager(context, contextMap, cookie);
}
if (this.isKeepMessages()) {
this.restoreAllMessages(context);
}
} else if (currentPhase.equals(PhaseId.RENDER_RESPONSE) &&
contextMap.containsKey(ForceAlwaysWriteFlashCookie) &&
(Boolean) contextMap.get(ForceAlwaysWriteFlashCookie)) {
PreviousNextFlashInfoManager flashManager = getCurrentFlashManager(contextMap, true);
cookie = flashManager.encode();
if (null != cookie) {
setCookie(context, flashManager, cookie, true);
} else {
if (LOGGER.isLoggable(Level.WARNING)) {
LOGGER.log(Level.WARNING,
"jsf.externalcontext.flash.force.write.cookie.failed");
}
}
}
}
@Override
public void doPostPhaseActions(FacesContext context) {
if (context.getAttributes().containsKey(ACT_AS_DO_LAST_PHASE_ACTIONS)) {
Boolean outgoingResponseIsRedirect =
(Boolean) context.getAttributes().get(ACT_AS_DO_LAST_PHASE_ACTIONS);
doLastPhaseActions(context, outgoingResponseIsRedirect);
return;
}
PhaseId currentPhase = context.getCurrentPhaseId();
Map contextMap = context.getAttributes();
boolean
responseCompleteJustSetTrue = responseCompleteWasJustSetTrue(context, contextMap),
lastPhaseForThisRequest = responseCompleteJustSetTrue ||
currentPhase == PhaseId.RENDER_RESPONSE;
if (lastPhaseForThisRequest) {
doLastPhaseActions(context, false);
}
}
public static final String ACT_AS_DO_LAST_PHASE_ACTIONS =
ELFlash.class.getPackage().getName() + ".ACT_AS_DO_LAST_PHASE_ACTIONS";
/**
*
This is the most magic of methods. There are several scenarios
* in which this method can be called, but the first time it is
* called for a request it takes action, while on subsequent times
* it returns without taking action. This is due to the call to
* {@link #releaseCurrentFlashManager}. After this call, any calls
* to {@link #getCurrentFlashManager} will return null.
*
Scenario 1: normal request ending. This will be called after
* the RENDER_RESPONSE phase executes. outgoingResponseIsRedirect will be false.
*
Scenario 2: navigationHandler asks extContext for redirect.
* In this case, extContext calls this method directly,
* outgoingResponseIsRedirect will be true.
*
Scenario 3: extContext.flushBuffer(): As far as I can tell,
* this is only called in the JSP case, but it's good to call it
* from there anyway, because we need to write our cookie before the
* response is committed. outgoingResponseIsRedirect is false.
*
Scenario 4: after rendering the response in JSP, but before
* the buffer is flushed. In the JSP case, I've found this necessary
* because the call to extContext.flushBuffer() is too late, the
* response has already been committed by that
* point. outgoingResponseIsRedirect is false.
*/
public void doLastPhaseActions(FacesContext context, boolean outgoingResponseIsRedirect) {
Map contextMap = context.getAttributes();
PreviousNextFlashInfoManager flashManager = getCurrentFlashManager(contextMap, false);
if (null == flashManager) {
return;
}
if (this.isKeepMessages()) {
this.saveAllMessages(context);
}
releaseCurrentFlashManager(contextMap);
// What we do in this if-else statement has consequences for
// PreviousNextFlashInfoManager.decode().
if (outgoingResponseIsRedirect) {
FlashInfo previousRequestFlashInfo = flashManager.getPreviousRequestFlashInfo();
// Next two methods are VITALLY IMPORTANT!
previousRequestFlashInfo.setIsRedirect(true);
flashManager.expireNext_MovePreviousToNext();
} else {
FlashInfo flashInfo = flashManager.getPreviousRequestFlashInfo();
if (null != flashInfo && flashInfo.getLifetimeMarker() ==
LifetimeMarker.SecondTimeThru) {
flashManager.expirePrevious();
}
}
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.log(Level.FINEST, "---------------------------------------");
}
setCookie(context, flashManager, flashManager.encode(), false);
}
//
//
void setFlashInnerMap(Map> flashInnerMap) {
this.flashInnerMap = flashInnerMap;
}
Map> getFlashInnerMap() {
return flashInnerMap;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("[\n");
for (Map.Entry entry : this.entrySet()) {
builder.append("{").append(entry.getKey()).append(", ").append(entry.getValue()).append("}\n");
}
builder.append("]\n");
return builder.toString();
}
private void maybeWriteCookie(FacesContext context,
PreviousNextFlashInfoManager flashManager) {
FlashInfo flashInfo = flashManager.getPreviousRequestFlashInfo();
if (null != flashInfo && flashInfo.getLifetimeMarker() ==
LifetimeMarker.SecondTimeThru) {
PreviousNextFlashInfoManager copiedFlashManager =
flashManager.copyWithoutInnerMap();
copiedFlashManager.expirePrevious();
setCookie(context, flashManager,
copiedFlashManager.encode(), false);
}
}
static void setKeepFlag(FacesContext context) {
context.getAttributes().put(CONSTANTS.KeepFlagAttributeName, Boolean.TRUE);
}
void clearKeepFlag(FacesContext context) {
context.getAttributes().remove(CONSTANTS.KeepFlagAttributeName);
}
boolean isKeepFlagSet(FacesContext context) {
return Boolean.TRUE ==
context.getAttributes().get(CONSTANTS.KeepFlagAttributeName);
}
private long getNewSequenceNumber() {
long result = sequenceNumber.incrementAndGet();
if (0 == result % numberOfFlashesBetweenFlashReapings) {
reapFlashes();
}
if (result == Long.MAX_VALUE) {
result = 1;
sequenceNumber.set(1);
}
return result;
}
private void reapFlashes() {
if (flashInnerMap.size() < numberOfConcurentFlashUsers) {
return;
}
Set keys = flashInnerMap.keySet();
long
sequenceNumberToTest,
currentSequenceNumber = sequenceNumber.get();
Map curFlash;
for (String cur : keys) {
sequenceNumberToTest = Long.parseLong(cur);
if (numberOfConcurentFlashUsers < currentSequenceNumber - sequenceNumberToTest) {
if (null != (curFlash = flashInnerMap.get(cur))) {
curFlash.clear();
}
flashInnerMap.remove(cur);
}
}
if (distributable && FacesContext.getCurrentInstance().getExternalContext().getSession(false) != null) {
ExternalContext extContext = FacesContext.getCurrentInstance().getExternalContext();
SessionHelper sessionHelper = SessionHelper.getInstance(extContext);
if (null != sessionHelper) {
sessionHelper.remove(extContext);
sessionHelper = new SessionHelper();
sessionHelper.update(extContext, this);
}
}
}
private boolean responseCompleteWasJustSetTrue(FacesContext context,
Map contextMap) {
boolean result = false;
// If it was false, but it's now true, return true
result = (Boolean.FALSE == contextMap.get(CONSTANTS.SavedResponseCompleteFlagValue) &&
context.getResponseComplete());
return result;
}
private static String getLogPrefix(FacesContext context) {
StringBuilder result = new StringBuilder();
ExternalContext extContext = context.getExternalContext();
Object request = extContext.getRequest();
if (request instanceof HttpServletRequest) {
result.append(((HttpServletRequest)request).getMethod()).append(" ");
}
UIViewRoot root = context.getViewRoot();
if (null != root) {
String viewId = root.getViewId();
if (null != viewId) {
result.append(viewId).append(" ");
}
}
return result.toString();
}
private Map loggingGetPhaseMapForWriting(boolean loggingEnabled) {
FacesContext context = FacesContext.getCurrentInstance();
Map result = null;
PhaseId currentPhase = context.getCurrentPhaseId();
Map contextMap = context.getAttributes();
PreviousNextFlashInfoManager flashManager;
if (null != (flashManager = getCurrentFlashManager(contextMap, true))) {
FlashInfo flashInfo;
boolean isDebugLog = loggingEnabled && LOGGER.isLoggable(Level.FINEST);
if (currentPhase.getOrdinal() < PhaseId.RENDER_RESPONSE.getOrdinal()) {
flashInfo = flashManager.getPreviousRequestFlashInfo();
if (isDebugLog) {
LOGGER.log(Level.FINEST, "{0}previous[{1}]",
new Object[]{getLogPrefix(context),
flashInfo.getSequenceNumber()});
}
} else {
flashInfo = flashManager.getNextRequestFlashInfo(this, true);
if (isDebugLog) {
LOGGER.log(Level.FINEST, "{0}next[{1}]",
new Object[]{getLogPrefix(context),
flashInfo.getSequenceNumber()});
}
maybeWriteCookie(context, flashManager);
}
result = flashInfo.getFlashMap();
}
return result;
}
/**
*
If the current phase is earlier than RENDER_RESPONSE, return
* the map for the "previous" request. Otherwise, return the map
* for the "next" request. Note that we use
* getCurrentFlashManager(contextMap,true). This is because if this
* method is being called, we know we actually need the map, so we
* have to ensure the underlying data structure is present before
* trying to access it.
Always return the map for the "previous" request. Note that
* we use getCurrentFlashManager(contextMap,false). This is because
* if this method is being called, and there is pre-existing data in
* the flash from a previous write, then the
* PreviousNextFlashInfoManager will already have been created. If
* there is not pre-existing data, we don't create the
* PreviousNextFlashInfoManager, and therefore just return the empty
* map.
*/
private Map getPhaseMapForReading() {
return loggingGetPhaseMapForReading(true);
}
void saveAllMessages(FacesContext context) {
// take no action on the GET that comes after a REDIRECT
Map contextMap = context.getAttributes();
PreviousNextFlashInfoManager flashManager;
if (null == (flashManager = getCurrentFlashManager(contextMap, true))) {
return;
}
if (flashManager.getPreviousRequestFlashInfo().isIsRedirect()) {
return;
}
Iterator messageClientIds = context.getClientIdsWithMessages();
List facesMessages;
Map> allFacesMessages = null;
Iterator messageIter;
String curMessageId;
// Save all the FacesMessages into a Map, which we store in the flash.
// Step 1, go through the FacesMessage instances for each clientId
// in the messageClientIds list.
while (messageClientIds.hasNext()) {
curMessageId = messageClientIds.next();
// Get the messages for this clientId
messageIter = context.getMessages(curMessageId);
facesMessages = new ArrayList<>();
while (messageIter.hasNext()) {
facesMessages.add(messageIter.next());
}
// Add the list to the map
if (null == allFacesMessages) {
allFacesMessages = new HashMap<>();
}
allFacesMessages.put(curMessageId, facesMessages);
}
facesMessages = null;
// Step 2, go through the FacesMessages that do not have a client
// id associated with them.
messageIter = context.getMessages(null);
// Make sure to overwrite the previous facesMessages list
facesMessages = new ArrayList<>();
while (messageIter.hasNext()) {
facesMessages.add(messageIter.next());
}
if (null != facesMessages) {
// Add the list to the map
if (null == allFacesMessages) {
allFacesMessages = new HashMap<>();
}
allFacesMessages.put(null, facesMessages);
}
getPhaseMapForWriting().put(CONSTANTS.FacesMessageAttributeName.toString(),
allFacesMessages);
}
@SuppressWarnings(ELEMENT_TYPE_MISMATCH)
void restoreAllMessages(FacesContext context) {
Map> allFacesMessages;
Map phaseMap = getPhaseMapForReading();
List facesMessages;
if (null != (allFacesMessages = (Map>)
phaseMap.get(CONSTANTS.FacesMessageAttributeName.toString()))) {
for (Map.Entry> cur : allFacesMessages.entrySet()) {
if (null != (facesMessages = allFacesMessages.get(cur.getKey()))) {
for (FacesMessage curMessage : facesMessages) {
context.addMessage(cur.getKey(), curMessage);
}
}
}
phaseMap.remove(CONSTANTS.FacesMessageAttributeName.toString());
}
}
/**
*
Return the cookie that came from the browser, if any.
*/
private Cookie getCookie(ExternalContext extContext) {
Cookie result = null;
result = (Cookie) extContext.getRequestCookieMap().get(FLASH_COOKIE_NAME);
return result;
}
/**
*
Set the cookie iff the response was not yet committed. If the response
* was committed, log a warning.
*/
private void setCookie(FacesContext context,
PreviousNextFlashInfoManager flashManager,
Cookie toSet, boolean forceWrite) {
Map contextMap = context.getAttributes();
ExternalContext extContext = context.getExternalContext();
if (contextMap.containsKey(CONSTANTS.DidWriteCookieAttributeName)) {
return;
}
FlashInfo
nextFlash = flashManager.getNextRequestFlashInfo(),
prevFlash = flashManager.getPreviousRequestFlashInfo();
if (context.getAttributes().containsKey(CONSTANTS.ForceSetMaxAgeZero)) {
removeCookie(extContext, toSet);
return;
}
// Don't try to write the cookie unless there is data in the flash.
if (forceWrite || (null != nextFlash && !nextFlash.getFlashMap().isEmpty()) ||
(null != prevFlash && !prevFlash.getFlashMap().isEmpty())) {
if (extContext.isResponseCommitted()) {
if (LOGGER.isLoggable(Level.WARNING)) {
LOGGER.log(Level.WARNING,
"jsf.externalcontext.flash.response.already.committed");
}
} else {
Map properties = new HashMap();
Object val;
if (null != (val = toSet.getComment())) {
properties.put("comment", val);
}
if (null != (val = toSet.getDomain())) {
properties.put("domain", val);
}
if (null != (val = toSet.getMaxAge())) {
properties.put("maxAge", val);
}
if (extContext.isSecure()) {
properties.put("secure", Boolean.TRUE);
} else if (null != (val = toSet.getSecure())) {
properties.put("secure", val);
}
if (null != (val = toSet.getPath())) {
properties.put("path", val);
}
properties.put("httpOnly", Boolean.TRUE);
extContext.addResponseCookie(toSet.getName(), toSet.getValue(),
!properties.isEmpty() ? properties : null);
properties = null;
}
contextMap.put(CONSTANTS.DidWriteCookieAttributeName, Boolean.TRUE);
} else {
removeCookie(extContext, toSet);
}
}
private void removeCookie(ExternalContext extContext, Cookie toRemove) {
if (extContext.isResponseCommitted()) {
return;
}
Map properties = new HashMap();
Object val;
toRemove.setMaxAge(0);
if (null != (val = toRemove.getComment())) {
properties.put("comment", val);
}
if (null != (val = toRemove.getDomain())) {
properties.put("domain", val);
}
if (null != (val = toRemove.getMaxAge())) {
properties.put("maxAge", val);
}
if (extContext.isSecure()) {
properties.put("secure", Boolean.TRUE);
} else if (null != (val = toRemove.getSecure())) {
properties.put("secure", val);
}
if (null != (val = toRemove.getPath())) {
properties.put("path", val);
}
properties.put("httpOnly", Boolean.TRUE);
extContext.addResponseCookie(toRemove.getName(), toRemove.getValue(),
!properties.isEmpty() ? properties : null);
properties = null;
}
//
//
private enum LifetimeMarker {
// these must be unique
FirstTimeThru("f"),
SecondTimeThru("s"),
IsRedirect("r"),
IsNormal("n");
private static char FIRST_TIME_THRU = 'f';
private static char SECOND_TIME_THRU = 's';
private static char IS_REDIRECT = 'r';
private static char IS_NORMAL = 'n';
private String name;
private LifetimeMarker(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
public char encode() {
return name.charAt(0);
}
public static LifetimeMarker decode(char c) {
LifetimeMarker result = FirstTimeThru;
if (FIRST_TIME_THRU == c) {
result = FirstTimeThru;
} else if (SECOND_TIME_THRU == c) {
result = SecondTimeThru;
} else if (IS_REDIRECT == c) {
result = IsRedirect;
} else if (IS_NORMAL == c) {
result = IsNormal;
} else {
throw new IllegalStateException("class invariant failed: invalid lifetime marker");
}
return result;
}
}
private void releaseCurrentFlashManager(Map contextMap) {
contextMap.remove(CONSTANTS.RequestFlashManager);
}
/**
*
Called when you need to get access to the flashManager. If
* argument create is true, and no instance of the FlashManager
* exists for this request, create it and store it in the
* contextMap.
*/
private PreviousNextFlashInfoManager getCurrentFlashManager(Map contextMap,
boolean create) {
PreviousNextFlashInfoManager result = (PreviousNextFlashInfoManager)
contextMap.get(CONSTANTS.RequestFlashManager);
if (null == result && create) {
result = new PreviousNextFlashInfoManager(guard, flashInnerMap);
result.initializeBaseCase(this);
contextMap.put(CONSTANTS.RequestFlashManager, result);
}
return result;
}
/**
*
Called on the preRestoreView phase if the browser sent us a
* cookie. If no instance of the FlashManager exists for this
* request, create it and store it in the contextMap.
*/
private PreviousNextFlashInfoManager getCurrentFlashManager(FacesContext context,
Map contextMap,
Cookie cookie) {
PreviousNextFlashInfoManager result = (PreviousNextFlashInfoManager)
contextMap.get(CONSTANTS.RequestFlashManager);
if (null == result) {
result = new PreviousNextFlashInfoManager(guard, flashInnerMap);
try {
result.decode(context, this, cookie);
contextMap.put(CONSTANTS.RequestFlashManager, result);
} catch (InvalidKeyException ike) {
contextMap.put(CONSTANTS.ForceSetMaxAgeZero, Boolean.TRUE);
if (LOGGER.isLoggable(Level.SEVERE)) {
result = getCurrentFlashManager(contextMap, true);
LOGGER.log(Level.SEVERE,
"jsf.externalcontext.flash.bad.cookie",
new Object [] { ike.getMessage() });
}
}
}
return result;
}
/**
*
On any given request, there are actually two maps behind the
* flash. Which one is actually used on a given Map method depends
* on the current lifecycle phase at the time the method is invoked.
* There is a "next" map, and a "previous" map. This class manages
* the complexities of dealing with these two maps, and does so by
* relying on another inner class, FlashInfo.
*
The "next" map is used in only one case, which happens to be a
* VERY common case: write operations to the flash that happen
* during render response.
*
The "previous" map is used for write operations that happen
* before render response, and for all read operations.
*
This class knows how to "decode" its state from an incoming
* cookie, written by a previous call to "encode".
*
See the docs for FlashInfo for more information.
*/
private static final class PreviousNextFlashInfoManager {
private FlashInfo previousRequestFlashInfo;
private FlashInfo nextRequestFlashInfo;
private boolean incomingCookieCameFromRedirect = false;
private Map> innerMap;
private ByteArrayGuardAESCTR guard;
private PreviousNextFlashInfoManager(ByteArrayGuardAESCTR guard) {
this.guard = guard;
}
private PreviousNextFlashInfoManager(ByteArrayGuardAESCTR guard, Map> innerMap) {
this.guard = guard;
this.innerMap = innerMap;
}
protected PreviousNextFlashInfoManager copyWithoutInnerMap() {
PreviousNextFlashInfoManager result = new PreviousNextFlashInfoManager(guard);
result.innerMap = Collections.emptyMap();
if (null != previousRequestFlashInfo) {
result.previousRequestFlashInfo = this.previousRequestFlashInfo.copyWithoutInnerMap();
}
if (null != nextRequestFlashInfo) {
result.nextRequestFlashInfo = this.nextRequestFlashInfo.copyWithoutInnerMap();
}
result.incomingCookieCameFromRedirect = this.incomingCookieCameFromRedirect;
return result;
}
@Override
public String toString() {
String result = null;
result = "previousRequestSequenceNumber: " +
((null != previousRequestFlashInfo) ? previousRequestFlashInfo.getSequenceNumber() : "null") +
" nextRequestSequenceNumber: " +
((null != nextRequestFlashInfo) ? nextRequestFlashInfo.getSequenceNumber() : "null");
return result;
}
void initializeBaseCase(ELFlash flash) {
Map flashMap = null;
previousRequestFlashInfo = new FlashInfo(flash.getNewSequenceNumber(),
LifetimeMarker.FirstTimeThru, false);
innerMap.put(previousRequestFlashInfo.getSequenceNumber() + "",
flashMap = new HashMap<>());
previousRequestFlashInfo.setFlashMap(flashMap);
nextRequestFlashInfo = new FlashInfo(flash.getNewSequenceNumber(),
LifetimeMarker.FirstTimeThru, false);
innerMap.put(nextRequestFlashInfo.getSequenceNumber() + "",
flashMap = new HashMap<>());
nextRequestFlashInfo.setFlashMap(flashMap);
}
void expirePrevious() {
// expire previous
if (null != previousRequestFlashInfo) {
Map flashMap;
// clear the old map
if (null != (flashMap = previousRequestFlashInfo.getFlashMap())) {
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.log(Level.FINEST, "{0} expire previous[{1}]",
new Object[]{getLogPrefix(FacesContext.getCurrentInstance()),
previousRequestFlashInfo.getSequenceNumber()});
}
FacesContext context = FacesContext.getCurrentInstance();
context.getApplication().publishEvent(context, PreClearFlashEvent.class,
flashMap);
flashMap.clear();
}
// remove it from the flash
innerMap.remove(previousRequestFlashInfo.getSequenceNumber() + "");
previousRequestFlashInfo = null;
}
}
void expireNext_MovePreviousToNext() {
if (null != nextRequestFlashInfo) {
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.log(Level.FINEST, "{0} expire next[{1}], move previous to next",
new Object[]{getLogPrefix(FacesContext.getCurrentInstance()),
nextRequestFlashInfo.getSequenceNumber()});
}
Map flashMap = nextRequestFlashInfo.getFlashMap();
FacesContext context = FacesContext.getCurrentInstance();
context.getApplication().publishEvent(context, PreClearFlashEvent.class,
flashMap);
// clear the old map
flashMap.clear();
// remove it from the flash
innerMap.remove(nextRequestFlashInfo.getSequenceNumber() + "");
nextRequestFlashInfo = null;
}
nextRequestFlashInfo = previousRequestFlashInfo;
previousRequestFlashInfo = null;
}
/**
*
Decode the state of the PreviousNextFlashInfoManager from
* a Cookie. This entire method is wrapped in a try-catch block
* to prevent any errors from malformed cookies from polluting
* the system. When any error occurs, the flash is not usable
* for this request, and a nice error message is logged.
*
This method is where the LifetimeMarker is incremented,
* UNLESS the incoming request is the GET after the REDIRECT
* after POST, in which case we don't increment it because the
* system will expire the entries in the doLastPhaseActions.
*
*/
void decode(FacesContext context, ELFlash flash, Cookie cookie) throws InvalidKeyException {
String temp;
String value;
String urlDecodedValue = null;
try {
urlDecodedValue = URLDecoder.decode(cookie.getValue(), "UTF-8");
} catch (UnsupportedEncodingException uee) {
urlDecodedValue = cookie.getValue();
}
value = guard.decrypt(urlDecodedValue);
try {
int i = value.indexOf("_");
// IMPORTANT: what was "next" when the cookie was
// encoded is now "previous". Therefore decode "next" first.
temp = value.substring(0, i++);
if (0 < temp.length()) {
nextRequestFlashInfo = new FlashInfo();
nextRequestFlashInfo.decode(temp);
}
// invariant we must always have something after the _
previousRequestFlashInfo = new FlashInfo();
previousRequestFlashInfo.decode(value.substring(i));
// handle the consequences of action taken on doLastPhaseActions
if (previousRequestFlashInfo.isIsRedirect()) {
this.setIncomingCookieCameFromRedirect(true);
previousRequestFlashInfo.setIsRedirect(false);
} else {
// Don't make the flash older on debug requests
if (!UIDebug.debugRequest(context)) {
previousRequestFlashInfo.setLifetimeMarker(LifetimeMarker.SecondTimeThru);
nextRequestFlashInfo = null;
}
}
Map flashMap;
// If the browser sent a cookie that is valid, but
// doesn't correspond to a map in memory...
if (null == (flashMap = innerMap.get(previousRequestFlashInfo.getSequenceNumber() + ""))) {
// create a new map
previousRequestFlashInfo = new FlashInfo();
previousRequestFlashInfo.setSequenceNumber(flash.getNewSequenceNumber());
previousRequestFlashInfo.setLifetimeMarker(LifetimeMarker.FirstTimeThru);
previousRequestFlashInfo.setIsRedirect(false);
// put it in the flash
innerMap.put(previousRequestFlashInfo.getSequenceNumber() + "",
flashMap = new HashMap<>());
}
previousRequestFlashInfo.setFlashMap(flashMap);
if (null != nextRequestFlashInfo) {
if (null == (flashMap = innerMap.get(nextRequestFlashInfo.getSequenceNumber() + ""))) {
// create a new map
nextRequestFlashInfo = new FlashInfo();
nextRequestFlashInfo.setSequenceNumber(flash.getNewSequenceNumber());
nextRequestFlashInfo.setLifetimeMarker(LifetimeMarker.FirstTimeThru);
nextRequestFlashInfo.setIsRedirect(false);
// put it in the flash
innerMap.put(nextRequestFlashInfo.getSequenceNumber() + "",
flashMap = new HashMap<>());
}
nextRequestFlashInfo.setFlashMap(flashMap);
}
} catch (Throwable t) {
context.getAttributes().put(CONSTANTS.ForceSetMaxAgeZero, Boolean.TRUE);
if (LOGGER.isLoggable(Level.SEVERE)) {
LOGGER.log(Level.SEVERE,
"jsf.externalcontext.flash.bad.cookie",
new Object [] { value });
}
}
}
/**
*
Encode the current state of the
* PreviousNextFlashInfoManager to the cookie.