1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.apache.struts2.dispatcher;
23
24 import javax.servlet.http.HttpServletRequest;
25 import javax.servlet.http.HttpServletResponse;
26 import static javax.servlet.http.HttpServletResponse.*;
27
28 import org.apache.struts2.ServletActionContext;
29 import org.apache.struts2.views.util.UrlHelper;
30 import org.apache.struts2.dispatcher.mapper.ActionMapper;
31 import org.apache.struts2.dispatcher.mapper.ActionMapping;
32
33 import com.opensymphony.xwork2.ActionContext;
34 import com.opensymphony.xwork2.ActionInvocation;
35 import com.opensymphony.xwork2.config.entities.ResultConfig;
36 import com.opensymphony.xwork2.inject.Inject;
37 import com.opensymphony.xwork2.util.logging.Logger;
38 import com.opensymphony.xwork2.util.logging.LoggerFactory;
39 import com.opensymphony.xwork2.util.reflection.ReflectionException;
40 import com.opensymphony.xwork2.util.reflection.ReflectionExceptionHandler;
41
42 import java.io.IOException;
43 import java.util.Map;
44 import java.util.LinkedHashMap;
45 import java.util.Iterator;
46 import java.util.List;
47 import java.util.Arrays;
48
49
50 /***
51 * <!-- START SNIPPET: description -->
52 *
53 * Calls the {@link HttpServletResponse#sendRedirect(String) sendRedirect}
54 * method to the location specified. The response is told to redirect the
55 * browser to the specified location (a new request from the client). The
56 * consequence of doing this means that the action (action instance, action
57 * errors, field errors, etc) that was just executed is lost and no longer
58 * available. This is because actions are built on a single-thread model. The
59 * only way to pass data is through the session or with web parameters
60 * (url?name=value) which can be OGNL expressions.
61 *
62 * <!-- END SNIPPET: description -->
63 * <p/>
64 * <b>This result type takes the following parameters:</b>
65 *
66 * <!-- START SNIPPET: params -->
67 *
68 * <ul>
69 *
70 * <li><b>location (default)</b> - the location to go to after execution.</li>
71 *
72 * <li><b>parse</b> - true by default. If set to false, the location param will
73 * not be parsed for Ognl expressions.</li>
74 *
75 * </ul>
76 *
77 * <p>
78 * This result follows the same rules from {@link StrutsResultSupport}.
79 * </p>
80 *
81 * <!-- END SNIPPET: params -->
82 *
83 * <b>Example:</b>
84 *
85 * <pre><!-- START SNIPPET: example -->
86 * <result name="success" type="redirect">
87 * <param name="location">foo.jsp</param>
88 * <param name="parse">false</param>
89 * </result>
90 * <!-- END SNIPPET: example --></pre>
91 *
92 */
93 public class ServletRedirectResult extends StrutsResultSupport implements ReflectionExceptionHandler {
94
95 private static final long serialVersionUID = 6316947346435301270L;
96
97 private static final Logger LOG = LoggerFactory.getLogger(ServletRedirectResult.class);
98
99 protected boolean prependServletContext = true;
100
101 protected ActionMapper actionMapper;
102
103 protected int statusCode = SC_FOUND;
104
105 protected boolean supressEmptyParameters = false;
106
107 protected Map<String, String> requestParameters = new LinkedHashMap<String, String>();
108
109 public ServletRedirectResult() {
110 super();
111 }
112
113 public ServletRedirectResult(String location) {
114 super(location);
115 }
116
117 @Inject
118 public void setActionMapper(ActionMapper mapper) {
119 this.actionMapper = mapper;
120 }
121
122 public void setStatusCode(int code) {
123 this.statusCode = code;
124 }
125
126 /***
127 * Sets whether or not to prepend the servlet context path to the redirected URL.
128 *
129 * @param prependServletContext <tt>true</tt> to prepend the location with the servlet context path,
130 * <tt>false</tt> otherwise.
131 */
132 public void setPrependServletContext(boolean prependServletContext) {
133 this.prependServletContext = prependServletContext;
134 }
135
136 /***
137 * Redirects to the location specified by calling {@link HttpServletResponse#sendRedirect(String)}.
138 *
139 * @param finalLocation the location to redirect to.
140 * @param invocation an encapsulation of the action execution state.
141 * @throws Exception if an error occurs when redirecting.
142 */
143 protected void doExecute(String finalLocation, ActionInvocation invocation) throws Exception {
144 ActionContext ctx = invocation.getInvocationContext();
145 HttpServletRequest request = (HttpServletRequest) ctx.get(ServletActionContext.HTTP_REQUEST);
146 HttpServletResponse response = (HttpServletResponse) ctx.get(ServletActionContext.HTTP_RESPONSE);
147
148 if (isPathUrl(finalLocation)) {
149 if (!finalLocation.startsWith("/")) {
150 ActionMapping mapping = actionMapper.getMapping(
151 request, Dispatcher.getInstance().getConfigurationManager());
152 String namespace = null;
153 if (mapping != null) {
154 namespace = mapping.getNamespace();
155 }
156
157 if ((namespace != null) && (namespace.length() > 0) && (!"/".equals(namespace))) {
158 finalLocation = namespace + "/" + finalLocation;
159 } else {
160 finalLocation = "/" + finalLocation;
161 }
162 }
163
164
165 if (prependServletContext && (request.getContextPath() != null) && (request.getContextPath().length() > 0)) {
166 finalLocation = request.getContextPath() + finalLocation;
167 }
168
169 ResultConfig resultConfig = invocation.getProxy().getConfig().getResults().get(invocation.getResultCode());
170 if (resultConfig != null ) {
171 Map resultConfigParams = resultConfig.getParams();
172 for (Iterator i = resultConfigParams.entrySet().iterator(); i.hasNext();) {
173 Map.Entry e = (Map.Entry) i.next();
174
175 if (!getProhibitedResultParams().contains(e.getKey())) {
176 requestParameters.put(e.getKey().toString(),
177 e.getValue() == null ? "" :
178 conditionalParse(e.getValue().toString(), invocation));
179 String potentialValue = e.getValue() == null ? "" : conditionalParse(e.getValue().toString(), invocation);
180 if (!supressEmptyParameters || ((potentialValue != null) && (potentialValue.length() > 0))) {
181 requestParameters.put(e.getKey().toString(), potentialValue);
182 }
183 }
184 }
185 }
186
187 StringBuilder tmpLocation = new StringBuilder(finalLocation);
188 UrlHelper.buildParametersString(requestParameters, tmpLocation, "&");
189
190 finalLocation = response.encodeRedirectURL(tmpLocation.toString());
191 }
192
193 if (LOG.isDebugEnabled()) {
194 LOG.debug("Redirecting to finalLocation " + finalLocation);
195 }
196
197 sendRedirect(response, finalLocation);
198 }
199
200 protected List<String> getProhibitedResultParams() {
201 return Arrays.asList(new String[]{
202 DEFAULT_PARAM, "namespace", "method", "encode", "parse", "location",
203 "prependServletContext", "supressEmptyParameters"});
204 }
205
206
207 /***
208 * Sends the redirection. Can be overridden to customize how the redirect is handled (i.e. to use a different
209 * status code)
210 *
211 * @param response The response
212 * @param finalLocation The location URI
213 * @throws IOException
214 */
215 protected void sendRedirect(HttpServletResponse response, String finalLocation) throws IOException {
216 if (SC_FOUND == statusCode) {
217 response.sendRedirect(finalLocation);
218 } else {
219 response.setStatus(statusCode);
220 response.setHeader("Location", finalLocation);
221 response.getWriter().write(finalLocation);
222 response.getWriter().close();
223 }
224
225 }
226
227 private static boolean isPathUrl(String url) {
228
229
230
231 return (url.indexOf(':') == -1);
232 }
233
234 /***
235 * Sets the supressEmptyParameters option
236 *
237 * @param supressEmptyParameters The new value for this option
238 */
239 public void setSupressEmptyParameters(boolean supressEmptyParameters) {
240 this.supressEmptyParameters = supressEmptyParameters;
241 }
242
243 /***
244 * Adds a request parameter to be added to the redirect url
245 *
246 * @param key The parameter name
247 * @param value The parameter value
248 */
249 public ServletRedirectResult addParameter(String key, Object value) {
250 requestParameters.put(key, String.valueOf(value));
251 return this;
252 }
253
254 public void handle(ReflectionException ex) {
255
256 LOG.debug(ex.getMessage(), ex);
257 }
258 }