dev@javaserverfaces.java.net

Seeking retroactive review [419-ImplicitNavigation]

From: Ed Burns <Ed.Burns_at_Sun.COM>
Date: Fri, 16 Jan 2009 18:22:46 -0800

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()

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.

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

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

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>


-- 
| ed.burns_at_sun.com  | office: 408 884 9519 OR x31640
| homepage:         | http://ridingthecrest.com/