| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.struts2.result; |
| |
| import com.opensymphony.xwork2.ActionContext; |
| import com.opensymphony.xwork2.ActionInvocation; |
| import com.opensymphony.xwork2.config.entities.ResultConfig; |
| import com.opensymphony.xwork2.inject.Inject; |
| import com.opensymphony.xwork2.util.reflection.ReflectionException; |
| import com.opensymphony.xwork2.util.reflection.ReflectionExceptionHandler; |
| import org.apache.logging.log4j.LogManager; |
| import org.apache.logging.log4j.Logger; |
| import org.apache.struts2.dispatcher.Dispatcher; |
| import org.apache.struts2.dispatcher.mapper.ActionMapper; |
| import org.apache.struts2.dispatcher.mapper.ActionMapping; |
| import org.apache.struts2.views.util.UrlHelper; |
| |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| import java.io.IOException; |
| import java.net.MalformedURLException; |
| import java.net.URI; |
| import java.net.URL; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import static javax.servlet.http.HttpServletResponse.SC_FOUND; |
| |
| /** |
| * Calls the {@link HttpServletResponse#sendRedirect(String) sendRedirect} |
| * method to the location specified. The response is told to redirect the |
| * browser to the specified location (a new request from the client). The |
| * consequence of doing this means that the action (action instance, action |
| * errors, field errors, etc) that was just executed is lost and no longer |
| * available. This is because actions are built on a single-thread model. The |
| * only way to pass data is through the session or with web parameters |
| * (url?name=value) which can be OGNL expressions. |
| * |
| * <b>This result type takes the following parameters:</b> |
| * |
| * <ul> |
| * <li><b>location (default)</b> - the location to go to after execution.</li> |
| * <li><b>parse</b> - true by default. If set to false, the location param will |
| * not be parsed for Ognl expressions.</li> |
| * <li><b>anchor</b> - Optional. Also known as "fragment" or colloquially as |
| * "hash". You can specify an anchor for a result.</li> |
| * </ul> |
| * This result follows the same rules from {@link StrutsResultSupport}. |
| * |
| * <b>Example:</b> |
| * <pre> |
| * <!-- START SNIPPET: example --> |
| * <!-- |
| * The redirect URL generated will be: |
| * /foo.jsp#FRAGMENT |
| * --> |
| * <result name="success" type="redirect"> |
| * <param name="location">foo.jsp</param> |
| * <param name="parse">false</param> |
| * <param name="anchor">FRAGMENT</param> |
| * </result> |
| * <!-- END SNIPPET: example --> |
| * </pre> |
| */ |
| public class ServletRedirectResult extends StrutsResultSupport implements ReflectionExceptionHandler, Redirectable { |
| |
| private static final long serialVersionUID = 6316947346435301270L; |
| |
| private static final Logger LOG = LogManager.getLogger(ServletRedirectResult.class); |
| |
| protected boolean prependServletContext = true; |
| protected ActionMapper actionMapper; |
| protected int statusCode = SC_FOUND; |
| protected boolean suppressEmptyParameters = false; |
| protected Map<String, Object> requestParameters = new LinkedHashMap<>(); |
| protected String anchor; |
| |
| private UrlHelper urlHelper; |
| |
| public ServletRedirectResult() { |
| super(); |
| } |
| |
| public ServletRedirectResult(String location) { |
| this(location, null); |
| } |
| |
| public ServletRedirectResult(String location, String anchor) { |
| super(location); |
| this.anchor = anchor; |
| } |
| |
| @Inject |
| public void setActionMapper(ActionMapper mapper) { |
| this.actionMapper = mapper; |
| } |
| |
| @Inject |
| public void setUrlHelper(UrlHelper urlHelper) { |
| this.urlHelper = urlHelper; |
| } |
| |
| public void setStatusCode(int code) { |
| this.statusCode = code; |
| } |
| |
| /** |
| * Set the optional anchor value. |
| * |
| * @param anchor the anchor value |
| */ |
| public void setAnchor(String anchor) { |
| this.anchor = anchor; |
| } |
| |
| /** |
| * Sets whether or not to prepend the servlet context path to the redirected |
| * URL. |
| * |
| * @param prependServletContext <tt>true</tt> to prepend the location with the servlet context path, <tt>false</tt> otherwise. |
| */ |
| public void setPrependServletContext(boolean prependServletContext) { |
| this.prependServletContext = prependServletContext; |
| } |
| |
| public void execute(ActionInvocation invocation) throws Exception { |
| if (anchor != null) { |
| anchor = conditionalParse(anchor, invocation); |
| } |
| super.execute(invocation); |
| } |
| |
| /** |
| * Redirects to the location specified by calling |
| * {@link HttpServletResponse#sendRedirect(String)}. |
| * |
| * @param finalLocation the location to redirect to. |
| * @param invocation an encapsulation of the action execution state. |
| * @throws Exception if an error occurs when redirecting. |
| */ |
| protected void doExecute(String finalLocation, ActionInvocation invocation) throws Exception { |
| ActionContext ctx = invocation.getInvocationContext(); |
| HttpServletRequest request = ctx.getServletRequest(); |
| HttpServletResponse response = ctx.getServletResponse(); |
| |
| if (isPathUrl(finalLocation)) { |
| if (!finalLocation.startsWith("/")) { |
| ActionMapping mapping = actionMapper.getMapping(request, Dispatcher.getInstance().getConfigurationManager()); |
| String namespace = null; |
| if (mapping != null) { |
| namespace = mapping.getNamespace(); |
| } |
| |
| if ((namespace != null) && (namespace.length() > 0) && (!"/".equals(namespace))) { |
| finalLocation = namespace + "/" + finalLocation; |
| } else { |
| finalLocation = "/" + finalLocation; |
| } |
| } |
| |
| // if the URL's are relative to the servlet context, append the servlet context path |
| if (prependServletContext && (request.getContextPath() != null) && (request.getContextPath().length() > 0)) { |
| finalLocation = request.getContextPath() + finalLocation; |
| } |
| } |
| ResultConfig resultConfig = invocation.getProxy().getConfig().getResults().get(invocation.getResultCode()); |
| if (resultConfig != null) { |
| Map<String, String> resultConfigParams = resultConfig.getParams(); |
| |
| List<String> prohibitedResultParams = getProhibitedResultParams(); |
| for (Map.Entry<String, String> e : resultConfigParams.entrySet()) { |
| if (!prohibitedResultParams.contains(e.getKey())) { |
| Collection<String> values = conditionalParseCollection(e.getValue(), invocation, suppressEmptyParameters); |
| if (!suppressEmptyParameters || !values.isEmpty()) { |
| requestParameters.put(e.getKey(), values); |
| } |
| } |
| } |
| } |
| |
| StringBuilder tmpLocation = new StringBuilder(finalLocation); |
| urlHelper.buildParametersString(requestParameters, tmpLocation, "&"); |
| |
| // add the anchor |
| if (anchor != null) { |
| tmpLocation.append('#').append(anchor); |
| } |
| |
| finalLocation = response.encodeRedirectURL(tmpLocation.toString()); |
| |
| LOG.debug("Redirecting to finalLocation: {}", finalLocation); |
| |
| sendRedirect(response, finalLocation); |
| } |
| |
| protected List<String> getProhibitedResultParams() { |
| return Arrays.asList( |
| DEFAULT_PARAM, |
| "namespace", |
| "method", |
| "encode", |
| "parse", |
| "location", |
| "prependServletContext", |
| "suppressEmptyParameters", |
| "anchor", |
| "statusCode" |
| ); |
| } |
| |
| /** |
| * Sends the redirection. Can be overridden to customize how the redirect is |
| * handled (i.e. to use a different status code) |
| * |
| * @param response The response |
| * @param finalLocation The location URI |
| * @throws IOException in case of IO errors |
| */ |
| protected void sendRedirect(HttpServletResponse response, String finalLocation) throws IOException { |
| try { |
| if (SC_FOUND == statusCode) { |
| response.sendRedirect(finalLocation); |
| } else { |
| response.setStatus(statusCode); |
| response.setHeader("Location", finalLocation); |
| try { |
| response.getWriter().write(finalLocation); |
| } finally { |
| response.getWriter().close(); |
| } |
| } |
| } catch (IOException ioe) { |
| LOG.warn("Unable to redirect to: {}, code: {}; {}", finalLocation, statusCode, ioe); |
| throw ioe; // Re-throw required to preserve existing default behaviour (no stacktrace in above warn for this reason) |
| } catch (IllegalStateException ise) { |
| LOG.warn("Unable to redirect to: {}, code: {}; isCommited: {}; {}", finalLocation, statusCode, response.isCommitted(), ise); |
| throw ise; // Re-throw required to preserve existing default behaviour (no stacktrace in above warn for this reason) |
| } |
| |
| } |
| |
| /** |
| * Checks if url is simple path or either full url |
| * |
| * @param url string |
| * @return true if it's just a path not a full url |
| */ |
| protected boolean isPathUrl(String url) { |
| try { |
| String rawUrl = url; |
| if (url.contains("?")) { |
| rawUrl = url.substring(0, url.indexOf('?')); |
| } |
| URI uri = URI.create(rawUrl.replaceAll(" ", "%20")); |
| if (uri.isAbsolute()) { |
| URL validUrl = uri.toURL(); |
| LOG.debug("[{}] is full url, not a path", url); |
| return validUrl.getProtocol() == null; |
| } else { |
| LOG.debug("[{}] isn't absolute URI, assuming it's a path", url); |
| return true; |
| } |
| } catch (IllegalArgumentException e) { |
| LOG.debug("[{}] isn't a valid URL, assuming it's a path", url, e); |
| return true; |
| } catch (MalformedURLException e) { |
| LOG.debug("[{}] isn't a valid URL, assuming it's a path", url, e); |
| return true; |
| } |
| } |
| |
| /** |
| * Sets the suppressEmptyParameters option |
| * |
| * @param suppressEmptyParameters The new value for this option |
| */ |
| public void setSuppressEmptyParameters(boolean suppressEmptyParameters) { |
| this.suppressEmptyParameters = suppressEmptyParameters; |
| } |
| |
| /** |
| * Adds a request parameter to be added to the redirect url |
| * |
| * @param key The parameter name |
| * @param value The parameter value |
| * @return the servlet redirect result |
| */ |
| public ServletRedirectResult addParameter(String key, Object value) { |
| requestParameters.put(key, String.valueOf(value)); |
| return this; |
| } |
| |
| public void handle(ReflectionException ex) { |
| // Only log as debug as they are probably parameters to be appended to the url |
| if (LOG.isDebugEnabled()) { |
| LOG.debug(ex.getMessage(), ex); |
| } |
| } |
| |
| } |