Coverage Report - org.apache.myfaces.shared.application.DefaultViewHandlerSupport
 
Classes in this File Line Coverage Branch Coverage Complexity
DefaultViewHandlerSupport
48%
96/199
30%
49/160
7.188
 
 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one
 3  
  * or more contributor license agreements.  See the NOTICE file
 4  
  * distributed with this work for additional information
 5  
  * regarding copyright ownership.  The ASF licenses this file
 6  
  * to you under the Apache License, Version 2.0 (the
 7  
  * "License"); you may not use this file except in compliance
 8  
  * with the License.  You may obtain a copy of the License at
 9  
  *
 10  
  *   http://www.apache.org/licenses/LICENSE-2.0
 11  
  *
 12  
  * Unless required by applicable law or agreed to in writing,
 13  
  * software distributed under the License is distributed on an
 14  
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 15  
  * KIND, either express or implied.  See the License for the
 16  
  * specific language governing permissions and limitations
 17  
  * under the License.
 18  
  */
 19  
 package org.apache.myfaces.shared.application;
 20  
 
 21  
 import java.net.MalformedURLException;
 22  
 import java.util.Map;
 23  
 import java.util.logging.Level;
 24  
 import java.util.logging.Logger;
 25  
 
 26  
 import javax.faces.application.ProjectStage;
 27  
 import javax.faces.application.ViewHandler;
 28  
 import javax.faces.context.ExternalContext;
 29  
 import javax.faces.context.FacesContext;
 30  
 import javax.faces.render.ResponseStateManager;
 31  
 import javax.faces.view.ViewDeclarationLanguage;
 32  
 
 33  
 import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFWebConfigParam;
 34  
 import org.apache.myfaces.shared.renderkit.html.util.SharedStringBuilder;
 35  
 import org.apache.myfaces.shared.util.ConcurrentLRUCache;
 36  
 import org.apache.myfaces.shared.util.ExternalContextUtils;
 37  
 import org.apache.myfaces.shared.util.StringUtils;
 38  
 import org.apache.myfaces.shared.util.ViewProtectionUtils;
 39  
 import org.apache.myfaces.shared.util.WebConfigParamUtils;
 40  
 
 41  
 /**
 42  
  * A ViewHandlerSupport implementation for use with standard Java Servlet engines,
 43  
  * ie an engine that supports javax.servlet, and uses a standard web.xml file.
 44  
  */
 45  
 public class DefaultViewHandlerSupport implements ViewHandlerSupport
 46  
 {
 47  
     /**
 48  
      * Identifies the FacesServlet mapping in the current request map.
 49  
      */
 50  1
     private static final String CACHED_SERVLET_MAPPING =
 51  
         DefaultViewHandlerSupport.class.getName() + ".CACHED_SERVLET_MAPPING";
 52  
 
 53  
     //private static final Log log = LogFactory.getLog(DefaultViewHandlerSupport.class);
 54  1
     private static final Logger log = Logger.getLogger(DefaultViewHandlerSupport.class.getName());
 55  
 
 56  
     /**
 57  
      * Controls the size of the cache used to "remember" if a view exists or not.
 58  
      */
 59  
     @JSFWebConfigParam(defaultValue = "500", since = "2.0.2", group="viewhandler", tags="performance", 
 60  
             classType="java.lang.Integer",
 61  
             desc="Controls the size of the cache used to 'remember' if a view exists or not.")
 62  
     private static final String CHECKED_VIEWID_CACHE_SIZE_ATTRIBUTE = "org.apache.myfaces.CHECKED_VIEWID_CACHE_SIZE";
 63  
     private static final int CHECKED_VIEWID_CACHE_DEFAULT_SIZE = 500;
 64  
 
 65  
     /**
 66  
      * Enable or disable a cache used to "remember" if a view exists or not and reduce the impact of
 67  
      * sucesive calls to ExternalContext.getResource().
 68  
      */
 69  
     @JSFWebConfigParam(defaultValue = "true", since = "2.0.2", expectedValues="true, false", group="viewhandler", 
 70  
             tags="performance",
 71  
             desc="Enable or disable a cache used to 'remember' if a view exists or not and reduce the impact " +
 72  
                  "of sucesive calls to ExternalContext.getResource().")
 73  
     private static final String CHECKED_VIEWID_CACHE_ENABLED_ATTRIBUTE = 
 74  
         "org.apache.myfaces.CHECKED_VIEWID_CACHE_ENABLED";
 75  
     private static final boolean CHECKED_VIEWID_CACHE_ENABLED_DEFAULT = true;
 76  
     
 77  
     private static final String VIEW_HANDLER_SUPPORT_SB = "oam.viewhandler.SUPPORT_SB";
 78  
 
 79  8
     private volatile ConcurrentLRUCache<String, Boolean> _checkedViewIdMap = null;
 80  8
     private Boolean _checkedViewIdCacheEnabled = null;
 81  
     
 82  
     private final String[] _faceletsViewMappings;
 83  
     private final String[] _contextSuffixes;
 84  
     private final String _faceletsContextSufix;
 85  
     private final boolean _initialized;
 86  
     
 87  
     public DefaultViewHandlerSupport()
 88  8
     {
 89  8
         _faceletsViewMappings = null;
 90  8
         _contextSuffixes = null;
 91  8
         _faceletsContextSufix = null;
 92  8
         _initialized = false;
 93  8
     }
 94  
     
 95  
     public DefaultViewHandlerSupport(FacesContext facesContext)
 96  0
     {
 97  0
         _faceletsViewMappings = getFaceletsViewMappings(facesContext);
 98  0
         _contextSuffixes = getContextSuffix(facesContext);
 99  0
         _faceletsContextSufix = getFaceletsContextSuffix(facesContext);
 100  0
         _initialized = true;
 101  0
     }
 102  
 
 103  
     public String calculateViewId(FacesContext context, String viewId)
 104  
     {
 105  
         //If no viewId found, don't try to derive it, just continue.
 106  0
         if (viewId == null)
 107  
         {
 108  0
             return null;
 109  
         }
 110  0
         FacesServletMapping mapping = getFacesServletMapping(context);
 111  0
         if (mapping == null || mapping.isExtensionMapping())
 112  
         {
 113  0
             viewId = handleSuffixMapping(context, viewId);
 114  
         }
 115  0
         else if(mapping.isPrefixMapping())
 116  
         {
 117  0
             viewId = handlePrefixMapping(viewId,mapping.getPrefix());
 118  
             
 119  
             // A viewId that is equals to the prefix mapping on servlet mode is
 120  
             // considered invalid, because jsp vdl will use RequestDispatcher and cause
 121  
             // a loop that ends in a exception. Note in portlet mode the view
 122  
             // could be encoded as a query param, so the viewId could be valid.
 123  0
             if (viewId != null && viewId.equals(mapping.getPrefix()) &&
 124  
                 !ExternalContextUtils.isPortlet(context.getExternalContext()))
 125  
             {
 126  0
                 throw new InvalidViewIdException();
 127  
             }
 128  
         }
 129  0
         else if (mapping.getUrlPattern().startsWith(viewId))
 130  
         {
 131  0
             throw new InvalidViewIdException(viewId);
 132  
         }
 133  
 
 134  
         //if(viewId != null)
 135  
         //{
 136  
         //    return (checkResourceExists(context,viewId) ? viewId : null);
 137  
         //}
 138  
 
 139  0
         return viewId;    // return null if no physical resource exists
 140  
     }
 141  
     
 142  
     public String calculateAndCheckViewId(FacesContext context, String viewId)
 143  
     {
 144  
         //If no viewId found, don't try to derive it, just continue.
 145  8
         if (viewId == null)
 146  
         {
 147  0
             return null;
 148  
         }
 149  8
         FacesServletMapping mapping = getFacesServletMapping(context);
 150  8
         if (mapping == null || mapping.isExtensionMapping())
 151  
         {
 152  3
             viewId = handleSuffixMapping(context, viewId);
 153  
         }
 154  5
         else if(mapping.isPrefixMapping())
 155  
         {
 156  5
             viewId = handlePrefixMapping(viewId,mapping.getPrefix());
 157  
 
 158  5
             if(viewId != null)
 159  
             {
 160  
                 // A viewId that is equals to the prefix mapping on servlet mode is
 161  
                 // considered invalid, because jsp vdl will use RequestDispatcher and cause
 162  
                 // a loop that ends in a exception. Note in portlet mode the view
 163  
                 // could be encoded as a query param, so the viewId could be valid.
 164  5
                 if (viewId.equals(mapping.getPrefix()) &&
 165  
                     !ExternalContextUtils.isPortlet(context.getExternalContext()))
 166  
                 {
 167  0
                     throw new InvalidViewIdException();
 168  
                 }
 169  
 
 170  5
                 return (checkResourceExists(context,viewId) ? viewId : null);
 171  
             }
 172  
         }
 173  0
         else if (mapping.getUrlPattern().startsWith(viewId))
 174  
         {
 175  0
             throw new InvalidViewIdException(viewId);
 176  
         }
 177  
         else
 178  
         {
 179  0
             if(viewId != null)
 180  
             {
 181  0
                 return (checkResourceExists(context,viewId) ? viewId : null);
 182  
             }
 183  
         }
 184  
 
 185  3
         return viewId;    // return null if no physical resource exists
 186  
     }
 187  
 
 188  
     public String calculateActionURL(FacesContext context, String viewId)
 189  
     {
 190  0
         if (viewId == null || !viewId.startsWith("/"))
 191  
         {
 192  0
             throw new IllegalArgumentException("ViewId must start with a '/': " + viewId);
 193  
         }
 194  
 
 195  0
         FacesServletMapping mapping = getFacesServletMapping(context);
 196  0
         ExternalContext externalContext = context.getExternalContext();
 197  0
         String contextPath = externalContext.getRequestContextPath();
 198  
         //StringBuilder builder = new StringBuilder(contextPath);
 199  0
         StringBuilder builder = SharedStringBuilder.get(context, VIEW_HANDLER_SUPPORT_SB);
 200  
         // If the context path is root, it is not necessary to append it, otherwise
 201  
         // and extra '/' will be set.
 202  0
         if (contextPath != null && !(contextPath.length() == 1 && contextPath.charAt(0) == '/') )
 203  
         {
 204  0
             builder.append(contextPath);
 205  
         }
 206  0
         if (mapping != null)
 207  
         {
 208  0
             if (mapping.isExtensionMapping())
 209  
             {
 210  
                 //See JSF 2.0 section 7.5.2 
 211  0
                 String[] contextSuffixes = _initialized ? _contextSuffixes : getContextSuffix(context); 
 212  0
                 boolean founded = false;
 213  0
                 for (String contextSuffix : contextSuffixes)
 214  
                 {
 215  0
                     if (viewId.endsWith(contextSuffix))
 216  
                     {
 217  0
                         builder.append(viewId.substring(0, viewId.indexOf(contextSuffix)));
 218  0
                         builder.append(mapping.getExtension());
 219  0
                         founded = true;
 220  0
                         break;
 221  
                     }
 222  
                 }
 223  0
                 if (!founded)
 224  
                 {   
 225  
                     //See JSF 2.0 section 7.5.2
 226  
                     // - If the argument viewId has an extension, and this extension is mapping, 
 227  
                     // the result is contextPath + viewId
 228  
                     //
 229  
                     // -= Leonardo Uribe =- It is evident that when the page is generated, the derived 
 230  
                     // viewId will end with the 
 231  
                     // right contextSuffix, and a navigation entry on faces-config.xml should use such id,
 232  
                     // this is just a workaroud
 233  
                     // for usability. There is a potential risk that change the mapping in a webapp make 
 234  
                     // the same application fail,
 235  
                     // so use viewIds ending with mapping extensions is not a good practice.
 236  0
                     if (viewId.endsWith(mapping.getExtension()))
 237  
                     {
 238  0
                         builder.append(viewId);
 239  
                     }
 240  0
                     else if(viewId.lastIndexOf('.') != -1 )
 241  
                     {
 242  0
                         builder.append(viewId.substring(0, viewId.lastIndexOf('.')));
 243  0
                         builder.append(contextSuffixes[0]);
 244  
                     }
 245  
                     else
 246  
                     {
 247  0
                         builder.append(viewId);
 248  0
                         builder.append(contextSuffixes[0]);
 249  
                     }
 250  
                 }
 251  0
             }
 252  
             else
 253  
             {
 254  0
                 builder.append(mapping.getPrefix());
 255  0
                 builder.append(viewId);
 256  
             }
 257  
         }
 258  
         else
 259  
         {
 260  0
             builder.append(viewId);
 261  
         }
 262  
         
 263  
         //JSF 2.2 check view protection.
 264  0
         if (ViewProtectionUtils.isViewProtected(context, viewId))
 265  
         {
 266  0
             int index = builder.indexOf("?");
 267  0
             if (index >= 0)
 268  
             {
 269  0
                 builder.append("&");
 270  
             }
 271  
             else
 272  
             {
 273  0
                 builder.append("?");
 274  
             }
 275  0
             builder.append(ResponseStateManager.NON_POSTBACK_VIEW_TOKEN_PARAM);
 276  0
             builder.append("=");
 277  0
             ResponseStateManager rsm = context.getRenderKit().getResponseStateManager();
 278  0
             builder.append(rsm.getCryptographicallyStrongTokenFromSession(context));
 279  
         }
 280  
         
 281  0
         String calculatedActionURL = builder.toString();
 282  0
         if (log.isLoggable(Level.FINEST))
 283  
         {
 284  0
             log.finest("Calculated actionURL: '" + calculatedActionURL + "' for viewId: '" + viewId + "'");
 285  
         }
 286  0
         return calculatedActionURL;
 287  
     }
 288  
 
 289  
     /**
 290  
      * Read the web.xml file that is in the classpath and parse its internals to
 291  
      * figure out how the FacesServlet is mapped for the current webapp.
 292  
      */
 293  
     protected FacesServletMapping getFacesServletMapping(FacesContext context)
 294  
     {
 295  8
         Map<Object, Object> attributes = context.getAttributes();
 296  
 
 297  
         // Has the mapping already been determined during this request?
 298  8
         FacesServletMapping mapping = (FacesServletMapping) attributes.get(CACHED_SERVLET_MAPPING);
 299  8
         if (mapping == null)
 300  
         {
 301  8
             ExternalContext externalContext = context.getExternalContext();
 302  8
             mapping = calculateFacesServletMapping(externalContext.getRequestServletPath(),
 303  
                     externalContext.getRequestPathInfo());
 304  
 
 305  8
             attributes.put(CACHED_SERVLET_MAPPING, mapping);
 306  
         }
 307  8
         return mapping;
 308  
     }
 309  
 
 310  
     /**
 311  
      * Determines the mapping of the FacesServlet in the web.xml configuration
 312  
      * file. However, there is no need to actually parse this configuration file
 313  
      * as runtime information is sufficient.
 314  
      *
 315  
      * @param servletPath The servletPath of the current request
 316  
      * @param pathInfo    The pathInfo of the current request
 317  
      * @return the mapping of the FacesServlet in the web.xml configuration file
 318  
      */
 319  
     protected static FacesServletMapping calculateFacesServletMapping(
 320  
         String servletPath, String pathInfo)
 321  
     {
 322  8
         if (pathInfo != null)
 323  
         {
 324  
             // If there is a "extra path", it's definitely no extension mapping.
 325  
             // Now we just have to determine the path which has been specified
 326  
             // in the url-pattern, but that's easy as it's the same as the
 327  
             // current servletPath. It doesn't even matter if "/*" has been used
 328  
             // as in this case the servletPath is just an empty string according
 329  
             // to the Servlet Specification (SRV 4.4).
 330  5
             return FacesServletMapping.createPrefixMapping(servletPath);
 331  
         }
 332  
         else
 333  
         {
 334  
             // In the case of extension mapping, no "extra path" is available.
 335  
             // Still it's possible that prefix-based mapping has been used.
 336  
             // Actually, if there was an exact match no "extra path"
 337  
             // is available (e.g. if the url-pattern is "/faces/*"
 338  
             // and the request-uri is "/context/faces").
 339  3
             int slashPos = servletPath.lastIndexOf('/');
 340  3
             int extensionPos = servletPath.lastIndexOf('.');
 341  3
             if (extensionPos > -1 && extensionPos > slashPos)
 342  
             {
 343  3
                 String extension = servletPath.substring(extensionPos);
 344  3
                 return FacesServletMapping.createExtensionMapping(extension);
 345  
             }
 346  
             else
 347  
             {
 348  
                 // There is no extension in the given servletPath and therefore
 349  
                 // we assume that it's an exact match using prefix-based mapping.
 350  0
                 return FacesServletMapping.createPrefixMapping(servletPath);
 351  
             }
 352  
         }
 353  
     }
 354  
 
 355  
     protected String[] getContextSuffix(FacesContext context)
 356  
     {
 357  3
         String defaultSuffix = context.getExternalContext().getInitParameter(ViewHandler.DEFAULT_SUFFIX_PARAM_NAME);
 358  3
         if (defaultSuffix == null)
 359  
         {
 360  3
             defaultSuffix = ViewHandler.DEFAULT_SUFFIX;
 361  
         }
 362  3
         return StringUtils.splitShortString(defaultSuffix, ' ');
 363  
     }
 364  
     
 365  
     protected String getFaceletsContextSuffix(FacesContext context)
 366  
     {
 367  1
         String defaultSuffix = context.getExternalContext().getInitParameter(ViewHandler.FACELETS_SUFFIX_PARAM_NAME);
 368  1
         if (defaultSuffix == null)
 369  
         {
 370  1
             defaultSuffix = ViewHandler.DEFAULT_FACELETS_SUFFIX;
 371  
         }
 372  1
         return defaultSuffix;
 373  
     }
 374  
     
 375  
     
 376  
     
 377  
     protected String[] getFaceletsViewMappings(FacesContext context)
 378  
     {
 379  3
         String faceletsViewMappings= context.getExternalContext().getInitParameter(
 380  
                 ViewHandler.FACELETS_VIEW_MAPPINGS_PARAM_NAME);
 381  3
         if(faceletsViewMappings == null)    //consider alias facelets.VIEW_MAPPINGS
 382  
         {
 383  3
             faceletsViewMappings= context.getExternalContext().getInitParameter("facelets.VIEW_MAPPINGS");
 384  
         }
 385  
         
 386  3
         return faceletsViewMappings == null ? null : StringUtils.splitShortString(faceletsViewMappings, ';');
 387  
     }
 388  
 
 389  
     /**
 390  
      * Return the normalized viewId according to the algorithm specified in 7.5.2 
 391  
      * by stripping off any number of occurrences of the prefix mapping from the viewId.
 392  
      * <p/>
 393  
      * For example, both /faces/view.xhtml and /faces/faces/faces/view.xhtml would both return view.xhtml
 394  
      * F 
 395  
      */
 396  
     protected String handlePrefixMapping(String viewId, String prefix)
 397  
     {
 398  
         // If prefix mapping (such as "/faces/*") is used for FacesServlet, 
 399  
         // normalize the viewId according to the following
 400  
         // algorithm, or its semantic equivalent, and return it.
 401  
                
 402  
         // Remove any number of occurrences of the prefix mapping from the viewId. 
 403  
         // For example, if the incoming value was /faces/faces/faces/view.xhtml 
 404  
         // the result would be simply view.xhtml.
 405  
         
 406  5
         if ("".equals(prefix))
 407  
         {
 408  
             // if prefix is an empty string (Spring environment), we let it be "//"
 409  
             // in order to prevent an infinite loop in uri.startsWith(-emptyString-).
 410  
             // Furthermore a prefix of "//" is just another double slash prevention.
 411  2
             prefix = "//";
 412  
         }
 413  
         else
 414  
         {
 415  
             // need to make sure its really /faces/* and not /facesPage.xhtml
 416  3
             prefix = prefix + '/'; 
 417  
         }
 418  
         
 419  5
         String uri = viewId;
 420  7
         while (uri.startsWith(prefix) || uri.startsWith("//")) 
 421  
         {
 422  2
             if (uri.startsWith(prefix))
 423  
             {
 424  
                 // cut off only /faces, leave the trailing '/' char for the next iteration
 425  2
                 uri = uri.substring(prefix.length() - 1);
 426  
             }
 427  
             else
 428  
             {
 429  
                 // uri starts with '//' --> cut off the leading slash, leaving
 430  
                 // the second slash to compare for the next iteration
 431  0
                 uri = uri.substring(1);
 432  
             }
 433  
         }
 434  
         
 435  
         //now delete any remaining leading '/'
 436  
         // TODO: CJH: I don't think this is correct, considering that getActionURL() expects everything to
 437  
         // start with '/', and in the suffix case we only mess with the suffix and leave leading
 438  
         // slashes alone.  Please review...
 439  
         /*if(uri.startsWith("/"))
 440  
         {
 441  
             uri = uri.substring(1);
 442  
         }*/
 443  
         
 444  5
         return uri;
 445  
     }
 446  
     
 447  
     /**
 448  
      * Return the viewId with any non-standard suffix stripped off and replaced with
 449  
      * the default suffix configured for the specified context.
 450  
      * <p/>
 451  
      * For example, an input parameter of "/foo.jsf" may return "/foo.jsp".
 452  
      */
 453  
     protected String handleSuffixMapping(FacesContext context, String requestViewId)
 454  
     {
 455  3
         String[] faceletsViewMappings = _initialized ? _faceletsViewMappings : getFaceletsViewMappings(context);
 456  3
         String[] jspDefaultSuffixes = _initialized ? _contextSuffixes : getContextSuffix(context);
 457  
         
 458  3
         int slashPos = requestViewId.lastIndexOf('/');
 459  3
         int extensionPos = requestViewId.lastIndexOf('.');
 460  
         
 461  3
         StringBuilder builder = SharedStringBuilder.get(context, VIEW_HANDLER_SUPPORT_SB);
 462  
         
 463  
         //Try to locate any resource that match with the expected id
 464  8
         for (String defaultSuffix : jspDefaultSuffixes)
 465  
         {
 466  
             //StringBuilder builder = new StringBuilder(requestViewId);
 467  7
             builder.setLength(0);
 468  7
             builder.append(requestViewId);
 469  
            
 470  7
             if (extensionPos > -1 && extensionPos > slashPos)
 471  
             {
 472  7
                 builder.replace(extensionPos, requestViewId.length(), defaultSuffix);
 473  
             }
 474  
             else
 475  
             {
 476  0
                 builder.append(defaultSuffix);
 477  
             }
 478  7
             String candidateViewId = builder.toString();
 479  
             
 480  7
             if( faceletsViewMappings != null && faceletsViewMappings.length > 0 )
 481  
             {
 482  0
                 for (String mapping : faceletsViewMappings)
 483  
                 {
 484  0
                     if(mapping.startsWith("/"))
 485  
                     {
 486  0
                         continue;   //skip this entry, its a prefix mapping
 487  
                     }
 488  0
                     if(mapping.equals(candidateViewId))
 489  
                     {
 490  0
                         return candidateViewId;
 491  
                     }
 492  0
                     if(mapping.startsWith(".")) //this is a wildcard entry
 493  
                     {
 494  0
                         builder.setLength(0); //reset/reuse the builder object 
 495  0
                         builder.append(candidateViewId); 
 496  0
                         builder.replace(candidateViewId.lastIndexOf('.'), candidateViewId.length(), mapping);
 497  0
                         String tempViewId = builder.toString();
 498  0
                         if(checkResourceExists(context,tempViewId))
 499  
                         {
 500  0
                             return tempViewId;
 501  
                         }
 502  
                     }
 503  
                 }
 504  
             }
 505  
 
 506  
             // forced facelets mappings did not match or there were no entries in faceletsViewMappings array
 507  7
             if(checkResourceExists(context,candidateViewId))
 508  
             {
 509  2
                 return candidateViewId;
 510  
             }
 511  
         
 512  
         }
 513  
         
 514  
         //jsp suffixes didn't match, try facelets suffix
 515  1
         String faceletsDefaultSuffix = _initialized ? _faceletsContextSufix : this.getFaceletsContextSuffix(context);
 516  1
         if (faceletsDefaultSuffix != null)
 517  
         {
 518  1
             for (String defaultSuffix : jspDefaultSuffixes)
 519  
             {
 520  1
                 if (faceletsDefaultSuffix.equals(defaultSuffix))
 521  
                 {
 522  1
                     faceletsDefaultSuffix = null;
 523  1
                     break;
 524  
                 }
 525  
             }
 526  
         }
 527  1
         if (faceletsDefaultSuffix != null)
 528  
         {
 529  
             //StringBuilder builder = new StringBuilder(requestViewId);
 530  0
             builder.setLength(0);
 531  0
             builder.append(requestViewId);
 532  
             
 533  0
             if (extensionPos > -1 && extensionPos > slashPos)
 534  
             {
 535  0
                 builder.replace(extensionPos, requestViewId.length(), faceletsDefaultSuffix);
 536  
             }
 537  
             else
 538  
             {
 539  0
                 builder.append(faceletsDefaultSuffix);
 540  
             }
 541  
             
 542  0
             String candidateViewId = builder.toString();
 543  0
             if(checkResourceExists(context,candidateViewId))
 544  
             {
 545  0
                 return candidateViewId;
 546  
             }
 547  
         }
 548  
 
 549  
         // Otherwise, if a physical resource exists with the name requestViewId let that value be viewId.
 550  1
         if(checkResourceExists(context,requestViewId))
 551  
         {
 552  0
             return requestViewId;
 553  
         }
 554  
         
 555  
         //Otherwise return null.
 556  1
         return null;
 557  
     }
 558  
     
 559  
     protected boolean checkResourceExists(FacesContext context, String viewId)
 560  
     {
 561  
         try
 562  
         {
 563  13
             if (isCheckedViewIdCachingEnabled(context))
 564  
             {
 565  13
                 Boolean resourceExists = getCheckedViewIDMap(context).get(
 566  
                         viewId);
 567  13
                 if (resourceExists == null)
 568  
                 {
 569  13
                     ViewDeclarationLanguage vdl = context.getApplication().getViewHandler()
 570  
                             .getViewDeclarationLanguage(context, viewId);
 571  13
                     if (vdl != null)
 572  
                     {
 573  0
                         resourceExists = vdl.viewExists(context, viewId);
 574  
                     }
 575  
                     else
 576  
                     {
 577  
                         // Fallback to default strategy
 578  13
                         resourceExists = context.getExternalContext().getResource(
 579  
                                 viewId) != null;
 580  
                     }
 581  13
                     getCheckedViewIDMap(context).put(viewId, resourceExists);
 582  
                 }
 583  13
                 return resourceExists;
 584  
             }
 585  
             else
 586  
             {
 587  0
                 ViewDeclarationLanguage vdl = context.getApplication().getViewHandler()
 588  
                             .getViewDeclarationLanguage(context, viewId);
 589  0
                 if (vdl != null)
 590  
                 {
 591  0
                     if (vdl.viewExists(context, viewId))
 592  
                     {
 593  0
                         return true;
 594  
                     }
 595  
                 }
 596  
                 else
 597  
                 {
 598  
                     // Fallback to default strategy
 599  0
                     if (context.getExternalContext().getResource(viewId) != null)
 600  
                     {
 601  0
                         return true;
 602  
                     }
 603  
                 }
 604  
             }
 605  
         }
 606  0
         catch(MalformedURLException e)
 607  
         {
 608  
             //ignore and move on
 609  0
         }     
 610  0
         return false;
 611  
     }
 612  
 
 613  
     private ConcurrentLRUCache<String, Boolean> getCheckedViewIDMap(FacesContext context)
 614  
     {
 615  26
         if (_checkedViewIdMap == null)
 616  
         {
 617  8
             int maxSize = getViewIDCacheMaxSize(context);
 618  8
             _checkedViewIdMap = new ConcurrentLRUCache<String, Boolean>((maxSize * 4 + 3) / 3, maxSize);
 619  
         }
 620  26
         return _checkedViewIdMap;
 621  
     }
 622  
 
 623  
     private boolean isCheckedViewIdCachingEnabled(FacesContext context)
 624  
     {
 625  13
         if (_checkedViewIdCacheEnabled == null)
 626  
         {
 627  
             // first, check if the ProjectStage is development and skip caching in this case
 628  8
             if (context.isProjectStage(ProjectStage.Development))
 629  
             {
 630  0
                 _checkedViewIdCacheEnabled = Boolean.FALSE;
 631  
             }
 632  
             else
 633  
             {
 634  
                 // in all ohter cases, make sure that the cache is not explicitly disabled via context param
 635  8
                 _checkedViewIdCacheEnabled = WebConfigParamUtils.getBooleanInitParameter(context.getExternalContext(),
 636  
                         CHECKED_VIEWID_CACHE_ENABLED_ATTRIBUTE,
 637  
                         CHECKED_VIEWID_CACHE_ENABLED_DEFAULT);
 638  
             }
 639  
 
 640  8
             if (log.isLoggable(Level.FINE))
 641  
             {
 642  0
                 log.log(Level.FINE, "MyFaces ViewID Caching Enabled="
 643  
                         + _checkedViewIdCacheEnabled);
 644  
             }
 645  
         }
 646  13
         return _checkedViewIdCacheEnabled;
 647  
     }
 648  
 
 649  
     private int getViewIDCacheMaxSize(FacesContext context)
 650  
     {
 651  8
         ExternalContext externalContext = context.getExternalContext();
 652  
 
 653  8
         return WebConfigParamUtils.getIntegerInitParameter(externalContext,
 654  
                 CHECKED_VIEWID_CACHE_SIZE_ATTRIBUTE, CHECKED_VIEWID_CACHE_DEFAULT_SIZE);
 655  
     }
 656  
 }