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.io.IOException;
020: import java.io.UnsupportedEncodingException;
021: import java.net.URLEncoder;
022: import java.util.Iterator;
023: import java.util.Map;
024:
025: import javax.servlet.http.HttpServletRequest;
026: import javax.servlet.http.HttpServletResponse;
027:
028: import org.springframework.core.JdkVersion;
029:
030: /**
031: * <p>View that redirects to an absolute, context relative, or current request
032: * relative URL, exposing all model attributes as HTTP query parameters.
033: *
034: * <p>A URL for this view is supposed to be a HTTP redirect URL, i.e.
035: * suitable for HttpServletResponse's <code>sendRedirect</code> method, which
036: * is what actually does the redirect if the HTTP 1.0 flag is on, or via sending
037: * back an HTTP 303 code - if the HTTP 1.0 compatibility flag is off.
038: *
039: * <p>Note that while the default value for the "contextRelative" flag is off,
040: * you will probably want to almost always set it to true. With the flag off,
041: * URLs starting with "/" are considered relative to the web server root, while
042: * with the flag on, they are considered relative to the web application root.
043: * Since most web apps will never know or care what their context path actually
044: * is, they are much better off setting this flag to true, and submitting paths
045: * which are to be considered relative to the web application root.
046: *
047: * <p>Note that in a Servlet 2.2 environment, i.e. a servlet container which
048: * is only compliant to the limits of this spec, this class will probably fail
049: * when feeding in URLs which are not fully absolute, or relative to the current
050: * request (no leading "/"), as these are the only two types of URL that
051: * <code>sendRedirect</code> supports in a Servlet 2.2 environment.
052: *
053: * @author Rod Johnson
054: * @author Juergen Hoeller
055: * @author Colin Sampaleanu
056: * @see #setContextRelative
057: * @see #setHttp10Compatible
058: * @see javax.servlet.http.HttpServletResponse#sendRedirect
059: */
060: public class RedirectView extends AbstractUrlBasedView {
061:
062: /** The default encoding scheme: UTF-8 */
063: public static final String DEFAULT_ENCODING_SCHEME = "UTF-8";
064:
065: private boolean contextRelative = false;
066:
067: private boolean http10Compatible = true;
068:
069: private String encodingScheme = DEFAULT_ENCODING_SCHEME;
070:
071: /**
072: * Constructor for use as a bean.
073: */
074: public RedirectView() {
075: }
076:
077: /**
078: * Create a new RedirectView with the given URL.
079: * <p>The given URL will be considered as relative to the web server,
080: * not as relative to the current ServletContext.
081: * @param url the URL to redirect to
082: * @see #RedirectView(String, boolean)
083: */
084: public RedirectView(String url) {
085: super (url);
086: }
087:
088: /**
089: * Create a new RedirectView with the given URL.
090: * @param url the URL to redirect to
091: * @param contextRelative whether to interpret the given URL as
092: * relative to the current ServletContext
093: */
094: public RedirectView(String url, boolean contextRelative) {
095: super (url);
096: this .contextRelative = contextRelative;
097: }
098:
099: /**
100: * Create a new RedirectView with the given URL.
101: * @param url the URL to redirect to
102: * @param contextRelative whether to interpret the given URL as
103: * relative to the current ServletContext
104: * @param http10Compatible whether to stay compatible with HTTP 1.0 clients
105: */
106: public RedirectView(String url, boolean contextRelative,
107: boolean http10Compatible) {
108: super (url);
109: this .contextRelative = contextRelative;
110: this .http10Compatible = http10Compatible;
111: }
112:
113: /**
114: * Set whether to interpret a given URL that starts with a slash ("/")
115: * as relative to the current ServletContext, i.e. as relative to the
116: * web application root.
117: * <p>Default is "false": A URL that starts with a slash will be interpreted
118: * as absolute, i.e. taken as-is. If true, the context path will be
119: * prepended to the URL in such a case.
120: * @see javax.servlet.http.HttpServletRequest#getContextPath
121: */
122: public void setContextRelative(boolean contextRelative) {
123: this .contextRelative = contextRelative;
124: }
125:
126: /**
127: * Set whether to stay compatible with HTTP 1.0 clients.
128: * <p>In the default implementation, this will enforce HTTP status code 302
129: * in any case, i.e. delegate to <code>HttpServletResponse.sendRedirect</code>.
130: * Turning this off will send HTTP status code 303, which is the correct
131: * code for HTTP 1.1 clients, but not understood by HTTP 1.0 clients.
132: * <p>Many HTTP 1.1 clients treat 302 just like 303, not making any
133: * difference. However, some clients depend on 303 when redirecting
134: * after a POST request; turn this flag off in such a scenario.
135: * @see javax.servlet.http.HttpServletResponse#sendRedirect
136: */
137: public void setHttp10Compatible(boolean http10Compatible) {
138: this .http10Compatible = http10Compatible;
139: }
140:
141: /**
142: * Set the encoding scheme for this view. Default is UTF-8.
143: */
144: public void setEncodingScheme(String encodingScheme) {
145: this .encodingScheme = encodingScheme;
146: }
147:
148: /**
149: * Convert model to request parameters and redirect to the given URL.
150: * @see #appendQueryProperties
151: * @see #sendRedirect
152: */
153: protected final void renderMergedOutputModel(Map model,
154: HttpServletRequest request, HttpServletResponse response)
155: throws IOException {
156:
157: // Prepare target URL.
158: StringBuffer targetUrl = new StringBuffer();
159: if (this .contextRelative && getUrl().startsWith("/")) {
160: // Do not apply context path to relative URLs.
161: targetUrl.append(request.getContextPath());
162: }
163: targetUrl.append(getUrl());
164: appendQueryProperties(targetUrl, model, this .encodingScheme);
165:
166: sendRedirect(request, response, targetUrl.toString(),
167: this .http10Compatible);
168: }
169:
170: /**
171: * Append query properties to the redirect URL.
172: * Stringifies, URL-encodes and formats model attributes as query properties.
173: * @param targetUrl the StringBuffer to append the properties to
174: * @param model Map that contains model attributes
175: * @param encodingScheme the encoding scheme to use
176: * @throws UnsupportedEncodingException if string encoding failed
177: * @see #queryProperties
178: */
179: protected void appendQueryProperties(StringBuffer targetUrl,
180: Map model, String encodingScheme)
181: throws UnsupportedEncodingException {
182:
183: // Extract anchor fragment, if any.
184: // The following code does not use JDK 1.4's StringBuffer.indexOf(String)
185: // method to retain JDK 1.3 compatibility.
186: String fragment = null;
187: int anchorIndex = targetUrl.toString().indexOf('#');
188: if (anchorIndex > -1) {
189: fragment = targetUrl.substring(anchorIndex);
190: targetUrl.delete(anchorIndex, targetUrl.length());
191: }
192:
193: // If there aren't already some parameters, we need a "?".
194: boolean first = (getUrl().indexOf('?') < 0);
195: Iterator entries = queryProperties(model).entrySet().iterator();
196: while (entries.hasNext()) {
197: if (first) {
198: targetUrl.append('?');
199: first = false;
200: } else {
201: targetUrl.append('&');
202: }
203: Map.Entry entry = (Map.Entry) entries.next();
204: String encodedKey = urlEncode(entry.getKey().toString(),
205: encodingScheme);
206: String encodedValue = (entry.getValue() != null ? urlEncode(
207: entry.getValue().toString(), encodingScheme)
208: : "");
209: targetUrl.append(encodedKey).append('=').append(
210: encodedValue);
211: }
212:
213: // Append anchor fragment, if any, to end of URL.
214: if (fragment != null) {
215: targetUrl.append(fragment);
216: }
217: }
218:
219: /**
220: * URL-encode the given input String with the given encoding scheme.
221: * <p>Default implementation uses <code>URLEncoder.encode(input, enc)</code>
222: * on JDK 1.4+, falling back to <code>URLEncoder.encode(input)</code>
223: * (which uses the platform default encoding) on JDK 1.3.
224: * @param input the unencoded input String
225: * @param encodingScheme the encoding scheme
226: * @return the encoded output String
227: * @throws UnsupportedEncodingException if thrown by the JDK URLEncoder
228: * @see java.net.URLEncoder#encode(String, String)
229: * @see java.net.URLEncoder#encode(String)
230: */
231: protected String urlEncode(String input, String encodingScheme)
232: throws UnsupportedEncodingException {
233: if (JdkVersion.getMajorJavaVersion() < JdkVersion.JAVA_14) {
234: if (logger.isDebugEnabled()) {
235: logger
236: .debug("Only JDK 1.3 URLEncoder available: using platform default encoding "
237: + "instead of the requested scheme '"
238: + encodingScheme + "'");
239: }
240: return URLEncoder.encode(input);
241: }
242: return URLEncoder.encode(input, encodingScheme);
243: }
244:
245: /**
246: * Determine name-value pairs for query strings, which will be stringified,
247: * URL-encoded and formatted by appendQueryProperties.
248: * <p>This implementation returns all model elements as-is.
249: * @see #appendQueryProperties
250: */
251: protected Map queryProperties(Map model) {
252: return model;
253: }
254:
255: /**
256: * Send a redirect back to the HTTP client
257: * @param request current HTTP request (allows for reacting to request method)
258: * @param response current HTTP response (for sending response headers)
259: * @param targetUrl the target URL to redirect to
260: * @param http10Compatible whether to stay compatible with HTTP 1.0 clients
261: * @throws IOException if thrown by response methods
262: */
263: protected void sendRedirect(HttpServletRequest request,
264: HttpServletResponse response, String targetUrl,
265: boolean http10Compatible) throws IOException {
266:
267: if (http10Compatible) {
268: // Always send status code 302.
269: response
270: .sendRedirect(response.encodeRedirectURL(targetUrl));
271: } else {
272: // Correct HTTP status code is 303, in particular for POST requests.
273: response.setStatus(303);
274: response.setHeader("Location", response
275: .encodeRedirectURL(targetUrl));
276: }
277: }
278:
279: }
|