View Javadoc

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.application;
20  
21  import org.apache.commons.logging.Log;
22  import org.apache.commons.logging.LogFactory;
23  
24  import javax.faces.application.ViewHandler;
25  import javax.faces.context.ExternalContext;
26  import javax.faces.context.FacesContext;
27  import java.util.Map;
28  
29  /**
30   * A ViewHandlerSupport implementation for use with standard Java Servlet engines,
31   * ie an engine that supports javax.servlet, and uses a standard web.xml file.
32   *
33   * @author Mathias Broekelmann (latest modification by $Author: skitching $)
34   * @version $Revision: 673811 $ $Date: 2008-07-03 16:18:36 -0500 (Thu, 03 Jul 2008) $
35   */
36  public class DefaultViewHandlerSupport implements ViewHandlerSupport
37  {
38      /**
39       * Identifies the FacesServlet mapping in the current request map.
40       */
41      private static final String CACHED_SERVLET_MAPPING =
42          DefaultViewHandlerSupport.class.getName() + ".CACHED_SERVLET_MAPPING";
43  
44      private static final Log log = LogFactory.getLog(DefaultViewHandlerSupport.class);
45  
46      public String calculateViewId(FacesContext context, String viewId)
47      {
48          FacesServletMapping mapping = getFacesServletMapping(context);
49          if (mapping == null || mapping.isExtensionMapping())
50          {
51              viewId = applyDefaultSuffix(context, viewId);
52          }
53          else if (mapping != null && viewId != null && mapping.getUrlPattern().startsWith(viewId))
54          {
55              throw new InvalidViewIdException(viewId);
56          }
57          return viewId;
58      }
59  
60      public String calculateActionURL(FacesContext context, String viewId)
61      {
62          if (viewId == null || !viewId.startsWith("/"))
63          {
64              throw new IllegalArgumentException("ViewId must start with a '/': " + viewId);
65          }
66  
67          FacesServletMapping mapping = getFacesServletMapping(context);
68          ExternalContext externalContext = context.getExternalContext();
69          String contextPath = externalContext.getRequestContextPath();
70          StringBuilder builder = new StringBuilder(contextPath);
71          if (mapping != null)
72          {
73              if (mapping.isExtensionMapping())
74              {
75                  String contextSuffix = getContextSuffix(context);
76                  if (viewId.endsWith(contextSuffix))
77                  {
78                      builder.append(viewId.substring(0, viewId.indexOf(contextSuffix)));
79                      builder.append(mapping.getExtension());
80                  }
81                  else
82                  {
83                      builder.append(viewId);
84                  }
85              }
86              else
87              {
88                  builder.append(mapping.getPrefix());
89                  builder.append(viewId);
90              }
91          }
92          else
93          {
94              builder.append(viewId);
95          }
96          String calculatedActionURL = builder.toString();
97          if (log.isTraceEnabled())
98          {
99              log.trace("Calculated actionURL: '" + calculatedActionURL + "' for viewId: '" + viewId + "'");
100         }
101         return calculatedActionURL;
102     }
103 
104     /**
105      * Read the web.xml file that is in the classpath and parse its internals to
106      * figure out how the FacesServlet is mapped for the current webapp.
107      */
108     protected FacesServletMapping getFacesServletMapping(FacesContext context)
109     {
110         Map<String, Object> requestMap = context.getExternalContext().getRequestMap();
111 
112         // Has the mapping already been determined during this request?
113         if (!requestMap.containsKey(CACHED_SERVLET_MAPPING))
114         {
115             ExternalContext externalContext = context.getExternalContext();
116             FacesServletMapping mapping =
117                 calculateFacesServletMapping(
118                     externalContext.getRequestServletPath(),
119                     externalContext.getRequestPathInfo());
120 
121             requestMap.put(CACHED_SERVLET_MAPPING, mapping);
122         }
123 
124         return (FacesServletMapping) requestMap.get(CACHED_SERVLET_MAPPING);
125     }
126 
127     /**
128      * Determines the mapping of the FacesServlet in the web.xml configuration
129      * file. However, there is no need to actually parse this configuration file
130      * as runtime information is sufficient.
131      *
132      * @param servletPath The servletPath of the current request
133      * @param pathInfo    The pathInfo of the current request
134      * @return the mapping of the FacesServlet in the web.xml configuration file
135      */
136     protected static FacesServletMapping calculateFacesServletMapping(
137         String servletPath, String pathInfo)
138     {
139         if (pathInfo != null)
140         {
141             // If there is a "extra path", it's definitely no extension mapping.
142             // Now we just have to determine the path which has been specified
143             // in the url-pattern, but that's easy as it's the same as the
144             // current servletPath. It doesn't even matter if "/*" has been used
145             // as in this case the servletPath is just an empty string according
146             // to the Servlet Specification (SRV 4.4).
147             return FacesServletMapping.createPrefixMapping(servletPath);
148         }
149         else
150         {
151             // In the case of extension mapping, no "extra path" is available.
152             // Still it's possible that prefix-based mapping has been used.
153             // Actually, if there was an exact match no "extra path"
154             // is available (e.g. if the url-pattern is "/faces/*"
155             // and the request-uri is "/context/faces").
156             int slashPos = servletPath.lastIndexOf('/');
157             int extensionPos = servletPath.lastIndexOf('.');
158             if (extensionPos > -1 && extensionPos > slashPos)
159             {
160                 String extension = servletPath.substring(extensionPos);
161                 return FacesServletMapping.createExtensionMapping(extension);
162             }
163             else
164             {
165                 // There is no extension in the given servletPath and therefore
166                 // we assume that it's an exact match using prefix-based mapping.
167                 return FacesServletMapping.createPrefixMapping(servletPath);
168             }
169         }
170     }
171 
172     protected String getContextSuffix(FacesContext context)
173     {
174         String defaultSuffix = context.getExternalContext().getInitParameter(ViewHandler.DEFAULT_SUFFIX_PARAM_NAME);
175         if (defaultSuffix == null)
176         {
177             defaultSuffix = ViewHandler.DEFAULT_SUFFIX;
178         }
179         return defaultSuffix;
180     }
181 
182     /**
183      * Return the viewId with any non-standard suffix stripped off and replaced with
184      * the default suffix configured for the specified context.
185      * <p/>
186      * For example, an input parameter of "/foo.jsf" may return "/foo.jsp".
187      */
188     protected String applyDefaultSuffix(FacesContext context, String viewId)
189     {
190         String defaultSuffix = getContextSuffix(context);
191 
192         if (viewId == null)
193         {
194             return null;
195         }
196 
197         if (!viewId.endsWith(defaultSuffix))
198         {
199             StringBuilder builder = new StringBuilder(viewId);
200             int slashPos = viewId.lastIndexOf('/');
201             int extensionPos = viewId.lastIndexOf('.');
202             if (extensionPos > -1 && extensionPos > slashPos)
203             {
204                 builder.replace(extensionPos, viewId.length(), defaultSuffix);
205             }
206             else
207             {
208                 builder.append(defaultSuffix);
209             }
210             viewId = builder.toString();
211             if (log.isTraceEnabled())
212             {
213                 log.trace("view id after applying the context suffix: " + viewId);
214             }
215         }
216         return viewId;
217     }
218 
219     /**
220      * Represents a mapping entry of the FacesServlet in the web.xml
221      * configuration file.
222      */
223     protected static class FacesServletMapping
224     {
225 
226         /**
227          * The path ("/faces", for example) which has been specified in the
228          * url-pattern of the FacesServlet mapping.
229          */
230         private String prefix;
231 
232         /**
233          * The extension (".jsf", for example) which has been specified in the
234          * url-pattern of the FacesServlet mapping.
235          */
236         private String extension;
237 
238         /**
239          * Creates a new FacesServletMapping object using prefix mapping.
240          *
241          * @param path The path ("/faces", for example) which has been specified
242          *             in the url-pattern of the FacesServlet mapping.
243          * @return a newly created FacesServletMapping
244          */
245         public static FacesServletMapping createPrefixMapping(String path)
246         {
247             FacesServletMapping mapping = new FacesServletMapping();
248             mapping.setPrefix(path);
249             return mapping;
250         }
251 
252         /**
253          * Creates a new FacesServletMapping object using extension mapping.
254          *
255          * @param path The extension (".jsf", for example) which has been
256          *             specified in the url-pattern of the FacesServlet mapping.
257          * @return a newly created FacesServletMapping
258          */
259         public static FacesServletMapping createExtensionMapping(
260             String extension)
261         {
262             FacesServletMapping mapping = new FacesServletMapping();
263             mapping.setExtension(extension);
264             return mapping;
265         }
266 
267         /**
268          * Returns the path ("/faces", for example) which has been specified in
269          * the url-pattern of the FacesServlet mapping. If this mapping is based
270          * on an extension, <code>null</code> will be returned. Note that this
271          * path is not the same as the specified url-pattern as the trailing
272          * "/*" is omitted.
273          *
274          * @return the path which has been specified in the url-pattern
275          */
276         public String getPrefix()
277         {
278             return prefix;
279         }
280 
281         /**
282          * Sets the path ("/faces/", for example) which has been specified in
283          * the url-pattern.
284          *
285          * @param path The path which has been specified in the url-pattern
286          */
287         public void setPrefix(String path)
288         {
289             this.prefix = path;
290         }
291 
292         /**
293          * Returns the extension (".jsf", for example) which has been specified
294          * in the url-pattern of the FacesServlet mapping. If this mapping is
295          * not based on an extension, <code>null</code> will be returned.
296          *
297          * @return the extension which has been specified in the url-pattern
298          */
299         public String getExtension()
300         {
301             return extension;
302         }
303 
304         /**
305          * Sets the extension (".jsf", for example) which has been specified in
306          * the url-pattern of the FacesServlet mapping.
307          *
308          * @param extension The extension which has been specified in the url-pattern
309          */
310         public void setExtension(String extension)
311         {
312             this.extension = extension;
313         }
314 
315         /**
316          * Indicates whether this mapping is based on an extension (e.g.
317          * ".jsp").
318          *
319          * @return <code>true</code>, if this mapping is based is on an
320          *         extension, <code>false</code> otherwise
321          */
322         public boolean isExtensionMapping()
323         {
324             return extension != null;
325         }
326 
327         /**
328          * Returns the url-pattern entry for this servlet mapping.
329          *
330          * @return the url-pattern entry for this servlet mapping
331          */
332         public String getUrlPattern()
333         {
334             if (isExtensionMapping())
335             {
336                 return "*" + extension;
337             }
338             else
339             {
340                 return prefix + "/*";
341             }
342         }
343 
344     }
345 }