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