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 }