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}