/* * $Id: NavigationHandlerImpl.java,v 1.3 2006/06/07 03:40:24 ssilvert Exp $ */ /* * The contents of this file are subject to the terms * of the Common Development and Distribution License * (the License). You may not use this file except in * compliance with the License. * * You can obtain a copy of the License at * https://javaserverfaces.dev.java.net/CDDL.html or * legal/CDDLv1.0.txt. * See the License for the specific language governing * permission and limitations under the License. * * When distributing Covered Code, include this CDDL * Header Notice in each file and include the License file * at legal/CDDLv1.0.txt. * If applicable, add the following below the CDDL Header, * with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * [Name of File] [ver.__] [Date] * * Copyright 2005 Sun Microsystems Inc. All Rights Reserved */ package com.sun.faces.application; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.faces.FacesException; import javax.faces.FactoryFinder; import javax.faces.application.ApplicationFactory; import javax.faces.application.NavigationHandler; import javax.faces.application.ViewHandler; import javax.faces.component.UIViewRoot; import javax.faces.context.ExternalContext; import javax.faces.context.FacesContext; import com.sun.faces.config.ConfigureListener; import com.sun.faces.util.Util; import com.sun.faces.util.MessageUtils; /** *

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 NavigationHandler { // // Protected Constants // // Log instance for this class private static Logger logger = Util.getLogger(Util.FACES_LOGGER + Util.APPLICATION_LOGGER); // // Class Variables // // Instance Variables /** * Map containing configured navigation cases. */ private Map> caseListMap; /** * Set containing wildcard navigation cases. */ private Set wildCardSet; /** * Flag indicating navigation cases properly consumed and available. */ private boolean navigationConfigured; /** * This constructor uses the current Application * 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 "); } // if the user is using the decorator pattern, this would cause // our ApplicationAssociate to be created, if it isn't already // created. ApplicationFactory aFactory = (ApplicationFactory) FactoryFinder.getFactory(FactoryFinder.APPLICATION_FACTORY); aFactory.getApplication(); ApplicationAssociate associate = ApplicationAssociate.getInstance( ConfigureListener.getExternalContextDuringInitialize()); if (associate != null) { caseListMap = associate.getNavigationCaseListMappings(); wildCardSet = associate.getNavigationWildCardList(); navigationConfigured = (wildCardSet != null && caseListMap != null); } } /** * Determine the next view based on the current view * (from-view-id stored in FacesContext), * fromAction and outcome. * * @param context The FacesContext * @param fromAction the action reference string * @param outcome the outcome string */ public void handleNavigation(FacesContext context, String fromAction, String outcome) { if (context == null) { String message = MessageUtils.getExceptionMessageString (MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "context"); throw new NullPointerException(message); } if (outcome == null) { if (logger.isLoggable(Level.FINE)) { logger.fine("No navigation rule found for null outcome " + "and viewId " + context.getViewRoot().getViewId() + " Explicitly remain on the current view "); } return; // Explicitly remain on the current view } CaseStruct caseStruct = getViewId(context, fromAction, outcome); ExternalContext extContext = context.getExternalContext(); if (caseStruct != null) { ViewHandler viewHandler = Util.getViewHandler(context); assert (null != viewHandler); if (caseStruct.navCase.hasRedirect()) { // perform a 302 redirect. String newPath = viewHandler.getActionURL(context, caseStruct.viewId); newPath = addRedirectParams(context, newPath, caseStruct.navCase); try { if (logger.isLoggable(Level.FINE)) { logger.fine("Redirecting to path " + newPath + " for outcome " + outcome + "and viewId " + caseStruct.viewId); } extContext.redirect(newPath); } catch (java.io.IOException ioe) { if (logger.isLoggable(Level.SEVERE)) { logger.log(Level.SEVERE,"jsf.redirect_failed_error", newPath); } throw new FacesException(ioe.getMessage(), ioe); } context.responseComplete(); if (logger.isLoggable(Level.FINE)) { logger.fine("Response complete for " + caseStruct.viewId); } } else { UIViewRoot newRoot = viewHandler.createView(context, caseStruct.viewId); context.setViewRoot(newRoot); if (logger.isLoggable(Level.FINE)) { logger.fine("Set new view in FacesContext for " + caseStruct.viewId); } } } } /** * If redirect params were specified, add them to the path. Otherwise, * return the path unchanged. */ private String addRedirectParams(FacesContext context, String path, ConfigNavigationCase navCase) { Map params = navCase.getRedirectParams(); if ((params == null) || params.isEmpty()) return path; StringBuilder builder = new StringBuilder(path); builder.append("?"); for (Iterator i = params.keySet().iterator(); i.hasNext();) { String key = i.next(); builder.append(key); builder.append("="); builder.append(resolveExpression(context, params.get(key))); if (i.hasNext()) { builder.append("&"); } } return builder.toString(); } private Object resolveExpression(FacesContext context, String expression) { return context.getApplication().evaluateExpressionGet(context, expression, Object.class); } /** * This method uses helper methods to determine the new view identifier. * Refer to section 7.4.2 of the specification for more details. * * @param context The Faces Context * @param fromAction The action reference string * @param outcome The outcome string * @return The view identifier. */ private CaseStruct getViewId(FacesContext context, String fromAction, String outcome) { String viewId = context.getViewRoot().getViewId(); CaseStruct caseStruct; caseStruct = findExactMatch(viewId, fromAction, outcome); if (caseStruct == null) { caseStruct = findWildCardMatch(viewId, fromAction, outcome); } if (caseStruct == null) { caseStruct = findDefaultMatch(fromAction, outcome); } if (caseStruct == null && logger.isLoggable(Level.WARNING)) { if (fromAction == null) { logger.log(Level.FINE, "jsf.navigation.no_matching_outcome", new Object[] {viewId, outcome}); } else { logger.log(Level.FINE, "jsf.navigation.no_matching_outcome_action", new Object[] {viewId, outcome, fromAction}); } } 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 viewId The current view identifier. * @param fromAction The action reference string. * @param outcome The outcome string. * @return The view identifier. */ private CaseStruct findExactMatch(String viewId, String fromAction, String outcome) { // if the user has elected to replace the Application instance // entirely if (!navigationConfigured) { return null; } List caseList = caseListMap.get(viewId); if (caseList == null) { return null; } // We've found an exact match for the viewId. 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 return determineViewFromActionOutcome(caseList, fromAction, outcome); } /** * 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 viewId The current view identifier. * @param fromAction The action reference string. * @param outcome The outcome string. * @return The view identifier. */ private CaseStruct findWildCardMatch(String viewId, String fromAction, String outcome) { CaseStruct result = null; // if the user has elected to replace the Application instance // entirely if (!navigationConfigured) { return null; } for (String fromViewId : wildCardSet) { // See if the entire wildcard string (without the trailing "*" is // contained in the incoming viewId. // 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.indexOf(fromViewId, 0) == -1) { continue; } // Append the trailing "*" so we can do our map lookup; String wcFromViewId = fromViewId + '*'; List caseList = caseListMap.get(wcFromViewId); if (caseList == 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(caseList, fromAction, outcome); if (result != null) { break; } } 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 fromAction The action reference string. * @param outcome The outcome string. * @return The view identifier. */ private CaseStruct findDefaultMatch(String fromAction, String outcome) { // if the user has elected to replace the Application instance // entirely if (!navigationConfigured) { return null; } List caseList = caseListMap.get("*"); if (caseList == 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 return determineViewFromActionOutcome(caseList, fromAction, outcome); } /** * This method will attempt to find the view identifier based on action reference * and outcome. Refer to section 7.4.2 of the specification for more details. * * @param caseList The list of navigation cases. * @param fromAction The action reference string. * @param outcome The outcome string. * @return The view identifier. */ private CaseStruct determineViewFromActionOutcome(List caseList, String fromAction, String outcome) { CaseStruct result = new CaseStruct(); for (ConfigNavigationCase cnc : caseList) { String cncFromAction = cnc.getFromAction(); String fromOutcome = cnc.getFromOutcome(); String toViewId = cnc.getToViewId(); if ((cncFromAction != null) && (fromOutcome != null)) { if ((cncFromAction.equals(fromAction)) && (fromOutcome.equals(outcome))) { result.viewId = toViewId; result.navCase = cnc; return result; } } if ((cncFromAction == null) && (fromOutcome != null)) { if (fromOutcome.equals(outcome)) { result.viewId = toViewId; result.navCase = cnc; return result; } } if ((cncFromAction != null) && (fromOutcome == null)) { if (cncFromAction.equals(fromAction)) { result.viewId = toViewId; result.navCase = cnc; return result; } } if ((cncFromAction == null) && (fromOutcome == null)) { result.viewId = toViewId; result.navCase = cnc; return result; } } return null; } private static class CaseStruct { String viewId; ConfigNavigationCase navCase; } }