001/*
002 * Copyright 2002-2020 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 *      https://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
017package org.springframework.web.servlet.view;
018
019import java.io.IOException;
020import java.io.UnsupportedEncodingException;
021import java.lang.reflect.Array;
022import java.net.URLEncoder;
023import java.util.Collection;
024import java.util.Collections;
025import java.util.LinkedHashMap;
026import java.util.Map;
027import java.util.regex.Matcher;
028import java.util.regex.Pattern;
029
030import javax.servlet.http.HttpServletRequest;
031import javax.servlet.http.HttpServletResponse;
032
033import org.springframework.beans.BeanUtils;
034import org.springframework.http.HttpStatus;
035import org.springframework.lang.Nullable;
036import org.springframework.util.Assert;
037import org.springframework.util.CollectionUtils;
038import org.springframework.util.ObjectUtils;
039import org.springframework.util.StringUtils;
040import org.springframework.web.context.WebApplicationContext;
041import org.springframework.web.servlet.HandlerMapping;
042import org.springframework.web.servlet.SmartView;
043import org.springframework.web.servlet.View;
044import org.springframework.web.servlet.support.RequestContextUtils;
045import org.springframework.web.servlet.support.RequestDataValueProcessor;
046import org.springframework.web.util.UriComponentsBuilder;
047import org.springframework.web.util.UriUtils;
048import org.springframework.web.util.WebUtils;
049
050/**
051 * View that redirects to an absolute, context relative, or current request
052 * relative URL. The URL may be a URI template in which case the URI template
053 * variables will be replaced with values available in the model. By default
054 * all primitive model attributes (or collections thereof) are exposed as HTTP
055 * query parameters (assuming they've not been used as URI template variables),
056 * but this behavior can be changed by overriding the
057 * {@link #isEligibleProperty(String, Object)} method.
058 *
059 * <p>A URL for this view is supposed to be an HTTP redirect URL, i.e.
060 * suitable for HttpServletResponse's {@code sendRedirect} method, which
061 * is what actually does the redirect if the HTTP 1.0 flag is on, or via sending
062 * back an HTTP 303 code - if the HTTP 1.0 compatibility flag is off.
063 *
064 * <p>Note that while the default value for the "contextRelative" flag is off,
065 * you will probably want to almost always set it to true. With the flag off,
066 * URLs starting with "/" are considered relative to the web server root, while
067 * with the flag on, they are considered relative to the web application root.
068 * Since most web applications will never know or care what their context path
069 * actually is, they are much better off setting this flag to true, and submitting
070 * paths which are to be considered relative to the web application root.
071 *
072 * <p><b>NOTE when using this redirect view in a Portlet environment:</b> Make sure
073 * that your controller respects the Portlet {@code sendRedirect} constraints.
074 *
075 * @author Rod Johnson
076 * @author Juergen Hoeller
077 * @author Colin Sampaleanu
078 * @author Sam Brannen
079 * @author Arjen Poutsma
080 * @author Rossen Stoyanchev
081 * @see #setContextRelative
082 * @see #setHttp10Compatible
083 * @see #setExposeModelAttributes
084 * @see javax.servlet.http.HttpServletResponse#sendRedirect
085 */
086public class RedirectView extends AbstractUrlBasedView implements SmartView {
087
088        private static final Pattern URI_TEMPLATE_VARIABLE_PATTERN = Pattern.compile("\\{([^/]+?)\\}");
089
090
091        private boolean contextRelative = false;
092
093        private boolean http10Compatible = true;
094
095        private boolean exposeModelAttributes = true;
096
097        @Nullable
098        private String encodingScheme;
099
100        @Nullable
101        private HttpStatus statusCode;
102
103        private boolean expandUriTemplateVariables = true;
104
105        private boolean propagateQueryParams = false;
106
107        @Nullable
108        private String[] hosts;
109
110
111        /**
112         * Constructor for use as a bean.
113         */
114        public RedirectView() {
115                setExposePathVariables(false);
116        }
117
118        /**
119         * Create a new RedirectView with the given URL.
120         * <p>The given URL will be considered as relative to the web server,
121         * not as relative to the current ServletContext.
122         * @param url the URL to redirect to
123         * @see #RedirectView(String, boolean)
124         */
125        public RedirectView(String url) {
126                super(url);
127                setExposePathVariables(false);
128        }
129
130        /**
131         * Create a new RedirectView with the given URL.
132         * @param url the URL to redirect to
133         * @param contextRelative whether to interpret the given URL as
134         * relative to the current ServletContext
135         */
136        public RedirectView(String url, boolean contextRelative) {
137                super(url);
138                this.contextRelative = contextRelative;
139                setExposePathVariables(false);
140        }
141
142        /**
143         * Create a new RedirectView with the given URL.
144         * @param url the URL to redirect to
145         * @param contextRelative whether to interpret the given URL as
146         * relative to the current ServletContext
147         * @param http10Compatible whether to stay compatible with HTTP 1.0 clients
148         */
149        public RedirectView(String url, boolean contextRelative, boolean http10Compatible) {
150                super(url);
151                this.contextRelative = contextRelative;
152                this.http10Compatible = http10Compatible;
153                setExposePathVariables(false);
154        }
155
156        /**
157         * Create a new RedirectView with the given URL.
158         * @param url the URL to redirect to
159         * @param contextRelative whether to interpret the given URL as
160         * relative to the current ServletContext
161         * @param http10Compatible whether to stay compatible with HTTP 1.0 clients
162         * @param exposeModelAttributes whether or not model attributes should be
163         * exposed as query parameters
164         */
165        public RedirectView(String url, boolean contextRelative, boolean http10Compatible, boolean exposeModelAttributes) {
166                super(url);
167                this.contextRelative = contextRelative;
168                this.http10Compatible = http10Compatible;
169                this.exposeModelAttributes = exposeModelAttributes;
170                setExposePathVariables(false);
171        }
172
173
174        /**
175         * Set whether to interpret a given URL that starts with a slash ("/")
176         * as relative to the current ServletContext, i.e. as relative to the
177         * web application root.
178         * <p>Default is "false": A URL that starts with a slash will be interpreted
179         * as absolute, i.e. taken as-is. If "true", the context path will be
180         * prepended to the URL in such a case.
181         * @see javax.servlet.http.HttpServletRequest#getContextPath
182         */
183        public void setContextRelative(boolean contextRelative) {
184                this.contextRelative = contextRelative;
185        }
186
187        /**
188         * Set whether to stay compatible with HTTP 1.0 clients.
189         * <p>In the default implementation, this will enforce HTTP status code 302
190         * in any case, i.e. delegate to {@code HttpServletResponse.sendRedirect}.
191         * Turning this off will send HTTP status code 303, which is the correct
192         * code for HTTP 1.1 clients, but not understood by HTTP 1.0 clients.
193         * <p>Many HTTP 1.1 clients treat 302 just like 303, not making any
194         * difference. However, some clients depend on 303 when redirecting
195         * after a POST request; turn this flag off in such a scenario.
196         * @see javax.servlet.http.HttpServletResponse#sendRedirect
197         */
198        public void setHttp10Compatible(boolean http10Compatible) {
199                this.http10Compatible = http10Compatible;
200        }
201
202        /**
203         * Set the {@code exposeModelAttributes} flag which denotes whether
204         * or not model attributes should be exposed as HTTP query parameters.
205         * <p>Defaults to {@code true}.
206         */
207        public void setExposeModelAttributes(final boolean exposeModelAttributes) {
208                this.exposeModelAttributes = exposeModelAttributes;
209        }
210
211        /**
212         * Set the encoding scheme for this view.
213         * <p>Default is the request's encoding scheme
214         * (which is ISO-8859-1 if not specified otherwise).
215         */
216        public void setEncodingScheme(String encodingScheme) {
217                this.encodingScheme = encodingScheme;
218        }
219
220        /**
221         * Set the status code for this view.
222         * <p>Default is to send 302/303, depending on the value of the
223         * {@link #setHttp10Compatible(boolean) http10Compatible} flag.
224         */
225        public void setStatusCode(HttpStatus statusCode) {
226                this.statusCode = statusCode;
227        }
228
229        /**
230         * Whether to treat the redirect URL as a URI template.
231         * Set this flag to {@code false} if the redirect URL contains open
232         * and close curly braces "{", "}" and you don't want them interpreted
233         * as URI variables.
234         * <p>Defaults to {@code true}.
235         */
236        public void setExpandUriTemplateVariables(boolean expandUriTemplateVariables) {
237                this.expandUriTemplateVariables = expandUriTemplateVariables;
238        }
239
240        /**
241         * When set to {@code true} the query string of the current URL is appended
242         * and thus propagated through to the redirected URL.
243         * <p>Defaults to {@code false}.
244         * @since 4.1
245         */
246        public void setPropagateQueryParams(boolean propagateQueryParams) {
247                this.propagateQueryParams = propagateQueryParams;
248        }
249
250        /**
251         * Whether to propagate the query params of the current URL.
252         * @since 4.1
253         */
254        public boolean isPropagateQueryProperties() {
255                return this.propagateQueryParams;
256        }
257
258        /**
259         * Configure one or more hosts associated with the application.
260         * All other hosts will be considered external hosts.
261         * <p>In effect, this property provides a way turn off encoding via
262         * {@link HttpServletResponse#encodeRedirectURL} for URLs that have a
263         * host and that host is not listed as a known host.
264         * <p>If not set (the default) all URLs are encoded through the response.
265         * @param hosts one or more application hosts
266         * @since 4.3
267         */
268        public void setHosts(@Nullable String... hosts) {
269                this.hosts = hosts;
270        }
271
272        /**
273         * Return the configured application hosts.
274         * @since 4.3
275         */
276        @Nullable
277        public String[] getHosts() {
278                return this.hosts;
279        }
280
281        /**
282         * Returns "true" indicating this view performs a redirect.
283         */
284        @Override
285        public boolean isRedirectView() {
286                return true;
287        }
288
289        /**
290         * An ApplicationContext is not strictly required for RedirectView.
291         */
292        @Override
293        protected boolean isContextRequired() {
294                return false;
295        }
296
297
298        /**
299         * Convert model to request parameters and redirect to the given URL.
300         * @see #appendQueryProperties
301         * @see #sendRedirect
302         */
303        @Override
304        protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
305                        HttpServletResponse response) throws IOException {
306
307                String targetUrl = createTargetUrl(model, request);
308                targetUrl = updateTargetUrl(targetUrl, model, request, response);
309
310                // Save flash attributes
311                RequestContextUtils.saveOutputFlashMap(targetUrl, request, response);
312
313                // Redirect
314                sendRedirect(request, response, targetUrl, this.http10Compatible);
315        }
316
317        /**
318         * Create the target URL by checking if the redirect string is a URI template first,
319         * expanding it with the given model, and then optionally appending simple type model
320         * attributes as query String parameters.
321         */
322        protected final String createTargetUrl(Map<String, Object> model, HttpServletRequest request)
323                        throws UnsupportedEncodingException {
324
325                // Prepare target URL.
326                StringBuilder targetUrl = new StringBuilder();
327                String url = getUrl();
328                Assert.state(url != null, "'url' not set");
329
330                if (this.contextRelative && getUrl().startsWith("/")) {
331                        // Do not apply context path to relative URLs.
332                        targetUrl.append(getContextPath(request));
333                }
334                targetUrl.append(getUrl());
335
336                String enc = this.encodingScheme;
337                if (enc == null) {
338                        enc = request.getCharacterEncoding();
339                }
340                if (enc == null) {
341                        enc = WebUtils.DEFAULT_CHARACTER_ENCODING;
342                }
343
344                if (this.expandUriTemplateVariables && StringUtils.hasText(targetUrl)) {
345                        Map<String, String> variables = getCurrentRequestUriVariables(request);
346                        targetUrl = replaceUriTemplateVariables(targetUrl.toString(), model, variables, enc);
347                }
348                if (isPropagateQueryProperties()) {
349                        appendCurrentQueryParams(targetUrl, request);
350                }
351                if (this.exposeModelAttributes) {
352                        appendQueryProperties(targetUrl, model, enc);
353                }
354
355                return targetUrl.toString();
356        }
357
358        private String getContextPath(HttpServletRequest request) {
359                String contextPath = request.getContextPath();
360                while (contextPath.startsWith("//")) {
361                        contextPath = contextPath.substring(1);
362                }
363                return contextPath;
364        }
365
366        /**
367         * Replace URI template variables in the target URL with encoded model
368         * attributes or URI variables from the current request. Model attributes
369         * referenced in the URL are removed from the model.
370         * @param targetUrl the redirect URL
371         * @param model a Map that contains model attributes
372         * @param currentUriVariables current request URI variables to use
373         * @param encodingScheme the encoding scheme to use
374         * @throws UnsupportedEncodingException if string encoding failed
375         */
376        protected StringBuilder replaceUriTemplateVariables(
377                        String targetUrl, Map<String, Object> model, Map<String, String> currentUriVariables, String encodingScheme)
378                        throws UnsupportedEncodingException {
379
380                StringBuilder result = new StringBuilder();
381                Matcher matcher = URI_TEMPLATE_VARIABLE_PATTERN.matcher(targetUrl);
382                int endLastMatch = 0;
383                while (matcher.find()) {
384                        String name = matcher.group(1);
385                        Object value = (model.containsKey(name) ? model.remove(name) : currentUriVariables.get(name));
386                        if (value == null) {
387                                throw new IllegalArgumentException("Model has no value for key '" + name + "'");
388                        }
389                        result.append(targetUrl, endLastMatch, matcher.start());
390                        result.append(UriUtils.encodePathSegment(value.toString(), encodingScheme));
391                        endLastMatch = matcher.end();
392                }
393                result.append(targetUrl.substring(endLastMatch));
394                return result;
395        }
396
397        @SuppressWarnings("unchecked")
398        private Map<String, String> getCurrentRequestUriVariables(HttpServletRequest request) {
399                String name = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
400                Map<String, String> uriVars = (Map<String, String>) request.getAttribute(name);
401                return (uriVars != null) ? uriVars : Collections.<String, String> emptyMap();
402        }
403
404        /**
405         * Append the query string of the current request to the target redirect URL.
406         * @param targetUrl the StringBuilder to append the properties to
407         * @param request the current request
408         * @since 4.1
409         */
410        protected void appendCurrentQueryParams(StringBuilder targetUrl, HttpServletRequest request) {
411                String query = request.getQueryString();
412                if (StringUtils.hasText(query)) {
413                        // Extract anchor fragment, if any.
414                        String fragment = null;
415                        int anchorIndex = targetUrl.indexOf("#");
416                        if (anchorIndex > -1) {
417                                fragment = targetUrl.substring(anchorIndex);
418                                targetUrl.delete(anchorIndex, targetUrl.length());
419                        }
420
421                        if (targetUrl.toString().indexOf('?') < 0) {
422                                targetUrl.append('?').append(query);
423                        }
424                        else {
425                                targetUrl.append('&').append(query);
426                        }
427                        // Append anchor fragment, if any, to end of URL.
428                        if (fragment != null) {
429                                targetUrl.append(fragment);
430                        }
431                }
432        }
433
434        /**
435         * Append query properties to the redirect URL.
436         * Stringifies, URL-encodes and formats model attributes as query properties.
437         * @param targetUrl the StringBuilder to append the properties to
438         * @param model a Map that contains model attributes
439         * @param encodingScheme the encoding scheme to use
440         * @throws UnsupportedEncodingException if string encoding failed
441         * @see #queryProperties
442         */
443        @SuppressWarnings("unchecked")
444        protected void appendQueryProperties(StringBuilder targetUrl, Map<String, Object> model, String encodingScheme)
445                        throws UnsupportedEncodingException {
446
447                // Extract anchor fragment, if any.
448                String fragment = null;
449                int anchorIndex = targetUrl.indexOf("#");
450                if (anchorIndex > -1) {
451                        fragment = targetUrl.substring(anchorIndex);
452                        targetUrl.delete(anchorIndex, targetUrl.length());
453                }
454
455                // If there aren't already some parameters, we need a "?".
456                boolean first = (targetUrl.toString().indexOf('?') < 0);
457                for (Map.Entry<String, Object> entry : queryProperties(model).entrySet()) {
458                        Object rawValue = entry.getValue();
459                        Collection<?> values;
460                        if (rawValue != null && rawValue.getClass().isArray()) {
461                                values = CollectionUtils.arrayToList(rawValue);
462                        }
463                        else if (rawValue instanceof Collection) {
464                                values = ((Collection<?>) rawValue);
465                        }
466                        else {
467                                values = Collections.singleton(rawValue);
468                        }
469                        for (Object value : values) {
470                                if (first) {
471                                        targetUrl.append('?');
472                                        first = false;
473                                }
474                                else {
475                                        targetUrl.append('&');
476                                }
477                                String encodedKey = urlEncode(entry.getKey(), encodingScheme);
478                                String encodedValue = (value != null ? urlEncode(value.toString(), encodingScheme) : "");
479                                targetUrl.append(encodedKey).append('=').append(encodedValue);
480                        }
481                }
482
483                // Append anchor fragment, if any, to end of URL.
484                if (fragment != null) {
485                        targetUrl.append(fragment);
486                }
487        }
488
489        /**
490         * Determine name-value pairs for query strings, which will be stringified,
491         * URL-encoded and formatted by {@link #appendQueryProperties}.
492         * <p>This implementation filters the model through checking
493         * {@link #isEligibleProperty(String, Object)} for each element,
494         * by default accepting Strings, primitives and primitive wrappers only.
495         * @param model the original model Map
496         * @return the filtered Map of eligible query properties
497         * @see #isEligibleProperty(String, Object)
498         */
499        protected Map<String, Object> queryProperties(Map<String, Object> model) {
500                Map<String, Object> result = new LinkedHashMap<>();
501                model.forEach((name, value) -> {
502                        if (isEligibleProperty(name, value)) {
503                                result.put(name, value);
504                        }
505                });
506                return result;
507        }
508
509        /**
510         * Determine whether the given model element should be exposed
511         * as a query property.
512         * <p>The default implementation considers Strings and primitives
513         * as eligible, and also arrays and Collections/Iterables with
514         * corresponding elements. This can be overridden in subclasses.
515         * @param key the key of the model element
516         * @param value the value of the model element
517         * @return whether the element is eligible as query property
518         */
519        protected boolean isEligibleProperty(String key, @Nullable Object value) {
520                if (value == null) {
521                        return false;
522                }
523                if (isEligibleValue(value)) {
524                        return true;
525                }
526                if (value.getClass().isArray()) {
527                        int length = Array.getLength(value);
528                        if (length == 0) {
529                                return false;
530                        }
531                        for (int i = 0; i < length; i++) {
532                                Object element = Array.get(value, i);
533                                if (!isEligibleValue(element)) {
534                                        return false;
535                                }
536                        }
537                        return true;
538                }
539                if (value instanceof Collection) {
540                        Collection<?> coll = (Collection<?>) value;
541                        if (coll.isEmpty()) {
542                                return false;
543                        }
544                        for (Object element : coll) {
545                                if (!isEligibleValue(element)) {
546                                        return false;
547                                }
548                        }
549                        return true;
550                }
551                return false;
552        }
553
554        /**
555         * Determine whether the given model element value is eligible for exposure.
556         * <p>The default implementation considers primitives, strings, numbers, dates,
557         * URIs, URLs etc as eligible, according to {@link BeanUtils#isSimpleValueType}.
558         * This can be overridden in subclasses.
559         * @param value the model element value
560         * @return whether the element value is eligible
561         * @see BeanUtils#isSimpleValueType
562         */
563        protected boolean isEligibleValue(@Nullable Object value) {
564                return (value != null && BeanUtils.isSimpleValueType(value.getClass()));
565        }
566
567        /**
568         * URL-encode the given input String with the given encoding scheme.
569         * <p>The default implementation uses {@code URLEncoder.encode(input, enc)}.
570         * @param input the unencoded input String
571         * @param encodingScheme the encoding scheme
572         * @return the encoded output String
573         * @throws UnsupportedEncodingException if thrown by the JDK URLEncoder
574         * @see java.net.URLEncoder#encode(String, String)
575         */
576        protected String urlEncode(String input, String encodingScheme) throws UnsupportedEncodingException {
577                return URLEncoder.encode(input, encodingScheme);
578        }
579
580        /**
581         * Find the registered {@link RequestDataValueProcessor}, if any, and allow
582         * it to update the redirect target URL.
583         * @param targetUrl the given redirect URL
584         * @return the updated URL or the same as URL as the one passed in
585         */
586        protected String updateTargetUrl(String targetUrl, Map<String, Object> model,
587                        HttpServletRequest request, HttpServletResponse response) {
588
589                WebApplicationContext wac = getWebApplicationContext();
590                if (wac == null) {
591                        wac = RequestContextUtils.findWebApplicationContext(request, getServletContext());
592                }
593
594                if (wac != null && wac.containsBean(RequestContextUtils.REQUEST_DATA_VALUE_PROCESSOR_BEAN_NAME)) {
595                        RequestDataValueProcessor processor = wac.getBean(
596                                        RequestContextUtils.REQUEST_DATA_VALUE_PROCESSOR_BEAN_NAME, RequestDataValueProcessor.class);
597                        return processor.processUrl(request, targetUrl);
598                }
599
600                return targetUrl;
601        }
602
603        /**
604         * Send a redirect back to the HTTP client.
605         * @param request current HTTP request (allows for reacting to request method)
606         * @param response current HTTP response (for sending response headers)
607         * @param targetUrl the target URL to redirect to
608         * @param http10Compatible whether to stay compatible with HTTP 1.0 clients
609         * @throws IOException if thrown by response methods
610         */
611        protected void sendRedirect(HttpServletRequest request, HttpServletResponse response,
612                        String targetUrl, boolean http10Compatible) throws IOException {
613
614                String encodedURL = (isRemoteHost(targetUrl) ? targetUrl : response.encodeRedirectURL(targetUrl));
615                if (http10Compatible) {
616                        HttpStatus attributeStatusCode = (HttpStatus) request.getAttribute(View.RESPONSE_STATUS_ATTRIBUTE);
617                        if (this.statusCode != null) {
618                                response.setStatus(this.statusCode.value());
619                                response.setHeader("Location", encodedURL);
620                        }
621                        else if (attributeStatusCode != null) {
622                                response.setStatus(attributeStatusCode.value());
623                                response.setHeader("Location", encodedURL);
624                        }
625                        else {
626                                // Send status code 302 by default.
627                                response.sendRedirect(encodedURL);
628                        }
629                }
630                else {
631                        HttpStatus statusCode = getHttp11StatusCode(request, response, targetUrl);
632                        response.setStatus(statusCode.value());
633                        response.setHeader("Location", encodedURL);
634                }
635        }
636
637        /**
638         * Whether the given targetUrl has a host that is a "foreign" system in which
639         * case {@link HttpServletResponse#encodeRedirectURL} will not be applied.
640         * This method returns {@code true} if the {@link #setHosts(String[])}
641         * property is configured and the target URL has a host that does not match.
642         * @param targetUrl the target redirect URL
643         * @return {@code true} the target URL has a remote host, {@code false} if it
644         * the URL does not have a host or the "host" property is not configured.
645         * @since 4.3
646         */
647        protected boolean isRemoteHost(String targetUrl) {
648                if (ObjectUtils.isEmpty(getHosts())) {
649                        return false;
650                }
651                String targetHost = UriComponentsBuilder.fromUriString(targetUrl).build().getHost();
652                if (!StringUtils.hasLength(targetHost)) {
653                        return false;
654                }
655                for (String host : getHosts()) {
656                        if (targetHost.equals(host)) {
657                                return false;
658                        }
659                }
660                return true;
661        }
662
663        /**
664         * Determines the status code to use for HTTP 1.1 compatible requests.
665         * <p>The default implementation returns the {@link #setStatusCode(HttpStatus) statusCode}
666         * property if set, or the value of the {@link #RESPONSE_STATUS_ATTRIBUTE} attribute.
667         * If neither are set, it defaults to {@link HttpStatus#SEE_OTHER} (303).
668         * @param request the request to inspect
669         * @param response the servlet response
670         * @param targetUrl the target URL
671         * @return the response status
672         */
673        protected HttpStatus getHttp11StatusCode(
674                        HttpServletRequest request, HttpServletResponse response, String targetUrl) {
675
676                if (this.statusCode != null) {
677                        return this.statusCode;
678                }
679                HttpStatus attributeStatusCode = (HttpStatus) request.getAttribute(View.RESPONSE_STATUS_ATTRIBUTE);
680                if (attributeStatusCode != null) {
681                        return attributeStatusCode;
682                }
683                return HttpStatus.SEE_OTHER;
684        }
685
686}