/** * This file Copyright (c) 2013 Magnolia International * Ltd. (http://www.magnolia-cms.com). All rights reserved. * * * This file is dual-licensed under both the Magnolia * Network Agreement and the GNU General Public License. * You may elect to use one or the other of these licenses. * * This file is distributed in the hope that it will be * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the * implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT. * Redistribution, except as permitted by whichever of the GPL * or MNA you select, is prohibited. * * 1. For the GPL license (GPL), you can redistribute and/or * modify this file under the terms of the GNU General * Public License, Version 3, as published by the Free Software * Foundation. You should have received a copy of the GNU * General Public License, Version 3 along with this program; * if not, write to the Free Software Foundation, Inc., 51 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * 2. For the Magnolia Network Agreement (MNA), this file * and the accompanying materials are made available under the * terms of the MNA which accompanies this distribution, and * is available at http://www.magnolia-cms.com/mna.html * * Any modifications to this file must keep this entire header * intact. * */ package info.magnolia.module.blossom.support; import java.io.IOException; import java.util.Deque; import java.util.Enumeration; import java.util.LinkedList; import java.util.NoSuchElementException; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import org.springframework.web.util.WebUtils; /** * Simulates forward and include dispatches by changing attributes and path elements appropriately. */ public class SimulatedDispatchRequestWrapper extends HttpServletRequestWrapper { private static final String[] SPECIAL_ATTRIBUTE_NAMES = new String[]{ WebUtils.FORWARD_REQUEST_URI_ATTRIBUTE, WebUtils.FORWARD_CONTEXT_PATH_ATTRIBUTE, WebUtils.FORWARD_SERVLET_PATH_ATTRIBUTE, WebUtils.FORWARD_PATH_INFO_ATTRIBUTE, WebUtils.FORWARD_QUERY_STRING_ATTRIBUTE, WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE, WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE, WebUtils.INCLUDE_SERVLET_PATH_ATTRIBUTE, WebUtils.INCLUDE_PATH_INFO_ATTRIBUTE, WebUtils.INCLUDE_QUERY_STRING_ATTRIBUTE }; private static class State { boolean simulatingPathElements; String forwardRequestUri; String forwardContextPath; String forwardServletPath; String forwardPathInfo; boolean simulatingForwardAttributes; String forwardRequestUriAttribute; String forwardContextPathAttribute; String forwardServletPathAttribute; String forwardPathInfoAttribute; String forwardQueryStringAttribute; boolean simulatingIncludeAttributes; String includeRequestUriAttribute; String includeContextPathAttribute; String includeServletPathAttribute; String includePathInfoAttribute; String includeQueryStringAttribute; } private boolean simulatingPathElements; private String forwardRequestUri; private String forwardContextPath; private String forwardServletPath; private String forwardPathInfo; private boolean simulatingForwardAttributes; private String forwardRequestUriAttribute; private String forwardContextPathAttribute; private String forwardServletPathAttribute; private String forwardPathInfoAttribute; private String forwardQueryStringAttribute; private boolean simulatingIncludeAttributes; private String includeRequestUriAttribute; private String includeContextPathAttribute; private String includeServletPathAttribute; private String includePathInfoAttribute; private String includeQueryStringAttribute; private final Deque stack = new LinkedList(); private SimulatedDispatchRequestWrapper(HttpServletRequest request) { super(request); } private void push() { State state = new State(); state.simulatingPathElements = simulatingPathElements; state.forwardRequestUri = forwardRequestUri; state.forwardContextPath = forwardContextPath; state.forwardServletPath = forwardServletPath; state.forwardPathInfo = forwardPathInfo; state.simulatingForwardAttributes = simulatingForwardAttributes; state.forwardRequestUriAttribute = forwardRequestUriAttribute; state.forwardContextPathAttribute = forwardContextPathAttribute; state.forwardServletPathAttribute = forwardServletPathAttribute; state.forwardPathInfoAttribute = forwardPathInfoAttribute; state.forwardQueryStringAttribute = forwardQueryStringAttribute; state.simulatingIncludeAttributes = simulatingIncludeAttributes; state.includeRequestUriAttribute = includeRequestUriAttribute; state.includeContextPathAttribute = includeContextPathAttribute; state.includeServletPathAttribute = includeServletPathAttribute; state.includePathInfoAttribute = includePathInfoAttribute; state.includeQueryStringAttribute = includeQueryStringAttribute; stack.push(state); } private void pop() { State state = stack.pop(); simulatingPathElements = state.simulatingPathElements; forwardRequestUri = state.forwardRequestUri; forwardContextPath = state.forwardContextPath; forwardServletPath = state.forwardServletPath; forwardPathInfo = state.forwardPathInfo; simulatingForwardAttributes = state.simulatingForwardAttributes; forwardRequestUriAttribute = state.forwardRequestUriAttribute; forwardContextPathAttribute = state.forwardContextPathAttribute; forwardServletPathAttribute = state.forwardServletPathAttribute; forwardPathInfoAttribute = state.forwardPathInfoAttribute; forwardQueryStringAttribute = state.forwardQueryStringAttribute; simulatingIncludeAttributes = state.simulatingIncludeAttributes; includeRequestUriAttribute = state.includeRequestUriAttribute; includeContextPathAttribute = state.includeContextPathAttribute; includeServletPathAttribute = state.includeServletPathAttribute; includePathInfoAttribute = state.includePathInfoAttribute; includeQueryStringAttribute = state.includeQueryStringAttribute; } private boolean isStackEmpty() { return stack.isEmpty(); } public int getDepth() { return stack.size() + 1; } @Override public Object getAttribute(String name) { if (simulatingForwardAttributes) { if (name.equals(WebUtils.FORWARD_REQUEST_URI_ATTRIBUTE)) { return forwardRequestUriAttribute; } if (name.equals(WebUtils.FORWARD_CONTEXT_PATH_ATTRIBUTE)) { return forwardContextPathAttribute; } if (name.equals(WebUtils.FORWARD_SERVLET_PATH_ATTRIBUTE)) { return forwardServletPathAttribute; } if (name.equals(WebUtils.FORWARD_PATH_INFO_ATTRIBUTE)) { return forwardPathInfoAttribute; } if (name.equals(WebUtils.FORWARD_QUERY_STRING_ATTRIBUTE)) { return forwardQueryStringAttribute; } } if (simulatingIncludeAttributes) { if (name.equals(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE)) { return includeRequestUriAttribute; } if (name.equals(WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE)) { return includeContextPathAttribute; } if (name.equals(WebUtils.INCLUDE_SERVLET_PATH_ATTRIBUTE)) { return includeServletPathAttribute; } if (name.equals(WebUtils.INCLUDE_PATH_INFO_ATTRIBUTE)) { return includePathInfoAttribute; } if (name.equals(WebUtils.INCLUDE_QUERY_STRING_ATTRIBUTE)) { return includeQueryStringAttribute; } } return super.getAttribute(name); } @Override public Enumeration getAttributeNames() { if (simulatingForwardAttributes && simulatingIncludeAttributes) { return new ArrayOverlayEnumeration(SPECIAL_ATTRIBUTE_NAMES, super.getAttributeNames()); } if (simulatingForwardAttributes) { return new ArrayOverlayEnumeration(SPECIAL_ATTRIBUTE_NAMES, super.getAttributeNames(), 0, 5); } if (simulatingIncludeAttributes) { return new ArrayOverlayEnumeration(SPECIAL_ATTRIBUTE_NAMES, super.getAttributeNames(), 5, 10); } return super.getAttributeNames(); } @Override public String getRequestURI() { if (simulatingPathElements) { return forwardRequestUri; } return super.getRequestURI(); } @Override public String getContextPath() { if (simulatingPathElements) { return forwardContextPath; } return super.getContextPath(); } @Override public String getServletPath() { if (simulatingPathElements) { return forwardServletPath; } return super.getServletPath(); } @Override public String getPathInfo() { if (simulatingPathElements) { return forwardPathInfo; } return super.getPathInfo(); } @Override public RequestDispatcher getRequestDispatcher(String path) { if (simulatingPathElements || simulatingIncludeAttributes) { final RequestDispatcher dispatcher = super.getRequestDispatcher(path); return new RequestDispatcher() { @Override public void forward(ServletRequest request, ServletResponse response) throws ServletException, IOException { simulatingPathElements = false; try { dispatcher.forward(request, response); } finally { simulatingPathElements = true; } } @Override public void include(ServletRequest request, ServletResponse response) throws ServletException, IOException { simulatingIncludeAttributes = false; try { dispatcher.include(request, response); } finally { simulatingIncludeAttributes = true; } } }; } return super.getRequestDispatcher(path); } public static HttpServletRequest forward(HttpServletRequest request, String requestUri, String contextPath, String servletPath, String pathInfo) { SimulatedDispatchRequestWrapper dispatchWrapper = getDispatchWrapper(request); if (dispatchWrapper == null) { request = dispatchWrapper = new SimulatedDispatchRequestWrapper(request); } else { dispatchWrapper.push(); } dispatchWrapper.simulatingPathElements = true; dispatchWrapper.forwardRequestUri = requestUri; dispatchWrapper.forwardContextPath = contextPath; dispatchWrapper.forwardServletPath = servletPath; dispatchWrapper.forwardPathInfo = pathInfo; if (request.getAttribute(WebUtils.FORWARD_REQUEST_URI_ATTRIBUTE) == null) { dispatchWrapper.simulatingForwardAttributes = true; dispatchWrapper.forwardRequestUriAttribute = request.getRequestURI(); dispatchWrapper.forwardContextPathAttribute = request.getPathInfo(); dispatchWrapper.forwardServletPathAttribute = request.getServletPath(); dispatchWrapper.forwardPathInfoAttribute = request.getPathInfo(); dispatchWrapper.forwardQueryStringAttribute = request.getQueryString(); } return request; } public static HttpServletRequest include(HttpServletRequest request, String requestUri, String contextPath, String servletPath, String pathInfo, String queryString) { SimulatedDispatchRequestWrapper dispatchWrapper = getDispatchWrapper(request); if (dispatchWrapper == null) { request = dispatchWrapper = new SimulatedDispatchRequestWrapper(request); } else { dispatchWrapper.push(); } dispatchWrapper.simulatingIncludeAttributes = true; dispatchWrapper.includeRequestUriAttribute = requestUri; dispatchWrapper.includeContextPathAttribute = contextPath; dispatchWrapper.includeServletPathAttribute = servletPath; dispatchWrapper.includePathInfoAttribute = pathInfo; dispatchWrapper.includeQueryStringAttribute = queryString; return request; } public static HttpServletRequest release(HttpServletRequest request) { SimulatedDispatchRequestWrapper dispatchWrapper = getDispatchWrapper(request); if (dispatchWrapper == null) { throw new IllegalStateException("SimulatedDispatchRequestWrapper not found"); } if (!dispatchWrapper.isStackEmpty()) { dispatchWrapper.pop(); return request; } if (dispatchWrapper != request) { throw new IllegalStateException("Expected SimulatedDispatchRequestWrapper to be first in the request chain"); } return (HttpServletRequest) dispatchWrapper.getRequest(); } private static SimulatedDispatchRequestWrapper getDispatchWrapper(HttpServletRequest request) { while (request instanceof HttpServletRequestWrapper) { if (request instanceof SimulatedDispatchRequestWrapper) { return (SimulatedDispatchRequestWrapper) request; } request = (HttpServletRequest) ((HttpServletRequestWrapper) request).getRequest(); } return null; } /** * Overlays an enumeration with additional elements from an array. * * @param element type */ private static class ArrayOverlayEnumeration implements Enumeration { private final T[] array; private final Enumeration enumeration; private final int start; private final int end; private int pos; private T next; public ArrayOverlayEnumeration(T[] array, Enumeration enumeration) { this.array = array; this.enumeration = enumeration; this.start = 0; this.end = array.length; } public ArrayOverlayEnumeration(T[] array, Enumeration enumeration, int start, int end) { this.array = array; this.enumeration = enumeration; this.start = start; this.end = end; this.pos = start; } @Override public boolean hasMoreElements() { if (pos < end) { return true; } if (next != null) { return true; } next = findNext(); return next != null; } @Override public T nextElement() { if (pos < end) { return array[pos++]; } if (next == null) { next = findNext(); if (next == null) { throw new NoSuchElementException(); } } T result = next; next = null; return result; } private T findNext() { while (enumeration.hasMoreElements()) { T element = enumeration.nextElement(); boolean masked = false; for (int i = start; i < end; i++) { T arrayItem = array[i]; if (element.equals(arrayItem)) { masked = true; break; } } if (!masked) { return element; } } return null; } } }