/* * Copyright 2008 biaoping.yin * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.frameworkset.web.servlet.view; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.lang.reflect.Array; import java.net.URLEncoder; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.frameworkset.util.ObjectUtils; import org.frameworkset.web.util.WebUtils; import com.frameworkset.util.BeanUtils; /** * <p>Title: RedirectView.java</p> * <p>Description: </p> * <p>bboss workgroup</p> * <p>Copyright (c) 2008</p> * @Date 2010-9-29 * @author biaoping.yin * @version 1.0 */ public class RedirectView extends AbstractUrlBasedView { private boolean contextRelative = true; private boolean http10Compatible = true; private boolean exposeModelAttributes = true; private String encodingScheme; /** * Constructor for use as a bean. */ public RedirectView() { } /** * Create a new RedirectView with the given URL. * <p>The given URL will be considered as relative to the web server, * not as relative to the current ServletContext. * @param url the URL to redirect to * @see #RedirectView(String, boolean) */ public RedirectView(String url) { super(url); } /** * Create a new RedirectView with the given URL. * @param url the URL to redirect to * @param contextRelative whether to interpret the given URL as * relative to the current ServletContext */ public RedirectView(String url, boolean contextRelative) { super(url); this.contextRelative = contextRelative; } /** * Create a new RedirectView with the given URL. * @param url the URL to redirect to * @param contextRelative whether to interpret the given URL as * relative to the current ServletContext * @param http10Compatible whether to stay compatible with HTTP 1.0 clients */ public RedirectView(String url, boolean contextRelative, boolean http10Compatible) { super(url); this.contextRelative = contextRelative; this.http10Compatible = http10Compatible; } /** * Create a new RedirectView with the given URL. * @param url the URL to redirect to * @param contextRelative whether to interpret the given URL as * relative to the current ServletContext * @param http10Compatible whether to stay compatible with HTTP 1.0 clients * @param exposeModelAttributes whether or not model attributes should be * exposed as query parameters */ public RedirectView(String url, boolean contextRelative, boolean http10Compatible, boolean exposeModelAttributes) { super(url); this.contextRelative = contextRelative; this.http10Compatible = http10Compatible; this.exposeModelAttributes = exposeModelAttributes; } /** * Set whether to interpret a given URL that starts with a slash ("/") * as relative to the current ServletContext, i.e. as relative to the * web application root. * <p>Default is "false": A URL that starts with a slash will be interpreted * as absolute, i.e. taken as-is. If "true", the context path will be * prepended to the URL in such a case. * @see javax.servlet.http.HttpServletRequest#getContextPath */ public void setContextRelative(boolean contextRelative) { this.contextRelative = contextRelative; } /** * Set whether to stay compatible with HTTP 1.0 clients. * <p>In the default implementation, this will enforce HTTP status code 302 * in any case, i.e. delegate to <code>HttpServletResponse.sendRedirect</code>. * Turning this off will send HTTP status code 303, which is the correct * code for HTTP 1.1 clients, but not understood by HTTP 1.0 clients. * <p>Many HTTP 1.1 clients treat 302 just like 303, not making any * difference. However, some clients depend on 303 when redirecting * after a POST request; turn this flag off in such a scenario. * @see javax.servlet.http.HttpServletResponse#sendRedirect */ public void setHttp10Compatible(boolean http10Compatible) { this.http10Compatible = http10Compatible; } /** * Set the <code>exposeModelAttributes</code> flag which denotes whether * or not model attributes should be exposed as HTTP query parameters. * <p>Defaults to <code>true</code>. */ public void setExposeModelAttributes(final boolean exposeModelAttributes) { this.exposeModelAttributes = exposeModelAttributes; } /** * Set the encoding scheme for this view. * <p>Default is the request's encoding scheme * (which is ISO-8859-1 if not specified otherwise). */ public void setEncodingScheme(String encodingScheme) { this.encodingScheme = encodingScheme; } /** * Convert model to request parameters and redirect to the given URL. * @see #appendQueryProperties * @see #sendRedirect */ protected void renderMergedOutputModel( Map model, HttpServletRequest request, HttpServletResponse response) throws IOException { // Prepare target URL. StringBuffer targetUrl = new StringBuffer(); if (this.contextRelative && getUrl().startsWith("/")) { // Do not apply context path to relative URLs. targetUrl.append(request.getContextPath()); } targetUrl.append(getUrl()); if (this.exposeModelAttributes) { String enc = this.encodingScheme; if (enc == null) { enc = request.getCharacterEncoding(); } if (enc == null) { enc = WebUtils.DEFAULT_CHARACTER_ENCODING; } appendQueryProperties(targetUrl, model, enc); } sendRedirect(request, response, targetUrl.toString(), this.http10Compatible); } /** * Append query properties to the redirect URL. * Stringifies, URL-encodes and formats model attributes as query properties. * @param targetUrl the StringBuffer to append the properties to * @param model Map that contains model attributes * @param encodingScheme the encoding scheme to use * @throws UnsupportedEncodingException if string encoding failed * @see #queryProperties */ protected void appendQueryProperties(StringBuffer targetUrl, Map model, String encodingScheme) throws UnsupportedEncodingException { // Extract anchor fragment, if any. String fragment = null; int anchorIndex = targetUrl.indexOf("#"); if (anchorIndex > -1) { fragment = targetUrl.substring(anchorIndex); targetUrl.delete(anchorIndex, targetUrl.length()); } // If there aren't already some parameters, we need a "?". boolean first = (getUrl().indexOf('?') < 0); Iterator entries = queryProperties(model).entrySet().iterator(); while (entries.hasNext()) { Map.Entry entry = (Map.Entry) entries.next(); String key = entry.getKey().toString(); Object rawValue = entry.getValue(); Iterator valueIter = null; if (rawValue != null && rawValue.getClass().isArray()) { valueIter = Arrays.asList(ObjectUtils.toObjectArray(rawValue)).iterator(); } else if (rawValue instanceof Collection) { valueIter = ((Collection) rawValue).iterator(); } else { valueIter = Collections.singleton(rawValue).iterator(); } while (valueIter.hasNext()) { Object value = valueIter.next(); if (first) { targetUrl.append('?'); first = false; } else { targetUrl.append('&'); } String encodedKey = urlEncode(key, encodingScheme); String encodedValue = (value != null ? urlEncode(value.toString(), encodingScheme) : ""); targetUrl.append(encodedKey).append('=').append(encodedValue); } } // Append anchor fragment, if any, to end of URL. if (fragment != null) { targetUrl.append(fragment); } } /** * Determine name-value pairs for query strings, which will be stringified, * URL-encoded and formatted by {@link #appendQueryProperties}. * <p>This implementation filters the model through checking * {@link #isEligibleProperty(String, Object)} for each element, * by default accepting Strings, primitives and primitive wrappers only. * @param model the original model Map * @return the filtered Map of eligible query properties * @see #isEligibleProperty(String, Object) */ protected Map queryProperties(Map model) { Map result = new LinkedHashMap(); for (Iterator it = model.entrySet().iterator(); it.hasNext();) { Map.Entry entry = (Map.Entry) it.next(); String key = entry.getKey().toString(); Object value = entry.getValue(); if (isEligibleProperty(key, value)) { result.put(key, value); } } return result; } /** * Determine whether the given model element should be exposed * as a query property. * <p>The default implementation considers Strings and primitives * as eligible, and also arrays and Collections/Iterables with * corresponding elements. This can be overridden in subclasses. * @param key the key of the model element * @param value the value of the model element * @return whether the element is eligible as query property */ protected boolean isEligibleProperty(String key, Object value) { if (value == null) { return false; } if (isEligibleValue(value)) { return true; } if (value.getClass().isArray()) { int length = Array.getLength(value); if (length == 0) { return false; } for (int i = 0; i < length; i++) { Object element = Array.get(value, i); if (!isEligibleValue(element)) { return false; } } return true; } if (value instanceof Collection) { Collection coll = (Collection) value; if (coll.isEmpty()) { return false; } for (Iterator it = coll.iterator(); it.hasNext();) { Object element = it.next(); if (!isEligibleValue(element)) { return false; } } return true; } return false; } /** * Determine whether the given model element value is eligible for exposure. * <p>The default implementation considers primitives, Strings, Numbers, Dates, * URIs, URLs and Locale objects as eligible. This can be overridden in subclasses. * @param value the model element value * @return whether the element value is eligible * @see BeanUtils#isSimpleValueType */ protected boolean isEligibleValue(Object value) { return (value != null && BeanUtils.isSimpleValueType(value.getClass())); } /** * URL-encode the given input String with the given encoding scheme. * <p>The default implementation uses <code>URLEncoder.encode(input, enc)</code>. * @param input the unencoded input String * @param encodingScheme the encoding scheme * @return the encoded output String * @throws UnsupportedEncodingException if thrown by the JDK URLEncoder * @see java.net.URLEncoder#encode(String, String) * @see java.net.URLEncoder#encode(String) */ protected String urlEncode(String input, String encodingScheme) throws UnsupportedEncodingException { return (input != null ? URLEncoder.encode(input, encodingScheme) : null); } /** * Send a redirect back to the HTTP client * @param request current HTTP request (allows for reacting to request method) * @param response current HTTP response (for sending response headers) * @param targetUrl the target URL to redirect to * @param http10Compatible whether to stay compatible with HTTP 1.0 clients * @throws IOException if thrown by response methods */ protected void sendRedirect( HttpServletRequest request, HttpServletResponse response, String targetUrl, boolean http10Compatible) throws IOException { if (http10Compatible) { // Always send status code 302. response.sendRedirect(response.encodeRedirectURL(targetUrl)); } else { // Correct HTTP status code is 303, in particular for POST requests. response.setStatus(303); response.setHeader("Location", response.encodeRedirectURL(targetUrl)); } } }