com.sun.faces.application.NavigationHandlerImpl Maven / Gradle / Ivy
Show all versions of javax.faces Show documentation
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2013 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.dev.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.application;
import com.sun.faces.RIConstants;
import com.sun.faces.config.InitFacesContext;
import com.sun.faces.flow.FlowHandlerImpl;
import com.sun.faces.flow.FlowImpl;
import com.sun.faces.flow.builder.MutableNavigationCase;
import javax.faces.FacesException;
import javax.faces.application.NavigationCase;
import javax.faces.application.ViewHandler;
import javax.faces.component.UIViewRoot;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.context.PartialViewContext;
import java.util.*;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sun.faces.util.MessageUtils;
import com.sun.faces.util.Util;
import com.sun.faces.util.FacesLogger;
import java.util.concurrent.ConcurrentHashMap;
import javax.el.ELContext;
import javax.el.MethodExpression;
import javax.el.ValueExpression;
import javax.faces.application.ConfigurableNavigationHandler;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIViewAction;
import javax.faces.context.Flash;
import javax.faces.flow.FlowCallNode;
import javax.faces.flow.Flow;
import javax.faces.flow.FlowHandler;
import javax.faces.flow.FlowNode;
import javax.faces.flow.MethodCallNode;
import javax.faces.flow.Parameter;
import javax.faces.flow.ReturnNode;
import javax.faces.flow.SwitchCase;
import javax.faces.flow.SwitchNode;
import javax.faces.flow.ViewNode;
/**
* NavigationHandlerImpl is the class that implements
* default navigation handling. Refer to section 7.4.2 of the specification for
* more details.
* PENDING: Make independent of ApplicationAssociate.
*/
public class NavigationHandlerImpl extends ConfigurableNavigationHandler {
// Log instance for this class
private static final Logger logger = FacesLogger.APPLICATION.getLogger();
/**
* Map
containing configured navigation cases.
*/
private volatile Map navigationMaps;
/**
* Flag indicated the current mode.
*/
private boolean development;
private static final Pattern REDIRECT_EQUALS_TRUE = Pattern.compile("(.*)(faces-redirect=true)(.*)");
private static final Pattern INCLUDE_VIEW_PARAMS_EQUALS_TRUE = Pattern.compile("(.*)(includeViewParams=true)(.*)");
// ------------------------------------------------------------ Constructors
/**
* This constructor uses the current ApplicationAssociate
* instance to obtain the navigation mappings used to make
* navigational decisions.
*/
public NavigationHandlerImpl() {
super();
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "Created NavigationHandler instance ");
}
ApplicationAssociate associate = ApplicationAssociate.getInstance(
FacesContext.getCurrentInstance().getExternalContext());
if (associate != null) {
development = associate.isDevModeEnabled();
}
}
// ------------------------------ Methods from ConfigurableNavigationHandler
/**
* @see javax.faces.application.ConfigurableNavigationHandler#getNavigationCase(javax.faces.context.FacesContext, String, String)
*/
@Override
public NavigationCase getNavigationCase(FacesContext context, String fromAction, String outcome) {
return getNavigationCase(context, fromAction, outcome, "");
}
@Override
public NavigationCase getNavigationCase(FacesContext context, String fromAction, String outcome, String toFlowDocumentId) {
Util.notNull("context", context);
Util.notNull("toFlowDocumentId", toFlowDocumentId);
NavigationCase result = null;
CaseStruct caseStruct = getViewId(context, fromAction, outcome, toFlowDocumentId);
if (null != caseStruct) {
result = caseStruct.navCase;
}
return result;
}
/**
* @see javax.faces.application.ConfigurableNavigationHandler#getNavigationCases()
*/
@Override
public Map> getNavigationCases() {
FacesContext context = FacesContext.getCurrentInstance();
Map> result = getNavigationMap(context);
return result;
}
@Override
public void inspectFlow(FacesContext context, Flow flow) {
initializeNavigationFromFlow(context, flow);
}
// ------------------------------------------ Methods from NavigationHandler
@Override
public void handleNavigation(FacesContext context,
String fromAction,
String outcome) {
this.handleNavigation(context, fromAction, outcome, "");
}
@Override
public void handleNavigation(FacesContext context, String fromAction, String outcome, String toFlowDocumentId) {
Util.notNull("context", context);
CaseStruct caseStruct = getViewId(context, fromAction, outcome, toFlowDocumentId);
if (caseStruct != null) {
ExternalContext extContext = context.getExternalContext();
ViewHandler viewHandler = Util.getViewHandler(context);
assert (null != viewHandler);
Flash flash = extContext.getFlash();
boolean isUIViewActionBroadcastAndViewdsDiffer = false;
if (UIViewAction.isProcessingBroadcast(context)) {
flash.setKeepMessages(true);
String viewIdBefore = context.getViewRoot().getViewId();
viewIdBefore = (null == viewIdBefore) ? "" : viewIdBefore;
String viewIdAfter = caseStruct.navCase.getToViewId(context);
viewIdAfter = (null == viewIdAfter) ? "" : viewIdAfter;
isUIViewActionBroadcastAndViewdsDiffer = !viewIdBefore.equals(viewIdAfter);
}
if (caseStruct.navCase.isRedirect() || isUIViewActionBroadcastAndViewdsDiffer) {
// perform a 302 redirect.
String redirectUrl =
viewHandler.getRedirectURL(context,
caseStruct.viewId,
SharedUtils.evaluateExpressions(context, caseStruct.navCase.getParameters()),
caseStruct.navCase.isIncludeViewParams());
try {
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "Redirecting to path {0} for outcome {1}and viewId {2}", new Object[]{redirectUrl, outcome, caseStruct.viewId});
}
// encode the redirect to ensure session state
// is maintained
updateRenderTargets(context, caseStruct.viewId);
flash.setRedirect(true);
extContext.redirect(redirectUrl);
} catch (java.io.IOException ioe) {
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE,"jsf.redirect_failed_error",
redirectUrl);
}
throw new FacesException(ioe.getMessage(), ioe);
}
context.responseComplete();
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "Response complete for {0}", caseStruct.viewId);
}
} else {
UIViewRoot newRoot = viewHandler.createView(context,
caseStruct.viewId);
updateRenderTargets(context, caseStruct.viewId);
context.setViewRoot(newRoot);
FlowHandler flowHandler = context.getApplication().getFlowHandler();
if (null != flowHandler && !isDidTransition(context)) {
flowHandler.transition(context,
caseStruct.currentFlow, caseStruct.newFlow,
caseStruct.facesFlowCallNode, caseStruct.viewId);
setDidTransition(context, false);
}
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "Set new view in FacesContext for {0}", caseStruct.viewId);
}
}
clearViewMapIfNecessary(context, caseStruct.viewId);
}
}
// --------------------------------------------------------- Private Methods
private static final String ROOT_NAVIGATION_MAP_ID = NavigationHandlerImpl.class.getName() + ".NAVIGATION_MAP";
private Map> getRootNaviMap() {
Map> result = null;
NavigationInfo info = null;
if (null == navigationMaps) {
createNavigationMaps();
result = navigationMaps.get(ROOT_NAVIGATION_MAP_ID).ruleSet;
} else {
info = navigationMaps.get(ROOT_NAVIGATION_MAP_ID);
if (null == info.ruleSet) {
result = Collections.emptyMap();
} else {
result = info.ruleSet;
}
}
return result;
}
private Map> getNavigationMap(FacesContext context) {
Map> result = null;
NavigationInfo info = null;
if (null == navigationMaps) {
createNavigationMaps();
result = navigationMaps.get(ROOT_NAVIGATION_MAP_ID).ruleSet;
} else {
FlowHandler fh = context.getApplication().getFlowHandler();
if (null != fh) {
Flow currentFlow = fh.getCurrentFlow(context);
if (null != currentFlow) {
info = navigationMaps.get(currentFlow.getDefiningDocumentId() + currentFlow.getId());
// We are in a flow, but there are no navigation rules for
// this flow.
if (null == info) {
return Collections.emptyMap();
}
}
}
if (null == info) {
info = navigationMaps.get(ROOT_NAVIGATION_MAP_ID);
}
if (null == info.ruleSet) {
result = Collections.emptyMap();
} else {
result = info.ruleSet;
}
}
return result;
}
private void createNavigationMaps() {
if (null == navigationMaps) {
Map maps = new ConcurrentHashMap();
NavigationMap result = new NavigationMap();
NavigationInfo info = new NavigationInfo();
info.ruleSet = result;
maps.put(ROOT_NAVIGATION_MAP_ID, info);
navigationMaps = maps;
}
}
private Map> getRootNavigationMap() {
createNavigationMaps();
return navigationMaps.get(ROOT_NAVIGATION_MAP_ID).ruleSet;
}
private Set getWildCardMatchList(FacesContext context) {
Set result = Collections.emptySet();
NavigationInfo info = null;
FlowHandler fh = context.getApplication().getFlowHandler();
if (null != fh) {
Flow currentFlow = fh.getCurrentFlow(context);
if (null != currentFlow) {
info = navigationMaps.get(currentFlow.getDefiningDocumentId() + currentFlow.getId());
}
}
if (null == info) {
info = navigationMaps.get(ROOT_NAVIGATION_MAP_ID);
}
if (null != info.ruleSet && null != info.ruleSet.wildcardMatchList) {
result = info.ruleSet.wildcardMatchList;
}
return result;
}
private NavigationInfo getNavigationInfo(FacesContext context, String toFlowDocumentId, String flowId) {
NavigationInfo result = null;
assert(null != navigationMaps);
result = navigationMaps.get(toFlowDocumentId + flowId);
if (null == result) {
FlowHandler fh = context.getApplication().getFlowHandler();
if (null != fh) {
Flow currentFlow = fh.getCurrentFlow(context);
if (null != currentFlow) {
result = navigationMaps.get(currentFlow.getDefiningDocumentId() + currentFlow.getId());
}
}
}
return result;
}
private void initializeNavigationFromAssociate() {
ApplicationAssociate associate = ApplicationAssociate.getCurrentInstance();
if (associate != null) {
Map> m = associate.getNavigationCaseListMappings();
Map> rootMap = getRootNavigationMap();
if (m != null) {
rootMap.putAll(m);
}
}
}
private void initializeNavigationFromFlow(FacesContext context, Flow toInspect) {
if (context instanceof InitFacesContext) {
createNavigationMaps();
initializeNavigationFromFlowNonThreadSafe(toInspect);
} else {
// PENDING: When JAVASERVERFACES-2580 is done, the eager case will
// no longer be necessary and can be removed.
assert(null != navigationMaps);
initializeNavigationFromFlowThreadSafe(toInspect);
}
}
private void initializeNavigationFromFlowNonThreadSafe(Flow toInspect) {
String fullyQualifiedFlowId = toInspect.getDefiningDocumentId() + toInspect.getId();
// Is there an existing NavigationMap for this flowId
if (navigationMaps.containsKey(fullyQualifiedFlowId)) {
if (logger.isLoggable(Level.INFO)) {
logger.log(Level.INFO, "PENDING(edburns): merge existing map");
}
} else {
Map> navRules = toInspect.getNavigationCases();
Map switches = toInspect.getSwitches();
if (!navRules.isEmpty() || !switches.isEmpty()) {
NavigationInfo info = new NavigationInfo();
if (!switches.isEmpty()) {
info.switches = new ConcurrentHashMap();
for (Map.Entry cur : switches.entrySet()) {
info.switches.put(cur.getKey(), cur.getValue());
}
}
if (!navRules.isEmpty()) {
info.ruleSet = new NavigationMap();
info.ruleSet.putAll(navRules);
}
navigationMaps.put(fullyQualifiedFlowId, info);
}
}
}
private void initializeNavigationFromFlowThreadSafe(Flow toInspect) {
synchronized (this) {
initializeNavigationFromFlowNonThreadSafe(toInspect);
}
}
/**
* Calls clear()
on the ViewMap (if available) if the view
* ID of the UIViewRoot differs from newId
*/
private void clearViewMapIfNecessary(FacesContext facesContext, String newId) {
UIViewRoot root = facesContext.getViewRoot();
if (root != null && !root.getViewId().equals(newId)) {
Map viewMap = root.getViewMap(false);
if (viewMap != null) {
viewMap.clear();
}
}
}
private void updateRenderTargets(FacesContext ctx, String newId) {
if (ctx.getViewRoot() == null || !ctx.getViewRoot().getViewId().equals(newId)) {
PartialViewContext pctx = ctx.getPartialViewContext();
if (!pctx.isRenderAll()) {
pctx.setRenderAll(true);
}
}
}
/**
* This method uses helper methods to determine the new view
identifier.
* Refer to section 7.4.2 of the specification for more details.
*
* @param ctx the @{link FacesContext} for the current request
* @param fromAction The action reference string
* @param outcome The outcome string
* @return The view
identifier.
*/
private CaseStruct getViewId(FacesContext ctx,
String fromAction,
String outcome, String toFlowDocumentId) {
if (navigationMaps == null) {
synchronized (this) {
initializeNavigationFromAssociate();
}
}
UIViewRoot root = ctx.getViewRoot();
String viewId = (root != null ? root.getViewId() : null);
// if viewIdToTest is not null, use its value to find
// a navigation match, otherwise look for a match
// based soley on the fromAction and outcome
CaseStruct caseStruct = null;
Map> navMap = getNavigationMap(ctx);
if (viewId != null) {
caseStruct = findExactMatch(ctx, viewId, fromAction, outcome, toFlowDocumentId, navMap);
if (caseStruct == null) {
caseStruct = findWildCardMatch(ctx, viewId, fromAction, outcome, toFlowDocumentId, navMap);
}
}
if (caseStruct == null) {
caseStruct = findDefaultMatch(ctx, fromAction, outcome, toFlowDocumentId, navMap);
}
FlowHandler fh = ctx.getApplication().getFlowHandler();
// If the preceding steps found a match, but it was a flow call...
if (null != caseStruct && caseStruct.isFlowEntryFromExplicitRule) {
// Override the toFlowDocumentId with the value from the navigation-case, if present
toFlowDocumentId = (null != caseStruct.navCase.getToFlowDocumentId()) ? caseStruct.navCase.getToFlowDocumentId() : toFlowDocumentId;
// and try to call into the flow
caseStruct = findFacesFlowCallMatch(ctx, fromAction, convertToViewIdToFlowOrNodeId(ctx, caseStruct), toFlowDocumentId);
} else if ( null != caseStruct && fh != null && fh.getCurrentFlow() != null) {
String nodeId = convertToViewIdToFlowOrNodeId(ctx, caseStruct);
FlowNode node = fh.getCurrentFlow().getNode(nodeId);
if ( node != null) {
caseStruct =null;
outcome = nodeId;
}
}
// If we still don't have a match, see if this is a viewNode
if (null == caseStruct && null != fromAction && null != outcome) {
caseStruct = findViewNodeMatch(ctx, fromAction, outcome);
}
// If we still don't have a match, see if this is a switch
if (null == caseStruct && null != fromAction && null != outcome) {
caseStruct = findSwitchMatch(ctx, fromAction, outcome, toFlowDocumentId);
}
// If we still don't have a match, see if this is a method-call
if (null == caseStruct && null != fromAction && null != outcome) {
caseStruct = findMethodCallMatch(ctx, fromAction, outcome, toFlowDocumentId);
}
// If we still don't have a match, see if this is a faces-flow-call
if (null == caseStruct && null != outcome) {
caseStruct = findFacesFlowCallMatch(ctx, fromAction, outcome, toFlowDocumentId);
}
// If we still don't have a match, see if this is a flow-return
if (null == caseStruct && null != outcome) {
caseStruct = findReturnMatch(ctx, fromAction, outcome);
}
// If the navigation rules do not have a match...
if (caseStruct == null && outcome != null && viewId != null) {
// Treat empty string equivalent to null outcome. JSF 2.0 Rev a
// Changelog issue C063.
if (0 == outcome.length()) {
outcome = null;
} else {
caseStruct = findImplicitMatch(ctx, viewId, fromAction, outcome,
toFlowDocumentId);
}
}
// If we still don't have a match...
if (caseStruct == null && outcome != null && viewId != null) {
FlowHandler flowHandler = ctx.getApplication().getFlowHandler();
if (null != flowHandler) {
Flow currentFlow = null;
currentFlow = flowHandler.getCurrentFlow(ctx);
if (null != currentFlow) {
caseStruct = findRootNavigationMapAbandonedFlowMatch(ctx, viewId, fromAction, outcome, toFlowDocumentId);
}
}
}
// no navigation case found
if (caseStruct == null && outcome != null && development) {
String key;
Object[] params;
if (fromAction == null) {
key = MessageUtils.NAVIGATION_NO_MATCHING_OUTCOME_ID;
params = new Object[] { viewId, outcome };
} else {
key = MessageUtils.NAVIGATION_NO_MATCHING_OUTCOME_ACTION_ID;
params = new Object[] { viewId, fromAction, outcome };
}
FacesMessage m = MessageUtils.getExceptionMessage(key, params);
m.setSeverity(FacesMessage.SEVERITY_WARN);
ctx.addMessage(null, m);
}
return caseStruct;
}
/**
* This method finds the List of cases for the current view
identifier.
* After the cases are found, the from-action
and from-outcome
* values are evaluated to determine the new view
identifier.
* Refer to section 7.4.2 of the specification for more details.
*
* @param ctx the {@link FacesContext} for the current request
* @param viewId The current view
identifier.
* @param fromAction The action reference string.
* @param outcome The outcome string.
* @return The view
identifier.
*/
private CaseStruct findExactMatch(FacesContext ctx,
String viewId,
String fromAction,
String outcome, String toFlowDocumentId,
Map> navMap) {
Set caseSet = navMap.get(viewId);
if (caseSet == null) {
return null;
}
// We've found an exact match for the viewIdToTest. Now we need to evaluate
// from-action/outcome in the following order:
// 1) elements specifying both from-action and from-outcome
// 2) elements specifying only from-outcome
// 3) elements specifying only from-action
// 4) elements where both from-action and from-outcome are null
CaseStruct result = determineViewFromActionOutcome(ctx, caseSet, fromAction, outcome, toFlowDocumentId);
if (null != result) {
FlowHandler flowHandler = ctx.getApplication().getFlowHandler();
if (null != flowHandler) {
result.currentFlow = flowHandler.getCurrentFlow(ctx);
result.newFlow = result.currentFlow;
}
}
return result;
}
/**
* This method traverses the wild card match List (containing from-view-id
* strings and finds the List of cases for each from-view-id
string.
* Refer to section 7.4.2 of the specification for more details.
*
* @param ctx the {@link FacesContext} for the current request
* @param viewId The current view
identifier.
* @param fromAction The action reference string.
* @param outcome The outcome string.
* @return The view
identifier.
*/
private CaseStruct findWildCardMatch(FacesContext ctx,
String viewId,
String fromAction,
String outcome, String toFlowDocumentId,
Map> navMap) {
CaseStruct result = null;
StringBuilder sb = new StringBuilder(32);
for (String fromViewId : getWildCardMatchList(ctx)) {
// See if the entire wildcard string (without the trailing "*" is
// contained in the incoming viewIdToTest.
// Ex: /foobar is contained with /foobarbaz
// If so, then we have found our largest pattern match..
// If not, then continue on to the next case;
if (!viewId.startsWith(fromViewId)) {
continue;
}
// Append the trailing "*" so we can do our map lookup;
if ( sb.length() != 0 ) {
sb.delete(0, sb.length());
}
String wcFromViewId = sb.append(fromViewId).append('*').toString();
Set ccaseSet = navMap.get(wcFromViewId);
if (ccaseSet == null) {
return null;
}
// If we've found a match, then we need to evaluate
// from-action/outcome in the following order:
// 1) elements specifying both from-action and from-outcome
// 2) elements specifying only from-outcome
// 3) elements specifying only from-action
// 4) elements where both from-action and from-outcome are null
result = determineViewFromActionOutcome(ctx,
ccaseSet,
fromAction,
outcome, toFlowDocumentId);
if (result != null) {
break;
}
}
if (null != result) {
FlowHandler flowHandler = ctx.getApplication().getFlowHandler();
if (null != flowHandler) {
result.currentFlow = flowHandler.getCurrentFlow(ctx);
result.newFlow = result.currentFlow;
}
}
return result;
}
/**
* This method will extract the cases for which a from-view-id
is
* an asterisk "*".
* Refer to section 7.4.2 of the specification for more details.
*
* @param ctx the {@link FacesContext} for the current request
* @param fromAction The action reference string.
* @param outcome The outcome string.
* @return The view
identifier.
*/
private CaseStruct findDefaultMatch(FacesContext ctx,
String fromAction,
String outcome, String toFlowDocumentId,
Map> navMap) {
Set caseSet = navMap.get("*");
if (caseSet == null) {
return null;
}
// We need to evaluate from-action/outcome in the follow
// order: 1)elements specifying both from-action and from-outcome
// 2) elements specifying only from-outcome
// 3) elements specifying only from-action
// 4) elements where both from-action and from-outcome are null
CaseStruct result = determineViewFromActionOutcome(ctx, caseSet, fromAction, outcome, toFlowDocumentId);
if (null != result) {
FlowHandler flowHandler = ctx.getApplication().getFlowHandler();
if (null != flowHandler) {
result.currentFlow = flowHandler.getCurrentFlow(ctx);
result.newFlow = result.currentFlow;
}
}
return result;
}
private CaseStruct findRootNavigationMapAbandonedFlowMatch(FacesContext ctx,
String viewId,
String fromAction,
String outcome, String toFlowDocumentId) {
CaseStruct caseStruct = null;
Map> navMap = getRootNaviMap();
if (viewId != null) {
caseStruct = findExactMatch(ctx, viewId, fromAction, outcome, toFlowDocumentId, navMap);
if (caseStruct == null) {
caseStruct = findWildCardMatch(ctx, viewId, fromAction, outcome, toFlowDocumentId, navMap);
}
}
if (caseStruct == null) {
caseStruct = findDefaultMatch(ctx, fromAction, outcome, toFlowDocumentId, navMap);
}
if (null != caseStruct) {
caseStruct.newFlow = FlowImpl.ABANDONED_FLOW;
}
return caseStruct;
}
/**
*
* Create a navigation case based on content within the outcome.
*
*
* @param context the {@link FacesContext} for the current request
* @param viewId of the {@link UIViewRoot} for the current request
* @param fromAction the navigation action
* @param outcome the navigation outcome
* @return a CaseStruct representing the the navigation result based
* on the provided input
*/
private CaseStruct findImplicitMatch(FacesContext context,
String viewId,
String fromAction,
String outcome,
String flowDefiningDocumentId) {
// look for an implicit match.
String viewIdToTest = outcome;
String currentViewId = viewId;
Map> parameters = null;
boolean isRedirect = false;
boolean isIncludeViewParams = false;
CaseStruct result = null;
int questionMark = viewIdToTest.indexOf('?');
String queryString;
if (-1 != questionMark) {
int viewIdLen = viewIdToTest.length();
if (viewIdLen <= (questionMark+1)) {
if (logger.isLoggable(Level.SEVERE)) {
logger.log(Level.SEVERE, "jsf.navigation_invalid_query_string",
viewIdToTest);
}
if (development) {
String key;
Object[] params;
key = MessageUtils.NAVIGATION_INVALID_QUERY_STRING_ID;
params = new Object[]{viewIdToTest};
FacesMessage m = MessageUtils.getExceptionMessage(key, params);
m.setSeverity(FacesMessage.SEVERITY_WARN);
context.addMessage(null, m);
}
queryString = null;
viewIdToTest = viewIdToTest.substring(0, questionMark);
} else {
queryString = viewIdToTest.substring(questionMark + 1);
viewIdToTest = viewIdToTest.substring(0, questionMark);
Matcher m = REDIRECT_EQUALS_TRUE.matcher(queryString);
if (m.find()) {
isRedirect = true;
queryString = queryString.replace(m.group(2), "");
}
m = INCLUDE_VIEW_PARAMS_EQUALS_TRUE.matcher(queryString);
if (m.find()) {
isIncludeViewParams = true;
queryString = queryString.replace(m.group(2), "");
}
}
if (queryString != null && queryString.length() > 0) {
Map appMap = context.getExternalContext().getApplicationMap();
String[] queryElements = Util.split(appMap, queryString, "&|&");
for (int i = 0, len = queryElements.length; i < len; i ++) {
String[] elements = Util.split(appMap, queryElements[i], "=");
if (elements.length == 2) {
String rightHandSide = elements[1];
String sanitized = null != rightHandSide && 2 < rightHandSide.length() ? rightHandSide.trim() : "";
if (sanitized.contains("#{") || sanitized.contains("${")) {
if (logger.isLoggable(Level.INFO)) {
logger.log(Level.INFO, "jsf.navigation_invalid_query_string",
rightHandSide);
}
rightHandSide = "";
}
if (parameters == null) {
parameters = new LinkedHashMap>(len / 2, 1.0f);
List values = new ArrayList(2);
values.add(rightHandSide);
parameters.put(elements[0], values);
} else {
List values = parameters.get(elements[0]);
if (values == null) {
values = new ArrayList(2);
parameters.put(elements[0], values);
}
values.add(rightHandSide);
}
}
}
}
}
// If the viewIdToTest needs an extension, take one from the currentViewId.
StringBuilder viewIdBuf = new StringBuilder(viewIdToTest);
if (viewIdToTest.lastIndexOf('.') == -1) {
int idx = currentViewId.lastIndexOf('.');
if (idx != -1) {
viewIdBuf.append(currentViewId.substring(idx));
}
}
if (viewIdToTest.charAt(0) != '/') {
int lastSlash = currentViewId.lastIndexOf('/');
if (lastSlash != -1) {
currentViewId = currentViewId.substring(0, lastSlash + 1);
viewIdBuf.insert(0, currentViewId);
} else {
viewIdBuf.insert(0, "/");
}
}
viewIdToTest = viewIdBuf.toString();
ViewHandler viewHandler = Util.getViewHandler(context);
FlowHandler flowHandler = context.getApplication().getFlowHandler();
Flow currentFlow = null;
Flow newFlow = null;
if (null != flowHandler) {
currentFlow = flowHandler.getCurrentFlow(context);
newFlow = currentFlow;
// If we are in a flow, use the implicit rules to ensure the view
// is within that flow. This means viewIdToTest must start with
// the current flow id
if (null != currentFlow && null != viewIdToTest &&
!viewIdToTest.startsWith("/" + currentFlow.getId())) {
// ... it must be out of the current flow.
// If this is a flow return...
if (FlowHandler.NULL_FLOW.equals(flowDefiningDocumentId)) {
// treat it as such
newFlow = null;
viewIdToTest = null;
} else {
// otherwise Make sure the current flow is marked as abandoned.
newFlow = FlowImpl.ABANDONED_FLOW;
}
}
}
if (null != viewIdToTest) {
viewIdToTest = viewHandler.deriveViewId(context, viewIdToTest);
}
if (null == result && null != viewIdToTest) {
result = new CaseStruct();
result.viewId = viewIdToTest;
// If this navigation case does not involve flows, make sure
// to convey that fact.
if (null == newFlow && null == currentFlow &&
!FlowHandlerImpl.NULL_FLOW.equals(flowDefiningDocumentId)) {
flowDefiningDocumentId = null;
}
result.navCase = new NavigationCase(currentViewId,
fromAction,
outcome,
null,
viewIdToTest,
flowDefiningDocumentId,
parameters,
isRedirect,
isIncludeViewParams);
}
if (null != result) {
result.currentFlow = currentFlow;
result.newFlow = newFlow;
}
return result;
}
private CaseStruct findSwitchMatch(FacesContext context, String fromAction,
String outcome, String toFlowDocumentId) {
FlowHandler flowHandler = context.getApplication().getFlowHandler();
if (null == flowHandler) {
return null;
}
CaseStruct result = null;
NavigationInfo info = getNavigationInfo(context, toFlowDocumentId, fromAction);
Flow currentFlow = flowHandler.getCurrentFlow(context);
if (null != info && null != info.switches && !info.switches.isEmpty()) {
SwitchNode switchNode = info.switches.get(outcome);
if (null != switchNode) {
// Determine the outcome from the switch. The outcome
// may come from exactly one of two places:
// 1. the switch statements
List cases = switchNode.getCases();
boolean matched=false;
for (SwitchCase cur : cases) {
if (cur.getCondition(context)) {
outcome = cur.getFromOutcome();
matched=true;
break;
}
}
// 2. the default outcome
if ( !matched || outcome == null) {
outcome = switchNode.getDefaultOutcome(context);
}
if (null != outcome) {
if (null != currentFlow) {
FlowNode targetNode = currentFlow.getNode(outcome);
if (targetNode instanceof MethodCallNode) {
result = findMethodCallMatch(context, fromAction, outcome, toFlowDocumentId);
} else if (targetNode instanceof SwitchNode) {
result = findSwitchMatch(context, fromAction, outcome, toFlowDocumentId);
} else if (targetNode instanceof FlowCallNode) {
result = findFacesFlowCallMatch(context, fromAction, outcome, toFlowDocumentId);
}
}
if (null == result) {
Flow newFlow = flowHandler.getFlow(context, toFlowDocumentId,
fromAction);
// If this outcome corresponds to an existing flow...
if (null != newFlow) {
result = synthesizeCaseStruct(context, newFlow, fromAction, outcome);
} else {
newFlow = flowHandler.getCurrentFlow(context);
if (null != newFlow) {
result = synthesizeCaseStruct(context, newFlow, fromAction, outcome);
}
}
}
}
}
}
if (null != result) {
result.currentFlow = currentFlow;
if (result.newFlow == FlowImpl.SYNTHESIZED_RETURN_CASE_FLOW) {
result.newFlow = null;
} else {
result.newFlow = currentFlow;
}
}
return result;
}
private CaseStruct synthesizeCaseStruct(FacesContext context, Flow flow, String fromAction, String outcome) {
CaseStruct result = null;
FlowNode node = flow.getNode(outcome);
if (null != node) {
if (node instanceof ViewNode) {
result = new CaseStruct();
result.viewId = ((ViewNode)node).getVdlDocumentId();
result.navCase = new MutableNavigationCase(fromAction,
fromAction, outcome, null, result.viewId,
flow.getDefiningDocumentId(), null, false, false);
} else if (node instanceof ReturnNode) {
String fromOutcome = ((ReturnNode)node).getFromOutcome(context);
FlowHandler flowHandler = context.getApplication().getFlowHandler();
try {
flowHandler.pushReturnMode(context);
result = getViewId(context, fromAction, fromOutcome, FlowHandler.NULL_FLOW);
// We are in a return node, but no result can be found from that
// node. Show the last displayed viewId from the preceding flow.
if (null == result) {
Flow precedingFlow = flowHandler.getCurrentFlow(context);
if (null != precedingFlow) {
String toViewId = flowHandler.getLastDisplayedViewId(context);
if (null != toViewId) {
result = new CaseStruct();
result.viewId = toViewId;
result.navCase = new MutableNavigationCase(context.getViewRoot().getViewId(),
fromAction,
outcome,
null,
toViewId,
FlowHandler.NULL_FLOW,
null,
false,
false);
}
}
} else {
// setDidTransition(context, true);
flowHandler.transition(context, flow, null, null, result.viewId);
setDidTransition(context, true);
result.newFlow = FlowImpl.SYNTHESIZED_RETURN_CASE_FLOW;
}
}
finally {
flowHandler.popReturnMode(context);
}
}
} else {
// See if there is an implicit match within this flow, using outcome
// to derive a view id within this flow.
String currentViewId = outcome;
// If the viewIdToTest needs an extension, take one from the currentViewId.
String currentExtension;
int idx = currentViewId.lastIndexOf('.');
if (idx != -1) {
currentExtension = currentViewId.substring(idx);
} else {
// PENDING, don't hard code XHTML here, look it up from configuration
currentExtension = ".xhtml";
}
String viewIdToTest = "/" + flow.getId() + "/" + outcome + currentExtension;
ViewHandler viewHandler = Util.getViewHandler(context);
viewIdToTest = viewHandler.deriveViewId(context, viewIdToTest);
if (null != viewIdToTest) {
result = new CaseStruct();
result.viewId = viewIdToTest;
result.navCase = new MutableNavigationCase(fromAction,
fromAction, outcome, null, result.viewId,
null, false, false);
}
}
return result;
}
private CaseStruct findMethodCallMatch(FacesContext context, String fromAction, String outcome, String toFlowDocumentId) {
FlowHandler flowHandler = context.getApplication().getFlowHandler();
if (null == flowHandler) {
return null;
}
CaseStruct result = null;
Flow currentFlow = flowHandler.getCurrentFlow(context);
if (null != currentFlow) {
FlowNode node = currentFlow.getNode(outcome);
if (node instanceof MethodCallNode) {
MethodCallNode methodCallNode = (MethodCallNode) node;
MethodExpression me = methodCallNode.getMethodExpression();
if (null != me) {
List paramList= methodCallNode.getParameters();
Object[] params = null;
if (null != paramList) {
params = new Object[paramList.size()];
int i = 0;
ELContext elContext = context.getELContext();
for (Parameter cur : paramList) {
params[i++] = cur.getValue().getValue(elContext);
}
}
Object invokeResult = me.invoke(context.getELContext(), params);
if (null == invokeResult) {
ValueExpression ve = methodCallNode.getOutcome();
if (null != ve) {
invokeResult = ve.getValue(context.getELContext());
}
}
outcome = invokeResult.toString();
FlowNode targetNode = currentFlow.getNode(outcome);
if (targetNode instanceof MethodCallNode) {
result = findMethodCallMatch(context, fromAction, outcome, toFlowDocumentId);
} else if (targetNode instanceof SwitchNode) {
result = findSwitchMatch(context, fromAction, outcome, toFlowDocumentId);
} else if (targetNode instanceof FlowCallNode) {
result = findFacesFlowCallMatch(context, fromAction, outcome, toFlowDocumentId);
} else {
result = synthesizeCaseStruct(context, currentFlow, fromAction, outcome);
if (null != result) {
// Here we need to detect the case when the
// synthesizeCaseStruct() ended up hitting a .
// In this case, we must ensure the new flow of null
// is honored.
result.currentFlow = currentFlow;
if (result.newFlow == FlowImpl.SYNTHESIZED_RETURN_CASE_FLOW) {
result.newFlow = null;
} else {
result.newFlow = currentFlow;
}
}
}
}
}
}
return result;
}
private CaseStruct findFacesFlowCallMatch(FacesContext context,
String fromAction, String outcome, String toFlowDocumentId) {
FlowHandler flowHandler = context.getApplication().getFlowHandler();
if (null == flowHandler) {
return null;
}
CaseStruct result = null;
Flow currentFlow = flowHandler.getCurrentFlow(context);
Flow newFlow = null;
FlowCallNode facesFlowCallNode = null;
if (null != currentFlow) {
FlowNode node = currentFlow.getNode(outcome);
if (node instanceof FlowCallNode) {
facesFlowCallNode = (FlowCallNode) node;
String flowId = facesFlowCallNode.getCalledFlowId(context);
String flowDocumentId = facesFlowCallNode.getCalledFlowDocumentId(context);
if (null != flowId) {
newFlow = flowHandler.getFlow(context, flowDocumentId, flowId);
if (null != newFlow) {
String startNodeId = newFlow.getStartNodeId();
result = synthesizeCaseStruct(context, newFlow, fromAction, startNodeId);
if (null == result) {
assert(null != currentFlow);
// If no CaseStruct can be synthesized, we must execute the
// navigation handler algorithm to try to find the CaseStruct
// for the start node. However, in order to do that, we
// must enter the new flow. To preserve the intergity
// of the state machine, we enter the flow now, and mark
// that we must not enter it later.
try {
setDidTransition(context, true);
flowHandler.transition(context, currentFlow, newFlow, facesFlowCallNode, startNodeId);
result = getViewId(context, fromAction, startNodeId, toFlowDocumentId);
}
finally {
if (null == result) {
// If we did not find a CaseStruct, preserve the
// integrity of the state machine by transitioning
// out of the flow.
flowHandler.transition(context, newFlow, currentFlow, null, outcome);
setDidTransition(context, false);
}
}
}
}
}
}
} else {
// See if we are trying to enter a flow.
newFlow = flowHandler.getFlow(context, toFlowDocumentId, outcome);
if (null != newFlow) {
String startNodeId = newFlow.getStartNodeId();
result = synthesizeCaseStruct(context, newFlow, fromAction, startNodeId);
if (null == result) {
assert(null == currentFlow);
// If no CaseStruct can be synthesized, we must execute the
// navigation handler algorithm to try to find the CaseStruct
// for the start node. However, in order to do that, we
// must enter the new flow. To preserve the intergity
// of the state machine, we enter the flow now, and mark
// that we must not enter it later.
try {
setDidTransition(context, true);
flowHandler.transition(context, null, newFlow, null, startNodeId);
result = getViewId(context, fromAction, startNodeId, toFlowDocumentId);
}
finally {
if (null == result) {
// If we did not find a CaseStruct, preserve the
// integrity of the state machine by transitioning
// out of the flow.
flowHandler.transition(context, newFlow, null, null, outcome);
setDidTransition(context, false);
}
}
} else if (!outcome.equals(startNodeId) && null != result.navCase) {
((MutableNavigationCase)result.navCase).setFromOutcome(outcome);
}
}
}
if (null != result) {
result.currentFlow = currentFlow;
result.newFlow = newFlow;
result.facesFlowCallNode = facesFlowCallNode;
}
return result;
}
private static final String DID_TRANSITION_FLAG = RIConstants.FACES_PREFIX + "NavigationHandlerDidTransition";
private boolean isDidTransition(FacesContext context) {
boolean result = context.getAttributes().containsKey(DID_TRANSITION_FLAG);
return result;
}
private void setDidTransition(FacesContext context, boolean value) {
Map