dev@javaserverfaces.java.net

Re: Seeking retroactive review [419-ImplicitNavigation]

From: Ryan Lubke <Ryan.Lubke_at_Sun.COM>
Date: Tue, 20 Jan 2009 14:24:30 -0800

Ed Burns wrote:
> Issue: 419-ImplicitNavigation
>
> All automated tests continue to run successfully.
>
> - create ViewHandler.deriveViewId()
>
> This takes the place of the "viewid derivation algorithm". The
> algorithm hasn't changed except to allow it to be called passing in a
> new view id to derive.
>
> Here's a cut and paste from the spec to aid in your review. If you're
> on the EG, the spec can be downloaded from
> <https://javaserverfaces-spec-eg.dev.java.net/files/documents/2091/123674/jsf-spec-2.0-20090116.zip>
> If your on SWAN it's at
> <http://javaweb.sfbay.sun.com/java/javaee/Specs/JSF/2.0/PFD1/jsf-spec-20090116/>.
>
> [P1-start ViewHandler.deriveViewId() requirements] The deriveViewId()
> method must fulfill the following responsibilities:
>
> ? If prefix mapping (such as “/faces/*”) is used for FacesServlet, if
> argument input is not-null, the viewId is set as the argument
> input. Otherwise, the viewId is set from the extra path information of
> the request URI.
>
> ? If suffix mapping (such as “*.faces”) is used for FacesServlet, the
> viewId is set using following algorithm.
>
> If argument input is non-null, let requestViewId be the value of
> argument input. Otherwise, obtain the servlet path information of the
> request URI and let this value be requestViewId.
>
> Consult the javadocs for ViewHandler.FACELETS_VIEW_MAPPINGS_PARAM_NAME
> and perform the steps necessary to obtain a value for that param (or
> its alias as in the javadocs). Let this be faceletsViewMappings.
>
> Obtain the value of the context initialization parameter named by the
> symbolic constant ViewHandler.DEFAULT_SUFFIX_PARAM_NAME (if no such
> context initialization parameter is present, use the value of the
> symbolic constant ViewHandler.DEFAULT_SUFFIX). Let this be
> jspDefaultSuffixs. For each entry in the list from jspDefaultSuffixes,
> replace the suffix of requestViewId with jspDefaultSuffix. For
> discussion, call this derivedViewId. If derivedViewId matches one of
> the entries in the list of strings given by the value of
> faceletsViewMappings, consider the algorithm complete with the result
> being derivedViewId.
>
> Otherwise, look for a physical resource with the name
> derivedViewId. If such a resource exists, derivedViewId is the correct
> viewId.
>
> Otherwise, obtain the value of the context initialization parameter
> named by the symbolic constant
> ViewHandler.FACELETS_SUFFIX_PARAM_NAME. (if no such context
> initialization parameter is present, use the value of the symbolic
> constant ViewHandler.DEFAULT_FACELETS_SUFFIX). Let this be
> faceletsDefaultSuffix. Replace the suffix of requestViewId with
> faceletsDefaultSuffix. For discussion, call this derivedViewId. If a
> physical resource exists with that name, derivedViewId is the correct
> viewId.
>
> Otherwise, if a physical resource exists with the name requestViewId
> let that value be viewId.[P1-end]
>
> - Modify the navigation handler to allow implicit navigation, leveraging
> this new method. Here's a cut and paste from the spec.
>
> If no matching <navigation-case> element was located, return to Step 1
> and find the next matching <navigation-rule> element (if any). If
> there are no more matching rule elements, execute the following
> algorithm to search for an implicit match based on the current
> outcome.
>
> ? Let outcome be viewIdToTest.
>
> ? Examine the viewIdToTest for the presence of “?” character,
> indicating the presence of a URI query string. If one is found, remove
> the query string from viewIdToTest, including the leading “?” and let
> it be queryString, look for the string “redirect=true” within the
> query string. If found, let isRedirect be true, otherwise let
> isRedirect be false.
>
> ? If viewIdToTest does not have a “file extension”, take the file
> extension from the current viewId and append it properly to
> viewIdToTest.
>
> ? If viewIdToTest does not begin with “/”, take the current viewId and
> look for the last “/”. If not found, prepend a “/” and
> continue. Otherwise remove all characters in viewId after, but not
> including, “/”, then append viewIdToTest and let the result be
> viewIdToTest.
>
> ? Obtain the current ViewHandler and call its deriveViewId() method,
> passing the current FacesContext and viewIdToTest. If
> UnsupportedOperationException is thrown, which will be the case if the
> ViewHandler is a Pre JSF 2.0 ViewHandler, the implementation must
> ensure the algorithm described for ViewHandler.deriveViewId() specified
> in Section 7.5.2 “Default ViewHandler Implementation” is
> performed. Let the result be implicitViewId.
>
> ? If the implicitViewId is non-null, take the following action. If
> isRedirect is true, append the queryString to implicitViewId. Let
> virtualNavigationCase be a conceptual <navigation-case> element whose
> fromViewId is the current viewId, fromAction is passed through from
> the arguments to handleNavigation(), fromOutcome is passed through
> from the arguments to handleNavigation(), toViewId is implicitViewId,
> and redirect is the value of isRedirect. Treat virtualNavigationCase
> as a matching navigation case and return to the first step above that
> starts with “If a matching <navigation-case> element was located...”.
>
> ? If none of the above steps found a matching <navigation-case>, if
> ProjectStage is not Production render a message in the page that
> explains that there was no match for this outcome.
>
> A rule match always causes a new view to be created, losing the state
> of the old view.
>
> SECTION: Modified Files
> ----------------------------
> M jsf-api/src/javax/faces/application/ViewHandler.java
>
> - declare new method deriveViewId()
>
Why no implementaion of this method in the API proper?

> M jsf-ri/src/com/sun/faces/lifecycle/RestoreViewPhase.java
>
> - use new method deriveViewId()
>
> M jsf-ri/src/com/sun/faces/application/NavigationHandlerImpl.java
>
> - perform new contract for implicit navigation
>
> M jsf-ri/src/com/sun/faces/application/view/MultiViewHandler.java
>
> - implement deriveViewId(), but this calls through to Util.
>
See above.

Also you have:

+
+ public void setRestoreViewPhase(RestoreViewPhase restoreViewPhase) {
+
     }
 

What is that?
> M jsf-ri/src/com/sun/faces/config/WebConfiguration.java
>
> - getter for "list like" context-param values. Initialize a cache of such
> things in the ctor
>

+ private Map<WebContextInitParameter, String []> cachedListParams;
+
+ public String [] getOptionValue(WebContextInitParameter param, String sep) {
+ String [] result = null;
+
+ assert(null != cachedListParams);
+ if (null == (result = cachedListParams.get(param))) {
+ String value = getOptionValue(param);
+ if (null == value) {
+ result = new String[0];
+ } else {
+ result = value.split(sep);
+ }
+ cachedListParams.put(param, result);
+ }
+
+ return result;
+ }

Use Util.split() not String.split.

Also, minor nit, but it will save me going back and doing it is keep the structure
of the class consistent. Private members are declared at the top, not inline.

 

> M jsf-ri/src/com/sun/faces/util/Util.java
>
> - really implement deriveViewId()
>
> A jsf-ri/systest-per-webapp/implicit-navigation
> A jsf-ri/systest-per-webapp/implicit-navigation/src
> A jsf-ri/systest-per-webapp/implicit-navigation/src/java
> A jsf-ri/systest-per-webapp/implicit-navigation/src/java/com
> A jsf-ri/systest-per-webapp/implicit-navigation/src/java/com/sun
> A jsf-ri/systest-per-webapp/implicit-navigation/src/java/com/sun/faces
> A jsf-ri/systest-per-webapp/implicit-navigation/src/java/com/sun/faces/systest
> A jsf-ri/systest-per-webapp/implicit-navigation/src/java/com/sun/faces/systest/ImplicitNavigationTestCase.java
> A jsf-ri/systest-per-webapp/implicit-navigation/web
> A jsf-ri/systest-per-webapp/implicit-navigation/web/WEB-INF
> A jsf-ri/systest-per-webapp/implicit-navigation/web/WEB-INF/web.xml
> A jsf-ri/systest-per-webapp/implicit-navigation/web/page01.xhtml
> A jsf-ri/systest-per-webapp/implicit-navigation/web/page02.xhtml
> A jsf-ri/systest-per-webapp/implicit-navigation/web/page03.xhtml
> A jsf-ri/systest-per-webapp/implicit-navigation/web/page04.xhtml
>
> - Execute the new code PENDING(edburns) need automated test
>
I would much rather see this in the NavigationHandler cactus test case
unless there is a reason for having a separate webapp for this.
Each new systest-per-webapp slows down the testing cycle, so I would rather
we were conservative on adding these.
> SECTION: Diffs
> ----------------------------
> Index: jsf-api/src/javax/faces/application/ViewHandler.java
> ===================================================================
> --- jsf-api/src/javax/faces/application/ViewHandler.java (revision 6227)
> +++ jsf-api/src/javax/faces/application/ViewHandler.java (working copy)
> @@ -307,7 +307,23 @@
> */
> public abstract UIViewRoot createView(FacesContext context, String viewId);
>
> + /**
> + * <p class="changed_added_2_0">Derive the viewId from the current
> + * request, or the argument input by following the algorithm defined
> + * in the specification.</p>
> + *
> + * @since 2.0
> + * @param context the <code>FacesContext</code> for this request
>
> + * @param input the input candidate <code>viewId</code> to derive,
> + * or <code>null</code> to use the information in the current
> + * request to derive the <code>viewId</code>.
> +
> + */
> + public String deriveViewId(FacesContext context, String input) {
> + throw new UnsupportedOperationException("The default implementation must override this method");
> + }
> +
> /**
> * <p>Return a URL suitable for rendering (after optional encoding
> * performed by the <code>encodeActionURL()</code> method of
> Index: jsf-ri/src/com/sun/faces/lifecycle/RestoreViewPhase.java
> ===================================================================
> --- jsf-ri/src/com/sun/faces/lifecycle/RestoreViewPhase.java (revision 6227)
> +++ jsf-ri/src/com/sun/faces/lifecycle/RestoreViewPhase.java (working copy)
> @@ -159,42 +159,18 @@
> }
> return;
> }
> -
> - // Reconstitute or create the request tree
> - Map requestMap = facesContext.getExternalContext().getRequestMap();
> - String viewId = (String)
> - requestMap.get("javax.servlet.include.path_info");
> - if (viewId == null) {
> - viewId = facesContext.getExternalContext().getRequestPathInfo();
> + ViewHandler viewHandler = Util.getViewHandler(facesContext);
> + String viewId = null;
> +
> + try {
> + viewId = viewHandler.deriveViewId(facesContext, null);
> + } catch (UnsupportedOperationException e) {
> + viewId = Util.deriveViewId(facesContext, null);
> }
> -
> - // It could be that this request was mapped using
> - // a prefix mapping in which case there would be no
> - // path_info. Query the servlet path.
> - if (viewId == null) {
> - viewId = (String)
> - requestMap.get("javax.servlet.include.servlet_path");
> - }
> -
> - if (viewId == null) {
> - Object request = facesContext.getExternalContext().getRequest();
> - if (request instanceof HttpServletRequest) {
> - viewId = ((HttpServletRequest) request).getServletPath();
> - }
> - }
> -
> - if (viewId == null) {
> - if (LOGGER.isLoggable(Level.WARNING)) {
> - LOGGER.warning("viewId is null");
> - }
> - throw new FacesException(MessageUtils.getExceptionMessageString(
> - MessageUtils.NULL_REQUEST_VIEW_ERROR_MESSAGE_ID));
> - }
> -
> +
> boolean isPostBack = (facesContext.isPostback() && !isErrorPage(facesContext));
> if (isPostBack) {
> // try to restore the view
> - ViewHandler viewHandler = Util.getViewHandler(facesContext);
> viewRoot = viewHandler.restoreView(facesContext, viewId);
>
> if (viewRoot == null) {
> Index: jsf-ri/src/com/sun/faces/application/NavigationHandlerImpl.java
> ===================================================================
> --- jsf-ri/src/com/sun/faces/application/NavigationHandlerImpl.java (revision 6227)
> +++ jsf-ri/src/com/sun/faces/application/NavigationHandlerImpl.java (working copy)
> @@ -180,8 +180,8 @@
> return; // Explicitly remain on the current view
> }
> CaseStruct caseStruct = getViewId(context, fromAction, outcome);
> - ExternalContext extContext = context.getExternalContext();
> if (caseStruct != null) {
> + ExternalContext extContext = context.getExternalContext();
> ViewHandler viewHandler = Util.getViewHandler(context);
> assert (null != viewHandler);
>
> @@ -219,7 +219,7 @@
> caseStruct.viewId);
> }
> }
> - }
> + }
> }
>
>
> @@ -236,9 +236,11 @@
> String outcome) {
>
> UIViewRoot root = context.getViewRoot();
> + ViewHandler viewHandler = Util.getViewHandler(context);
> +
> String viewId = (root != null ? root.getViewId() : null);
>
> - // if viewId is not null, use its value to find
> + // 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;
> @@ -253,6 +255,11 @@
> if (caseStruct == null) {
> caseStruct = findDefaultMatch(fromAction, outcome);
> }
> + // If the navigation rules do not have a match...
> + if (null == caseStruct) {
> + caseStruct = findImplicitMatch(context, viewHandler, root,
> + fromAction, outcome);
> + }
>
> if (caseStruct == null && development) {
> String key;
> @@ -278,7 +285,7 @@
> * values are evaluated to determine the new <code>view</code> identifier.
> * Refer to section 7.4.2 of the specification for more details.
> *
> - * @param viewId The current <code>view</code> identifier.
> + * @param viewIdToTest The current <code>view</code> identifier.
> * @param fromAction The action reference string.
> * @param outcome The outcome string.
> * @return The <code>view</code> identifier.
> @@ -300,7 +307,7 @@
> return null;
> }
>
> - // We've found an exact match for the viewId. Now we need to evaluate
> + // 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
> @@ -317,7 +324,7 @@
> * strings and finds the List of cases for each <code>from-view-id</code> string.
> * Refer to section 7.4.2 of the specification for more details.
> *
> - * @param viewId The current <code>view</code> identifier.
> + * @param viewIdToTest The current <code>view</code> identifier.
> * @param fromAction The action reference string.
> * @param outcome The outcome string.
> * @return The <code>view</code> identifier.
> @@ -336,7 +343,7 @@
>
> for (String fromViewId : wildCardSet) {
> // See if the entire wildcard string (without the trailing "*" is
> - // contained in the incoming viewId.
> + // 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;
> @@ -403,8 +410,68 @@
>
> return determineViewFromActionOutcome(caseList, fromAction, outcome);
> }
> +
> + private CaseStruct findImplicitMatch(FacesContext context,
> + ViewHandler viewHandler, UIViewRoot root, String fromAction,
> + String outcome) {
> + CaseStruct caseStruct = null;
> + // look for an implicit match.
> + String viewIdToTest = outcome;
> + String currentViewId = root.getViewId();
> + boolean isRedirect = false;
> + final String REDIRECT_EQUALS_TRUE = "redirect=true";
> + int questionMark, redirectEqualsTrueIndex;
> +
> + // Does the outcome have a query string?
> + if (-1 != (questionMark = viewIdToTest.indexOf("?"))) {
> + // If so, does it have "redirect=true"?
> + if (-1 != (redirectEqualsTrueIndex =
> + viewIdToTest.indexOf(REDIRECT_EQUALS_TRUE, questionMark))) {
> + isRedirect = true;
> + }
> + // Remove the query string from the viewId.
> + viewIdToTest = viewIdToTest.substring(0, questionMark);
>
> + }
> +
> + // If the viewIdToTest If needs an extension, take one from the currentViewId.
> + if (-1 == (redirectEqualsTrueIndex = viewIdToTest.lastIndexOf("."))) {
> + if (-1 != (redirectEqualsTrueIndex = currentViewId.lastIndexOf("."))) {
> + viewIdToTest = viewIdToTest + currentViewId.substring(redirectEqualsTrueIndex);
> + }
> + }
>
> + if (!viewIdToTest.startsWith("/")) {
> + int lastSlash = currentViewId.lastIndexOf("/");
> + if (-1 != lastSlash) {
> + currentViewId = currentViewId.substring(0, lastSlash + 1);
> + viewIdToTest = currentViewId + viewIdToTest;
> + } else {
> + viewIdToTest = "/" + viewIdToTest;
> + }
> + }
> +
> + try {
> + viewIdToTest = viewHandler.deriveViewId(context, viewIdToTest);
> + } catch (UnsupportedOperationException e) {
> + viewIdToTest = Util.deriveViewId(context, viewIdToTest);
> + }
> +
> + if (null != viewIdToTest) {
> + caseStruct = new CaseStruct();
> + if (isRedirect) {
> + // Tack back on the query string
> + viewIdToTest = viewIdToTest + outcome.substring(questionMark);
> + }
> + caseStruct.viewId = viewIdToTest;
> + caseStruct.navCase = new NavigationCase(currentViewId,
> + fromAction, outcome, viewIdToTest, isRedirect);
> + }
> +
> + return caseStruct;
> + }
> +
> +
> /**
> * This method will attempt to find the <code>view</code> identifier based on action reference
> * and outcome. Refer to section 7.4.2 of the specification for more details.
> Index: jsf-ri/src/com/sun/faces/application/view/MultiViewHandler.java
> ===================================================================
> --- jsf-ri/src/com/sun/faces/application/view/MultiViewHandler.java (revision 6227)
> +++ jsf-ri/src/com/sun/faces/application/view/MultiViewHandler.java (working copy)
> @@ -56,6 +56,7 @@
>
> import com.sun.faces.RIConstants;
> import com.sun.faces.config.WebConfiguration;
> +import com.sun.faces.lifecycle.RestoreViewPhase;
> import com.sun.faces.util.FacesLogger;
> import com.sun.faces.util.MessageUtils;
> import com.sun.faces.util.Util;
> @@ -164,9 +165,8 @@
> public UIViewRoot restoreView(FacesContext context, String viewId) {
>
> Util.notNull("context", context);
> - String actualViewId = derivePhysicalViewId(context, viewId);
> - return pdlFactory.getPageDeclarationLanguage(actualViewId)
> - .restoreView(context, actualViewId);
> + return pdlFactory.getPageDeclarationLanguage(viewId)
> + .restoreView(context, viewId);
>
> }
>
> @@ -472,13 +472,23 @@
> public UIViewRoot createView(FacesContext context, String viewId) {
>
> Util.notNull("context", context);
> - String actualViewId = derivePhysicalViewId(context, viewId);
> - return pdlFactory.getPageDeclarationLanguage(actualViewId).createView(context,
> - actualViewId);
> + return pdlFactory.getPageDeclarationLanguage(viewId).createView(context,
> + viewId);
>
> }
>
> + @Override
> + public String deriveViewId(FacesContext facesContext, String input) {
> + String viewId = null;
> +
> + viewId = Util.deriveViewId(facesContext, input);
> +
> + return viewId;
> + }
> +
> +
>
> +
> /**
> * <p>
> * This code is currently common to all {_at_link ViewHandlingStrategy} instances.
> @@ -654,8 +664,7 @@
> public PageDeclarationLanguage getPageDeclarationLanguage(FacesContext context,
> String viewId) {
>
> - String actualViewId = derivePhysicalViewId(context, viewId);
> - return pdlFactory.getPageDeclarationLanguage(actualViewId);
> + return pdlFactory.getPageDeclarationLanguage(viewId);
>
> }
>
> @@ -721,41 +730,7 @@
> }
>
>
> - /**
> - * <p>if the specified mapping is a prefix mapping, and the provided
> - * request URI (usually the value from <code>ExternalContext.getRequestServletPath()</code>)
> - * starts with <code>mapping + '/'</code>, prune the mapping from the
> - * URI and return it, otherwise, return the original URI.
> - * @param uri the servlet request path
> - * @param mapping the FacesServlet mapping used for this request
> - * @return the URI without additional FacesServlet mappings
> - * @since 1.2
> - */
> - protected String normalizeRequestURI(String uri, String mapping) {
>
> - if (mapping == null || !Util.isPrefixMapped(mapping)) {
> - return uri;
> - } else {
> - int length = mapping.length() + 1;
> - StringBuilder builder = new StringBuilder(length);
> - builder.append(mapping).append('/');
> - String mappingMod = builder.toString();
> - boolean logged = false;
> - while (uri.startsWith(mappingMod)) {
> - if (!logged && logger.isLoggable(Level.WARNING)) {
> - logged = true;
> - logger.log(Level.WARNING,
> - "jsf.viewhandler.requestpath.recursion",
> - new Object[] {uri, mapping});
> - }
> - uri = uri.substring(length - 1);
> - }
> - return uri;
> - }
> -
> - }
> -
> -
> /**
> * <p>
> * Send {_at_link HttpServletResponse#SC_NOT_FOUND} (404) to the client.
> @@ -775,82 +750,10 @@
> }
>
> }
> -
> -
> - /**
> - * <p>Adjust the viewID per the requirements of {_at_link #renderView}.</p>
> - *
> - * @param context current {_at_link javax.faces.context.FacesContext}
> - * @param viewId incoming view ID
> - * @return the view ID with an altered suffix mapping (if necessary)
> - */
> - protected String convertViewId(FacesContext context, String viewId) {
> -
> - // if the viewId doesn't already use the above suffix,
> - // replace or append.
> - StringBuilder buffer = new StringBuilder(viewId);
> - for (String ext : configuredExtensions) {
> - if (viewId.endsWith(ext)) {
> - return viewId;
> - }
> - int extIdx = viewId.lastIndexOf('.');
> - if (extIdx != -1) {
> - buffer.replace(extIdx, viewId.length(), ext);
> - } else {
> - // no extension in the provided viewId, append the suffix
> - buffer.append(ext);
> - }
> - String convertedViewId = buffer.toString();
> - try {
> - if (context.getExternalContext().getResource(convertedViewId) != null) {
> - // RELEASE_PENDING (rlubke,driscoll) cache the lookup
> - return convertedViewId;
> - } else {
> - // reset the buffer to check for the next extension
> - buffer.setLength(0);
> - buffer.append(viewId);
> - }
> - } catch (MalformedURLException e) {
> - if (logger.isLoggable(Level.SEVERE)) {
> - logger.log(Level.SEVERE,
> - e.toString(),
> - e);
> - }
> - }
> - }
> -
> - // unable to find any resource match that the default ViewHandler
> - // can deal with. Return the viewId as it was passed. There is
> - // probably another ViewHandler in the stack that will handle this.
> - return viewId;
> -
> +
> + public void setRestoreViewPhase(RestoreViewPhase restoreViewPhase) {
> +
> }
>
>
> - protected String derivePhysicalViewId(FacesContext ctx, String viewId) {
> - if (viewId != null) {
> - String mapping = Util.getFacesMapping(ctx);
> -
> - if (mapping != null) {
> - if (!Util.isPrefixMapped(mapping)) {
> - viewId = convertViewId(ctx, viewId);
> - } else {
> - viewId = normalizeRequestURI(viewId, mapping);
> - if (viewId.equals(mapping)) {
> - // The request was to the FacesServlet only - no
> - // path info
> - // on some containers this causes a recursion in the
> - // RequestDispatcher and the request appears to hang.
> - // If this is detected, return status 404
> - send404Error(ctx);
> - }
> - }
> - }
> -
> - }
> - return viewId;
> - }
> -
> -
> -
> }
> Index: jsf-ri/src/com/sun/faces/config/WebConfiguration.java
> ===================================================================
> --- jsf-ri/src/com/sun/faces/config/WebConfiguration.java (revision 6227)
> +++ jsf-ri/src/com/sun/faces/config/WebConfiguration.java (working copy)
> @@ -55,6 +55,7 @@
> import javax.servlet.ServletContext;
>
> import com.sun.faces.util.FacesLogger;
> +import java.util.HashMap;
>
>
> /** Class Documentation */
> @@ -102,6 +103,12 @@
> processBooleanParameters(servletContext, contextName);
> processInitParameters(servletContext, contextName);
> processJndiEntries(contextName);
> +
> + // build the cache of list type params
> + cachedListParams = new HashMap<WebContextInitParameter, String []>(3);
> + getOptionValue(WebContextInitParameter.ResourceExcludes, " ");
> + getOptionValue(WebContextInitParameter.DefaultSuffix, " ");
> + getOptionValue(WebContextInitParameter.FaceletsViewMappings, ";");
>
> }
>
> @@ -207,6 +214,25 @@
> return result;
>
> }
> +
> + private Map<WebContextInitParameter, String []> cachedListParams;
> +
> + public String [] getOptionValue(WebContextInitParameter param, String sep) {
> + String [] result = null;
> +
> + assert(null != cachedListParams);
> + if (null == (result = cachedListParams.get(param))) {
> + String value = getOptionValue(param);
> + if (null == value) {
> + result = new String[0];
> + } else {
> + result = value.split(sep);
> + }
> + cachedListParams.put(param, result);
> + }
> +
> + return result;
> + }
>
>
> /**
> @@ -615,8 +641,12 @@
> "javax.faces.STATE_SAVING_METHOD",
> "server"
> ),
> + FaceletsSuffix(
> + ViewHandler.FACELETS_SUFFIX_PARAM_NAME,
> + ViewHandler.DEFAULT_FACELETS_SUFFIX
> + ),
> DefaultSuffix(
> - "javax.faces.DEFAULT_SUFFIX",
> + ViewHandler.DEFAULT_SUFFIX_PARAM_NAME,
> ViewHandler.DEFAULT_SUFFIX
> ),
> JavaxFacesConfigFiles(
> @@ -714,7 +744,7 @@
> ""
> ),
> FaceletsViewMappings(
> - "javax.faces.FACELETS_VIEW_MAPPINGS",
> + ViewHandler.FACELETS_VIEW_MAPPINGS_PARAM_NAME,
> ""
> ),
> FaceletsViewMappingsDeprecated(
> Index: jsf-ri/src/com/sun/faces/util/Util.java
> ===================================================================
> --- jsf-ri/src/com/sun/faces/util/Util.java (revision 6227)
> +++ jsf-ri/src/com/sun/faces/util/Util.java (working copy)
> @@ -44,6 +44,8 @@
>
> import com.sun.faces.RIConstants;
>
> +import com.sun.faces.config.WebConfiguration;
> +import com.sun.faces.config.WebConfiguration.WebContextInitParameter;
> import javax.el.ELResolver;
> import javax.el.ValueExpression;
> import javax.faces.FacesException;
> @@ -58,16 +60,13 @@
> import javax.faces.event.AbortProcessingException;
> import java.beans.FeatureDescriptor;
> import java.lang.reflect.Method;
> -import java.util.Iterator;
> +import java.net.MalformedURLException;
> import java.util.Locale;
> import java.util.Map;
> import java.util.logging.Level;
> import java.util.logging.Logger;
> import java.util.regex.Pattern;
> -import javax.faces.event.ComponentSystemEventListener;
> -import javax.faces.event.ListenerFor;
> -import javax.faces.event.SystemEvent;
> -import javax.faces.event.ListenersFor;
> +import javax.servlet.http.HttpServletRequest;
>
> /**
> * <B>Util</B> is a class ...
> @@ -334,9 +333,202 @@
> return Boolean.valueOf(String.valueOf(component.getAttributes().get("disabled"))) || Boolean.valueOf(String.valueOf(component.getAttributes().get("readonly")));
> }
>
> + public static String deriveViewId(FacesContext facesContext, String input) {
> + String viewId = null;
> +
> + String mapping = Util.getFacesMapping(facesContext);
> + boolean testForPhysicalResource = false;
>
> + if (mapping != null) {
> + if (Util.isPrefixMapped(mapping)) {
> + viewId = (null == input) ?
> + getViewIdFromExtraPathInfo(facesContext) : input;
> + viewId = normalizeRequestURI(viewId, mapping);
> + testForPhysicalResource = true;
> + } else {
> + if (null == input) {
> + viewId = findViewIdMatchUsingContextParams(facesContext);
> + } else {
> + viewId = input;
> + testForPhysicalResource = true;
> + }
> + }
> + if (testForPhysicalResource) {
> + try {
> + if (null == facesContext.getExternalContext().getResource(viewId)) {
> + viewId = null;
> + }
> + } catch (MalformedURLException e) {
> + viewId = null;
> + }
> + }
> + }
> +
> + return viewId;
> + }
>
> + /**
> + * <p>if the specified mapping is a prefix mapping, and the provided
> + * request URI (usually the value from <code>ExternalContext.getRequestServletPath()</code>)
> + * starts with <code>mapping + '/'</code>, prune the mapping from the
> + * URI and return it, otherwise, return the original URI.
> + * @param uri the servlet request path
> + * @param mapping the FacesServlet mapping used for this request
> + * @return the URI without additional FacesServlet mappings
> + * @since 1.2
> + */
> + public static String normalizeRequestURI(String uri, String mapping) {
>
> + if (mapping == null || !Util.isPrefixMapped(mapping)) {
> + return uri;
> + } else {
> + int length = mapping.length() + 1;
> + StringBuilder builder = new StringBuilder(length);
> + builder.append(mapping).append('/');
> + String mappingMod = builder.toString();
> + boolean logged = false;
> + while (uri.startsWith(mappingMod)) {
> + if (!logged && LOGGER.isLoggable(Level.WARNING)) {
> + logged = true;
> + LOGGER.log(Level.WARNING,
> + "jsf.viewhandler.requestpath.recursion",
> + new Object[] {uri, mapping});
> + }
> + uri = uri.substring(length - 1);
> + }
> + return uri;
> + }
> +
> + }
> +
> +
> +
> + private static String findViewIdMatchUsingContextParams(FacesContext context) {
> + ExternalContext extContext = context.getExternalContext();
> + WebConfiguration webConfig = WebConfiguration.getInstance(extContext);
> + String pathInfoViewId = null, candidateViewId = null, requestViewId = null;
> + int i,j;
> + boolean foundMatch = false;
> +
> + // Get the viewId from the path info
> + if (null == (pathInfoViewId = requestViewId =
> + getViewIdFromExtraPathInfo(context))) {
> + return null;
> + }
> +
> + // Remove the extension, including the '.'.
> + if (-1 == (i = requestViewId.lastIndexOf(".")) || 0 == i) {
> + return null;
> + }
> + requestViewId = requestViewId.substring(0, i);
> +
> + String jspDefaultSuffixes[] =
> + webConfig.getOptionValue(WebContextInitParameter.
> + DefaultSuffix, " ");
> + String faceletsViewMappings[] =
> + webConfig.getOptionValue(WebContextInitParameter.
> + FaceletsViewMappings, ";");
> + foundMatch = false;
> + // For each entry in the jspDefaultSuffixes list...
> + for (i = 0;
> + (i < jspDefaultSuffixes.length && !foundMatch); i++) {
> + // tack the current suffix onto the requestViewId...
> + candidateViewId = requestViewId + jspDefaultSuffixes[i];
> + // and search for a match in the faceletsViewMappings
> + for (j = 0; j < faceletsViewMappings.length; j++) {
> + if (null != faceletsViewMappings[j]) {
> + if (true == (foundMatch =
> + faceletsViewMappings[j].equals(candidateViewId))) {
> + break;
> + } else {
> + // If we didn't find a match in the faceletsViewMappings
> + // look for a physical resource with that name
> + try {
> + if (extContext.getResource(candidateViewId) != null) {
> + // RELEASE_PENDING (rlubke,driscoll) cache the lookup
> + requestViewId = candidateViewId;
> + foundMatch = true;
> + }
> + } catch (MalformedURLException e) {
> + }
> + }
> + }
> + }
> + }
> + // If we didn't find a match yet...
> + if (!foundMatch) {
> + // Try the FaceletsSuffix
> + String faceletsDefaultSuffix =
> + webConfig.getOptionValue(WebContextInitParameter.FaceletsSuffix);
> + candidateViewId = requestViewId + faceletsDefaultSuffix;
> + try {
> + if (extContext.getResource(candidateViewId) != null) {
> + // RELEASE_PENDING (rlubke,driscoll) cache the lookup
> + requestViewId = candidateViewId;
> + foundMatch = true;
> + }
> + } catch (MalformedURLException e) {
> + }
> +
> + }
> +
> + // Fall back on the pathInfoViewId
> + if (!foundMatch) {
> + candidateViewId = pathInfoViewId;
> + try {
> + if (extContext.getResource(candidateViewId) != null) {
> + // RELEASE_PENDING (rlubke,driscoll) cache the lookup
> + requestViewId = candidateViewId;
> + foundMatch = true;
> + }
> + } catch (MalformedURLException e) {
> + }
> + }
> +
> + if (!foundMatch) {
> + requestViewId = null;
> + }
> +
> + return requestViewId;
> + }
> +
> + private static String getViewIdFromExtraPathInfo(FacesContext facesContext) {
> + String viewId = null;
> + Map<String, Object> requestMap =
> + facesContext.getExternalContext().getRequestMap();
> + viewId = (String) requestMap.get("javax.servlet.include.path_info");
> + if (viewId == null) {
> + viewId = facesContext.getExternalContext().getRequestPathInfo();
> + }
> +
> + // It could be that this request was mapped using
> + // a prefix mapping in which case there would be no
> + // path_info. Query the servlet path.
> + if (viewId == null) {
> + viewId = (String) requestMap.get("javax.servlet.include.servlet_path");
> + }
> +
> + if (viewId == null) {
> + Object request = facesContext.getExternalContext().getRequest();
> + if (request instanceof HttpServletRequest) {
> + viewId = ((HttpServletRequest) request).getServletPath();
> + }
> + }
> +
> + if (viewId == null) {
> + if (LOGGER.isLoggable(Level.WARNING)) {
> + LOGGER.warning("viewId is null");
> + }
> + throw new FacesException(MessageUtils.getExceptionMessageString(
> + MessageUtils.NULL_REQUEST_VIEW_ERROR_MESSAGE_ID));
> + }
> +
> + return viewId;
> + }
> +
> +
> +
> +
> // W3C XML specification refers to IETF RFC 1766 for language code
> // structure, therefore the value for the xml:lang attribute should
> // be in the form of language or language-country or
> Index: jsf-ri/systest-per-webapp/implicit-navigation/src/java/com/sun/faces/systest/ImplicitNavigationTestCase.java
> ===================================================================
> --- jsf-ri/systest-per-webapp/implicit-navigation/src/java/com/sun/faces/systest/ImplicitNavigationTestCase.java (revision 0)
> +++ jsf-ri/systest-per-webapp/implicit-navigation/src/java/com/sun/faces/systest/ImplicitNavigationTestCase.java (revision 0)
> @@ -0,0 +1,82 @@
> +/*
> + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
> + *
> + * Copyright 1997-2008 Sun Microsystems, Inc. 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.html
> + * or glassfish/bootstrap/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 glassfish/bootstrap/legal/LICENSE.txt.
> + * Sun designates this particular file as subject to the "Classpath" exception
> + * as provided by Sun in the GPL Version 2 section of the License file that
> + * accompanied this code. If applicable, add the following below the License
> + * Header, with the fields enclosed by brackets [] replaced by your own
> + * identifying information: "Portions Copyrighted [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.systest;
> +
> +import junit.framework.Test;
> +import junit.framework.TestSuite;
> +import com.gargoylesoftware.htmlunit.html.HtmlPage;
> +import com.sun.faces.htmlunit.AbstractTestCase;
> +
> +public class ImplicitNavigationTestCase extends AbstractTestCase {
> +
> +
> + public ImplicitNavigationTestCase(String name) {
> + super(name);
> + }
> +
> + /**
> + * Set up instance variables required by this test case.
> + */
> + public void setUp() throws Exception {
> + super.setUp();
> + }
> +
> +
> + /**
> + * Return the tests included in this test suite.
> + */
> + public static Test suite() {
> + return (new TestSuite(ImplicitNavigationTestCase.class));
> + }
> +
> +
> + /**
> + * Tear down instance variables required by this test case.
> + */
> + public void tearDown() {
> + super.tearDown();
> + }
> +
> +
> + // ------------------------------------------------------------ Test Methods
> +
> + public void testImplicitNavigation() throws Exception {
> +
> + HtmlPage page = getPage("/faces/page01.xhtml");
> +
> + }
> +}
> Index: jsf-ri/systest-per-webapp/implicit-navigation/web/WEB-INF/web.xml
> ===================================================================
> --- jsf-ri/systest-per-webapp/implicit-navigation/web/WEB-INF/web.xml (revision 0)
> +++ jsf-ri/systest-per-webapp/implicit-navigation/web/WEB-INF/web.xml (revision 0)
> @@ -0,0 +1,77 @@
> +<?xml version="1.0" encoding="UTF-8"?>
> +<!--
> + DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
> +
> + Copyright 1997-2007 Sun Microsystems, Inc. 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.html
> + or glassfish/bootstrap/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 glassfish/bootstrap/legal/LICENSE.txt.
> + Sun designates this particular file as subject to the "Classpath" exception
> + as provided by Sun in the GPL Version 2 section of the License file that
> + accompanied this code. If applicable, add the following below the License
> + Header, with the fields enclosed by brackets [] replaced by your own
> + identifying information: "Portions Copyrighted [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.
> +-->
> +<web-app version="2.5"
> + xmlns="http://java.sun.com/xml/ns/javaee"
> + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
> + xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
> + <description>
> + Verify Implicit Navigation
> + </description>
> + <display-name>Implicit Navigation</display-name>
> +
> + <context-param>
> + <param-name>javax.faces.DEFAULT_SUFFIX</param-name>
> + <param-value>.xhtml</param-value>
> + </context-param>
> + <context-param>
> + <param-name>facelets.DEVELOPMENT</param-name>
> + <param-value>true</param-value>
> + </context-param>
> + <context-param>
> + <param-name>javax.faces.PROJECT_STAGE</param-name>
> + <param-value>Development</param-value>
> + </context-param>
> +
> + <!-- Faces Servlet -->
> + <servlet>
> + <servlet-name>Faces Servlet</servlet-name>
> + <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
> + <load-on-startup>1</load-on-startup>
> + </servlet>
> +
> + <servlet-mapping>
> + <servlet-name>Faces Servlet</servlet-name>
> + <url-pattern>/faces/*</url-pattern>
> + </servlet-mapping>
> +
> + <servlet-mapping>
> + <servlet-name>Faces Servlet</servlet-name>
> + <url-pattern>*.jsf</url-pattern>
> + </servlet-mapping>
> +
> +
> +</web-app>
> Index: jsf-ri/systest-per-webapp/implicit-navigation/web/page01.xhtml
> ===================================================================
> --- jsf-ri/systest-per-webapp/implicit-navigation/web/page01.xhtml (revision 0)
> +++ jsf-ri/systest-per-webapp/implicit-navigation/web/page01.xhtml (revision 0)
> @@ -0,0 +1,19 @@
> +<?xml version="1.0" encoding="UTF-8"?>
> +<!DOCTYPE html
> + PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
> + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"
> + xmlns:h="http://java.sun.com/jsf/html">
> +<h:head>
> + <title>Implicit Navigation</title>
> +</h:head>
> +<h:body>
> +
> + <h:form prependId="false">
> +
> + <p>[page01] <h:commandButton value="page02" action="/page02" /></p>
> +
> + </h:form>
> +
> +</h:body>
> +</html>
> Index: jsf-ri/systest-per-webapp/implicit-navigation/web/page02.xhtml
> ===================================================================
> --- jsf-ri/systest-per-webapp/implicit-navigation/web/page02.xhtml (revision 0)
> +++ jsf-ri/systest-per-webapp/implicit-navigation/web/page02.xhtml (revision 0)
> @@ -0,0 +1,20 @@
> +<?xml version="1.0" encoding="UTF-8"?>
> +<!DOCTYPE html
> + PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
> + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"
> + xmlns:h="http://java.sun.com/jsf/html">
> +<h:head>
> + <title>Implicit Navigation</title>
> +</h:head>
> +<h:body>
> +
> + <h:form prependId="false">
> +
> + <p><h:commandButton value="page01" action="page01" /> [page02]
> + <h:commandButton value="page03" action="page03" /></p>
> +
> + </h:form>
> +
> +</h:body>
> +</html>
> Index: jsf-ri/systest-per-webapp/implicit-navigation/web/page03.xhtml
> ===================================================================
> --- jsf-ri/systest-per-webapp/implicit-navigation/web/page03.xhtml (revision 0)
> +++ jsf-ri/systest-per-webapp/implicit-navigation/web/page03.xhtml (revision 0)
> @@ -0,0 +1,21 @@
> +<?xml version="1.0" encoding="UTF-8"?>
> +<!DOCTYPE html
> + PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
> + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"
> + xmlns:h="http://java.sun.com/jsf/html">
> +<h:head>
> + <title>Implicit Navigation</title>
> +</h:head>
> +<h:body>
> +
> + <h:form prependId="false">
> +
> + <p><h:commandButton value="page02" action="page02" /> [page03]
> +
> + <h:commandButton value="page04" action="page04?redirect=true" /></p>
> +
> + </h:form>
> +
> +</h:body>
> +</html>
> Index: jsf-ri/systest-per-webapp/implicit-navigation/web/page04.xhtml
> ===================================================================
> --- jsf-ri/systest-per-webapp/implicit-navigation/web/page04.xhtml (revision 0)
> +++ jsf-ri/systest-per-webapp/implicit-navigation/web/page04.xhtml (revision 0)
> @@ -0,0 +1,20 @@
> +<?xml version="1.0" encoding="UTF-8"?>
> +<!DOCTYPE html
> + PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
> + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"
> + xmlns:h="http://java.sun.com/jsf/html">
> +<h:head>
> + <title>Implicit Navigation</title>
> +</h:head>
> +<h:body>
> +
> + <h:form prependId="false">
> +
> +<p>This is the last page.</p>
> +
> +
> + </h:form>
> +
> +</h:body>
> +</html>
>
>
>