001/* 002 * Copyright 2002-2017 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; 018 019import java.util.HashMap; 020 021import org.springframework.lang.Nullable; 022import org.springframework.util.LinkedMultiValueMap; 023import org.springframework.util.MultiValueMap; 024import org.springframework.util.ObjectUtils; 025import org.springframework.util.StringUtils; 026 027/** 028 * A FlashMap provides a way for one request to store attributes intended for 029 * use in another. This is most commonly needed when redirecting from one URL 030 * to another -- e.g. the Post/Redirect/Get pattern. A FlashMap is saved before 031 * the redirect (typically in the session) and is made available after the 032 * redirect and removed immediately. 033 * 034 * <p>A FlashMap can be set up with a request path and request parameters to 035 * help identify the target request. Without this information, a FlashMap is 036 * made available to the next request, which may or may not be the intended 037 * recipient. On a redirect, the target URL is known and a FlashMap can be 038 * updated with that information. This is done automatically when the 039 * {@code org.springframework.web.servlet.view.RedirectView} is used. 040 * 041 * <p>Note: annotated controllers will usually not use FlashMap directly. 042 * See {@code org.springframework.web.servlet.mvc.support.RedirectAttributes} 043 * for an overview of using flash attributes in annotated controllers. 044 * 045 * @author Rossen Stoyanchev 046 * @since 3.1 047 * @see FlashMapManager 048 */ 049@SuppressWarnings("serial") 050public final class FlashMap extends HashMap<String, Object> implements Comparable<FlashMap> { 051 052 @Nullable 053 private String targetRequestPath; 054 055 private final MultiValueMap<String, String> targetRequestParams = new LinkedMultiValueMap<>(4); 056 057 private long expirationTime = -1; 058 059 060 /** 061 * Provide a URL path to help identify the target request for this FlashMap. 062 * <p>The path may be absolute (e.g. "/application/resource") or relative to the 063 * current request (e.g. "../resource"). 064 */ 065 public void setTargetRequestPath(@Nullable String path) { 066 this.targetRequestPath = path; 067 } 068 069 /** 070 * Return the target URL path (or {@code null} if none specified). 071 */ 072 @Nullable 073 public String getTargetRequestPath() { 074 return this.targetRequestPath; 075 } 076 077 /** 078 * Provide request parameters identifying the request for this FlashMap. 079 * @param params a Map with the names and values of expected parameters 080 */ 081 public FlashMap addTargetRequestParams(@Nullable MultiValueMap<String, String> params) { 082 if (params != null) { 083 params.forEach((key, values) -> { 084 for (String value : values) { 085 addTargetRequestParam(key, value); 086 } 087 }); 088 } 089 return this; 090 } 091 092 /** 093 * Provide a request parameter identifying the request for this FlashMap. 094 * @param name the expected parameter name (skipped if empty) 095 * @param value the expected value (skipped if empty) 096 */ 097 public FlashMap addTargetRequestParam(String name, String value) { 098 if (StringUtils.hasText(name) && StringUtils.hasText(value)) { 099 this.targetRequestParams.add(name, value); 100 } 101 return this; 102 } 103 104 /** 105 * Return the parameters identifying the target request, or an empty map. 106 */ 107 public MultiValueMap<String, String> getTargetRequestParams() { 108 return this.targetRequestParams; 109 } 110 111 /** 112 * Start the expiration period for this instance. 113 * @param timeToLive the number of seconds before expiration 114 */ 115 public void startExpirationPeriod(int timeToLive) { 116 this.expirationTime = System.currentTimeMillis() + timeToLive * 1000; 117 } 118 119 /** 120 * Set the expiration time for the FlashMap. This is provided for serialization 121 * purposes but can also be used instead {@link #startExpirationPeriod(int)}. 122 * @since 4.2 123 */ 124 public void setExpirationTime(long expirationTime) { 125 this.expirationTime = expirationTime; 126 } 127 128 /** 129 * Return the expiration time for the FlashMap or -1 if the expiration 130 * period has not started. 131 * @since 4.2 132 */ 133 public long getExpirationTime() { 134 return this.expirationTime; 135 } 136 137 /** 138 * Return whether this instance has expired depending on the amount of 139 * elapsed time since the call to {@link #startExpirationPeriod}. 140 */ 141 public boolean isExpired() { 142 return (this.expirationTime != -1 && System.currentTimeMillis() > this.expirationTime); 143 } 144 145 146 /** 147 * Compare two FlashMaps and prefer the one that specifies a target URL 148 * path or has more target URL parameters. Before comparing FlashMap 149 * instances ensure that they match a given request. 150 */ 151 @Override 152 public int compareTo(FlashMap other) { 153 int thisUrlPath = (this.targetRequestPath != null ? 1 : 0); 154 int otherUrlPath = (other.targetRequestPath != null ? 1 : 0); 155 if (thisUrlPath != otherUrlPath) { 156 return otherUrlPath - thisUrlPath; 157 } 158 else { 159 return other.targetRequestParams.size() - this.targetRequestParams.size(); 160 } 161 } 162 163 @Override 164 public boolean equals(@Nullable Object other) { 165 if (this == other) { 166 return true; 167 } 168 if (!(other instanceof FlashMap)) { 169 return false; 170 } 171 FlashMap otherFlashMap = (FlashMap) other; 172 return (super.equals(otherFlashMap) && 173 ObjectUtils.nullSafeEquals(this.targetRequestPath, otherFlashMap.targetRequestPath) && 174 this.targetRequestParams.equals(otherFlashMap.targetRequestParams)); 175 } 176 177 @Override 178 public int hashCode() { 179 int result = super.hashCode(); 180 result = 31 * result + ObjectUtils.nullSafeHashCode(this.targetRequestPath); 181 result = 31 * result + this.targetRequestParams.hashCode(); 182 return result; 183 } 184 185 @Override 186 public String toString() { 187 return "FlashMap [attributes=" + super.toString() + ", targetRequestPath=" + 188 this.targetRequestPath + ", targetRequestParams=" + this.targetRequestParams + "]"; 189 } 190 191}