001: /*
002: * Copyright 2002-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.web.servlet.view;
018:
019: import java.util.HashMap;
020: import java.util.Locale;
021: import java.util.Map;
022: import java.util.Properties;
023:
024: import org.springframework.beans.BeanUtils;
025: import org.springframework.core.Ordered;
026: import org.springframework.util.PatternMatchUtils;
027: import org.springframework.web.servlet.View;
028:
029: /**
030: * Simple implementation of the {@link org.springframework.web.servlet.ViewResolver}
031: * interface, allowing for direct resolution of symbolic view names to URLs,
032: * without explicit mapping definition. This is useful if your symbolic names
033: * match the names of your view resources in a straightforward manner
034: * (i.e. the symbolic name is the unique part of the resource's filename),
035: * without the need for a dedicated mapping to be defined for each view.
036: *
037: * <p>Supports {@link AbstractUrlBasedView} subclasses like {@link InternalResourceView},
038: * {@link org.springframework.web.servlet.view.velocity.VelocityView} and
039: * {@link org.springframework.web.servlet.view.freemarker.FreeMarkerView}.
040: * The view class for all views generated by this resolver can be specified
041: * via the "viewClass" property.
042: *
043: * <p>View names can either be resource URLs themselves, or get augmented by a
044: * specified prefix and/or suffix. Exporting an attribute that holds the
045: * RequestContext to all views is explicitly supported.
046: *
047: * <p>Example: prefix="/WEB-INF/jsp/", suffix=".jsp", viewname="test" ->
048: * "/WEB-INF/jsp/test.jsp"
049: *
050: * <p>As a special feature, redirect URLs can be specified via the "redirect:"
051: * prefix. E.g.: "redirect:myAction.do" will trigger a redirect to the given
052: * URL, rather than resolution as standard view name. This is typically used
053: * for redirecting to a controller URL after finishing a form workflow.
054: *
055: * <p>Furthermore, forward URLs can be specified via the "forward:" prefix. E.g.:
056: * "forward:myAction.do" will trigger a forward to the given URL, rather than
057: * resolution as standard view name. This is typically used for controller URLs;
058: * it is not supposed to be used for JSP URLs - use logical view names there.
059: *
060: * <p>Note: This class does not support localized resolution, i.e. resolving
061: * a symbolic view name to different resources depending on the current locale.
062: *
063: * <p>Note: When chaining ViewResolvers, a UrlBasedViewResolver always needs
064: * to be last, as it will attempt to resolve any view name, no matter whether
065: * the underlying resource actually exists.
066: *
067: * @author Juergen Hoeller
068: * @author Rob Harrop
069: * @since 13.12.2003
070: * @see #setViewClass
071: * @see #setPrefix
072: * @see #setSuffix
073: * @see #setRequestContextAttribute
074: * @see #REDIRECT_URL_PREFIX
075: * @see AbstractUrlBasedView
076: * @see InternalResourceView
077: * @see org.springframework.web.servlet.view.velocity.VelocityView
078: * @see org.springframework.web.servlet.view.freemarker.FreeMarkerView
079: */
080: public class UrlBasedViewResolver extends AbstractCachingViewResolver
081: implements Ordered {
082:
083: /**
084: * Prefix for special view names that specify a redirect URL (usually
085: * to a controller after a form has been submitted and processed).
086: * Such view names will not be resolved in the configured default
087: * way but rather be treated as special shortcut.
088: */
089: public static final String REDIRECT_URL_PREFIX = "redirect:";
090:
091: /**
092: * Prefix for special view names that specify a forward URL (usually
093: * to a controller after a form has been submitted and processed).
094: * Such view names will not be resolved in the configured default
095: * way but rather be treated as special shortcut.
096: */
097: public static final String FORWARD_URL_PREFIX = "forward:";
098:
099: private Class viewClass;
100:
101: private String prefix = "";
102:
103: private String suffix = "";
104:
105: private String[] viewNames = null;
106:
107: private String contentType;
108:
109: private boolean redirectContextRelative = true;
110:
111: private boolean redirectHttp10Compatible = true;
112:
113: private String requestContextAttribute;
114:
115: private int order = Integer.MAX_VALUE;
116:
117: /** Map of static attributes, keyed by attribute name (String) */
118: private final Map staticAttributes = new HashMap();
119:
120: /**
121: * Set the view class that should be used to create views.
122: * @param viewClass class that is assignable to the required view class
123: * (by default, AbstractUrlBasedView)
124: * @see AbstractUrlBasedView
125: */
126: public void setViewClass(Class viewClass) {
127: if (viewClass == null
128: || !requiredViewClass().isAssignableFrom(viewClass)) {
129: throw new IllegalArgumentException("Given view class ["
130: + (viewClass != null ? viewClass.getName() : null)
131: + "] is not of type ["
132: + requiredViewClass().getName() + "]");
133: }
134: this .viewClass = viewClass;
135: }
136:
137: /**
138: * Return the view class to be used to create views.
139: */
140: protected Class getViewClass() {
141: return this .viewClass;
142: }
143:
144: /**
145: * Return the required type of view for this resolver.
146: * This implementation returns AbstractUrlBasedView.
147: * @see AbstractUrlBasedView
148: */
149: protected Class requiredViewClass() {
150: return AbstractUrlBasedView.class;
151: }
152:
153: /**
154: * Set the prefix that gets prepended to view names when building a URL.
155: */
156: public void setPrefix(String prefix) {
157: this .prefix = (prefix != null ? prefix : "");
158: }
159:
160: /**
161: * Return the prefix that gets prepended to view names when building a URL.
162: */
163: protected String getPrefix() {
164: return this .prefix;
165: }
166:
167: /**
168: * Set the suffix that gets appended to view names when building a URL.
169: */
170: public void setSuffix(String suffix) {
171: this .suffix = (suffix != null ? suffix : "");
172: }
173:
174: /**
175: * Return the suffix that gets appended to view names when building a URL.
176: */
177: protected String getSuffix() {
178: return this .suffix;
179: }
180:
181: /**
182: * Set the content type for all views.
183: * <p>May be ignored by view classes if the view itself is assumed
184: * to set the content type, e.g. in case of JSPs.
185: */
186: public void setContentType(String contentType) {
187: this .contentType = contentType;
188: }
189:
190: /**
191: * Return the content type for all views, if any.
192: */
193: protected String getContentType() {
194: return this .contentType;
195: }
196:
197: /**
198: * Set whether to interpret a given redirect URL that starts with a
199: * slash ("/") as relative to the current ServletContext, i.e. as
200: * relative to the web application root.
201: * <p>Default is "true": A redirect URL that starts with a slash will be
202: * interpreted as relative to the web application root, i.e. the context
203: * path will be prepended to the URL.
204: * <p><b>Redirect URLs can be specified via the "redirect:" prefix.</b>
205: * E.g.: "redirect:myAction.do"
206: * @see RedirectView#setContextRelative
207: * @see #REDIRECT_URL_PREFIX
208: */
209: public void setRedirectContextRelative(
210: boolean redirectContextRelative) {
211: this .redirectContextRelative = redirectContextRelative;
212: }
213:
214: /**
215: * Return whether to interpret a given redirect URL that starts with a
216: * slash ("/") as relative to the current ServletContext, i.e. as
217: * relative to the web application root.
218: */
219: protected boolean isRedirectContextRelative() {
220: return this .redirectContextRelative;
221: }
222:
223: /**
224: * Set whether redirects should stay compatible with HTTP 1.0 clients.
225: * <p>In the default implementation, this will enforce HTTP status code 302
226: * in any case, i.e. delegate to <code>HttpServletResponse.sendRedirect</code>.
227: * Turning this off will send HTTP status code 303, which is the correct
228: * code for HTTP 1.1 clients, but not understood by HTTP 1.0 clients.
229: * <p>Many HTTP 1.1 clients treat 302 just like 303, not making any
230: * difference. However, some clients depend on 303 when redirecting
231: * after a POST request; turn this flag off in such a scenario.
232: * <p><b>Redirect URLs can be specified via the "redirect:" prefix.</b>
233: * E.g.: "redirect:myAction.do"
234: * @see RedirectView#setHttp10Compatible
235: * @see #REDIRECT_URL_PREFIX
236: */
237: public void setRedirectHttp10Compatible(
238: boolean redirectHttp10Compatible) {
239: this .redirectHttp10Compatible = redirectHttp10Compatible;
240: }
241:
242: /**
243: * Return whether redirects should stay compatible with HTTP 1.0 clients.
244: */
245: protected boolean isRedirectHttp10Compatible() {
246: return this .redirectHttp10Compatible;
247: }
248:
249: /**
250: * Set the name of the RequestContext attribute for all views.
251: * @param requestContextAttribute name of the RequestContext attribute
252: * @see AbstractView#setRequestContextAttribute
253: */
254: public void setRequestContextAttribute(
255: String requestContextAttribute) {
256: this .requestContextAttribute = requestContextAttribute;
257: }
258:
259: /**
260: * Return the name of the RequestContext attribute for all views, if any.
261: */
262: protected String getRequestContextAttribute() {
263: return this .requestContextAttribute;
264: }
265:
266: /**
267: * Set static attributes from a <code>java.util.Properties</code> object,
268: * for all views returned by this resolver.
269: * <p>This is the most convenient way to set static attributes. Note that
270: * static attributes can be overridden by dynamic attributes, if a value
271: * with the same name is included in the model.
272: * <p>Can be populated with a String "value" (parsed via PropertiesEditor)
273: * or a "props" element in XML bean definitions.
274: * @see org.springframework.beans.propertyeditors.PropertiesEditor
275: * @see AbstractView#setAttributes
276: */
277: public void setAttributes(Properties props) {
278: setAttributesMap(props);
279: }
280:
281: /**
282: * Set static attributes from a Map, for all views returned by this resolver.
283: * This allows to set any kind of attribute values, for example bean references.
284: * <p>Can be populated with a "map" or "props" element in XML bean definitions.
285: * @param attributes Map with name Strings as keys and attribute objects as values
286: * @see AbstractView#setAttributesMap
287: */
288: public void setAttributesMap(Map attributes) {
289: if (attributes != null) {
290: this .staticAttributes.putAll(attributes);
291: }
292: }
293:
294: /**
295: * Allow Map access to the static attributes for views returned by
296: * this resolver, with the option to add or override specific entries.
297: * <p>Useful for specifying entries directly, for example via
298: * "attributesMap[myKey]". This is particularly useful for
299: * adding or overriding entries in child view definitions.
300: */
301: public Map getAttributesMap() {
302: return this .staticAttributes;
303: }
304:
305: /**
306: * Set the view names (or name patterns) that can be handled by this
307: * {@link org.springframework.web.servlet.ViewResolver}. View names can contain
308: * simple wildcards such that 'my*', '*Report' and '*Repo*' will all match the
309: * view name 'myReport'.
310: * @see #canHandle
311: */
312: public void setViewNames(String[] viewNames) {
313: this .viewNames = viewNames;
314: }
315:
316: /**
317: * Return the view names (or name patterns) that can be handled by this
318: * {@link org.springframework.web.servlet.ViewResolver}.
319: */
320: protected String[] getViewNames() {
321: return this .viewNames;
322: }
323:
324: /**
325: * Set the order in which this {@link org.springframework.web.servlet.ViewResolver}
326: * is evaluated.
327: */
328: public void setOrder(int order) {
329: this .order = order;
330: }
331:
332: /**
333: * Return the order in which this {@link org.springframework.web.servlet.ViewResolver}
334: * is evaluated.
335: */
336: public int getOrder() {
337: return this .order;
338: }
339:
340: protected void initApplicationContext() {
341: super .initApplicationContext();
342: if (getViewClass() == null) {
343: throw new IllegalArgumentException(
344: "Property 'viewClass' is required");
345: }
346: }
347:
348: /**
349: * This implementation returns just the view name,
350: * as this ViewResolver doesn't support localized resolution.
351: */
352: protected Object getCacheKey(String viewName, Locale locale) {
353: return viewName;
354: }
355:
356: /**
357: * Overridden to implement check for "redirect:" prefix.
358: * <p>Not possible in <code>loadView</code>, since overridden
359: * <code>loadView</code> versions in subclasses might rely on the
360: * superclass always creating instances of the required view class.
361: * @see #loadView
362: * @see #requiredViewClass
363: */
364: protected View createView(String viewName, Locale locale)
365: throws Exception {
366: // If this resolver is not supposed to handle the given view,
367: // return null to pass on to the next resolver in the chain.
368: if (!canHandle(viewName, locale)) {
369: return null;
370: }
371: // Check for special "redirect:" prefix.
372: if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
373: String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX
374: .length());
375: return new RedirectView(redirectUrl,
376: isRedirectContextRelative(),
377: isRedirectHttp10Compatible());
378: }
379: // Check for special "forward:" prefix.
380: if (viewName.startsWith(FORWARD_URL_PREFIX)) {
381: String forwardUrl = viewName.substring(FORWARD_URL_PREFIX
382: .length());
383: return new InternalResourceView(forwardUrl);
384: }
385: // Else fall back to superclass implementation: calling loadView.
386: return super .createView(viewName, locale);
387: }
388:
389: /**
390: * Indicates whether or not this {@link org.springframework.web.servlet.ViewResolver} can
391: * handle the supplied view name. If not, {@link #createView(String, java.util.Locale)} will
392: * return <code>null</code>. The default implementation checks against the configured
393: * {@link #setViewNames view names}.
394: * @param viewName the name of the view to retrieve
395: * @param locale the Locale to retrieve the view for
396: * @return whether this resolver applies to the specified view
397: * @see org.springframework.util.PatternMatchUtils#simpleMatch(String, String)
398: */
399: protected boolean canHandle(String viewName, Locale locale) {
400: String[] viewNames = getViewNames();
401: return (viewNames == null || PatternMatchUtils.simpleMatch(
402: viewNames, viewName));
403: }
404:
405: /**
406: * Delegates to <code>buildView</code> for creating a new instance of the
407: * specified view class, and applies the following Spring lifecycle methods
408: * (as supported by the generic Spring bean factory):
409: * <ul>
410: * <li>ApplicationContextAware's <code>setApplicationContext</code>
411: * <li>InitializingBean's <code>afterPropertiesSet</code>
412: * </ul>
413: * @param viewName the name of the view to retrieve
414: * @return the View instance
415: * @throws Exception if the view couldn't be resolved
416: * @see #buildView(String)
417: * @see org.springframework.context.ApplicationContextAware#setApplicationContext
418: * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet
419: */
420: protected View loadView(String viewName, Locale locale)
421: throws Exception {
422: AbstractUrlBasedView view = buildView(viewName);
423: return (View) getApplicationContext()
424: .getAutowireCapableBeanFactory().initializeBean(view,
425: viewName);
426: }
427:
428: /**
429: * Creates a new View instance of the specified view class and configures it.
430: * Does <i>not</i> perform any lookup for pre-defined View instances.
431: * <p>Spring lifecycle methods as defined by the bean container do not have to
432: * be called here; those will be applied by the <code>loadView</code> method
433: * after this method returns.
434: * <p>Subclasses will typically call <code>super.buildView(viewName)</code>
435: * first, before setting further properties themselves. <code>loadView</code>
436: * will then apply Spring lifecycle methods at the end of this process.
437: * @param viewName the name of the view to build
438: * @return the View instance
439: * @throws Exception if the view couldn't be resolved
440: * @see #loadView(String, java.util.Locale)
441: */
442: protected AbstractUrlBasedView buildView(String viewName)
443: throws Exception {
444: AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils
445: .instantiateClass(getViewClass());
446: view.setUrl(getPrefix() + viewName + getSuffix());
447: String contentType = getContentType();
448: if (contentType != null) {
449: view.setContentType(contentType);
450: }
451: view.setRequestContextAttribute(getRequestContextAttribute());
452: view.setAttributesMap(getAttributesMap());
453: return view;
454: }
455:
456: }
|