/* * 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.sling.engine.impl.request; import static org.apache.sling.api.SlingConstants.SLING_CURRENT_SERVLET_NAME; import java.io.BufferedReader; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.ArrayList; import javax.servlet.Servlet; import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.ServletRequest; import javax.servlet.ServletRequestWrapper; import javax.servlet.ServletResponse; import javax.servlet.ServletResponseWrapper; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.SlingHttpServletResponse; import org.apache.sling.api.request.RecursionTooDeepException; import org.apache.sling.api.request.RequestPathInfo; import org.apache.sling.api.request.RequestProgressTracker; import org.apache.sling.api.request.RequestUtil; import org.apache.sling.api.request.TooManyCallsException; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ResourceResolver; import org.apache.sling.api.servlets.ServletResolver; import org.apache.sling.api.wrappers.SlingHttpServletRequestWrapper; import org.apache.sling.api.wrappers.SlingHttpServletResponseWrapper; import org.apache.sling.engine.impl.SlingHttpServletRequestImpl; import org.apache.sling.engine.impl.SlingHttpServletResponseImpl; import org.apache.sling.engine.impl.SlingMainServlet; import org.apache.sling.engine.impl.SlingRequestProcessorImpl; import org.apache.sling.engine.impl.StaticResponseHeader; import org.apache.sling.engine.impl.adapter.SlingServletRequestAdapter; import org.apache.sling.engine.impl.adapter.SlingServletResponseAdapter; import org.apache.sling.engine.impl.parameters.ParameterSupport; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The <code>RequestData</code> class provides access to objects which are set * on a Servlet Request wide basis such as the repository session, the * persistence manager, etc. * * The setup order is: * <ol> * <li>Invoke constructor</li> * <li>Invoke initResource()</li> * <li>Invoke initServlet()</li> * </ol> * @see ContentData */ public class RequestData { /** default log */ private final Logger log = LoggerFactory.getLogger(RequestData.class); /** * The default value for the number of recursive inclusions for a single * instance of this class (value is 50). */ public static final int DEFAULT_MAX_INCLUSION_COUNTER = 50; /** * The default value for the number of calls to the * {@link #service(SlingHttpServletRequest, SlingHttpServletResponse)} * method for a single instance of this class (value is 1000). */ public static final int DEFAULT_MAX_CALL_COUNTER = 1000; /** * The name of the request attribute providing the resource addressed by the * request URL. */ public static final String REQUEST_RESOURCE_PATH_ATTR = "$$sling.request.resource$$"; /** * The maximum inclusion depth (default * {@link #DEFAULT_MAX_INCLUSION_COUNTER}). This value is compared to the * number of entries in the {@link #contentDataStack} when the * {@link #pushContent(Resource, RequestPathInfo)} method is called. */ private static int maxInclusionCounter = DEFAULT_MAX_INCLUSION_COUNTER; /** * The maximum number of scripts which may be included through the * {@link #service(SlingHttpServletRequest, SlingHttpServletResponse)} * method (default {@link #DEFAULT_MAX_CALL_COUNTER}). This number should * not be too small to prevent request aborts. */ private static int maxCallCounter = DEFAULT_MAX_CALL_COUNTER; /** * The name of the request attribute to override the max call number (-1 for infinite or integer value). */ private static String REQUEST_MAX_CALL_OVERRIDE = "sling.max.calls"; private static SlingMainServlet SLING_MAIN_SERVLET; private static SlingHttpServletRequestFactory REQUEST_FACTORY; private static ArrayList<StaticResponseHeader> ADDITIONAL_RESPONSE_HEADERS; /** The SlingMainServlet used for request dispatching and other stuff */ private final SlingRequestProcessorImpl slingRequestProcessor; private final long startTimestamp; /** The original servlet Servlet Request Object */ private HttpServletRequest servletRequest; /** The original servlet Servlet Response object */ private HttpServletResponse servletResponse; /** The original servlet Servlet Request Object */ private SlingHttpServletRequest slingRequest; /** The original servlet Servlet Response object */ private SlingHttpServletResponse slingResponse; /** The parameter support class */ private ParameterSupport parameterSupport; private ResourceResolver resourceResolver; private RequestProgressTracker requestProgressTracker; /** the current ContentData */ private ContentData currentContentData; /** * the number of servlets called by * {@link #service(SlingHttpServletRequest, SlingHttpServletResponse)} */ private int servletCallCounter; /** * The name of the currently active serlvet. * * @see #setActiveServletName(String) * @see #getActiveServletName() */ private String activeServletName; /** * Recursion depth */ private int recursionDepth; /** * The peak value for the recursion depth. */ private int peakRecusionDepth; public static void setMaxCallCounter(int maxCallCounter) { RequestData.maxCallCounter = maxCallCounter; } public static int getMaxCallCounter() { return maxCallCounter; } public static void setMaxIncludeCounter(int maxInclusionCounter) { RequestData.maxInclusionCounter = maxInclusionCounter; } public static int getMaxIncludeCounter() { return maxInclusionCounter; } public static void setSlingMainServlet(final SlingMainServlet slingMainServlet) { RequestData.SLING_MAIN_SERVLET = slingMainServlet; RequestData.REQUEST_FACTORY = null; } public static void setAdditionalResponseHeaders(ArrayList<StaticResponseHeader> mappings){ RequestData.ADDITIONAL_RESPONSE_HEADERS = mappings; } public static ArrayList<StaticResponseHeader> getAdditionalResponseHeaders() { return ADDITIONAL_RESPONSE_HEADERS; } public RequestData(SlingRequestProcessorImpl slingRequestProcessor, HttpServletRequest request, HttpServletResponse response) { this.startTimestamp = System.currentTimeMillis(); this.slingRequestProcessor = slingRequestProcessor; this.servletRequest = request; this.servletResponse = response; this.slingRequest = getSlingHttpServletRequestFactory().createRequest(this, this.servletRequest); this.slingResponse = new SlingHttpServletResponseImpl(this, servletResponse); // Getting the RequestProgressTracker from the request attributes like // this should not be generally used, it's just a way to pass it from // its creation point to here, so it's made available via // the Sling request's getRequestProgressTracker method. final Object o = request.getAttribute(RequestProgressTracker.class.getName()); if(o instanceof SlingRequestProgressTracker) { this.requestProgressTracker = (SlingRequestProgressTracker)o; } else { log.warn("SlingRequestProgressTracker not found in request attributes"); this.requestProgressTracker = new SlingRequestProgressTracker(request); } } public Resource initResource(ResourceResolver resourceResolver) { // keep the resource resolver for request processing this.resourceResolver = resourceResolver; // resolve the resource requestProgressTracker.startTimer("ResourceResolution"); final SlingHttpServletRequest request = getSlingRequest(); StringBuffer requestURL = servletRequest.getRequestURL(); String path = request.getPathInfo(); if (requestURL.indexOf(";") > -1 && !path.contains(";")) { final String decodedURL; try { decodedURL = URLDecoder.decode(requestURL.toString(), "UTF-8"); } catch (UnsupportedEncodingException e) { throw new AssertionError("UTF-8 encoding is not supported"); } path = path.concat(decodedURL.substring(decodedURL.indexOf(';'))); } Resource resource = resourceResolver.resolve(request, path); if (request.getAttribute(REQUEST_RESOURCE_PATH_ATTR) == null) { request.setAttribute(REQUEST_RESOURCE_PATH_ATTR, resource.getPath()); } requestProgressTracker.logTimer("ResourceResolution", "URI={0} resolves to Resource={1}", getServletRequest().getRequestURI(), resource); return resource; } public void initServlet(final Resource resource, final ServletResolver sr) { // the resource and the request path info, will never be null RequestPathInfo requestPathInfo = new SlingRequestPathInfo(resource); ContentData contentData = setContent(resource, requestPathInfo); requestProgressTracker.log("Resource Path Info: {0}", requestPathInfo); // finally resolve the servlet for the resource requestProgressTracker.startTimer("ServletResolution"); Servlet servlet = sr.resolveServlet(slingRequest); requestProgressTracker.logTimer("ServletResolution", "URI={0} handled by Servlet={1}", getServletRequest().getRequestURI(), (servlet == null ? "-none-" : RequestUtil.getServletName(servlet))); contentData.setServlet(servlet); } public SlingRequestProcessorImpl getSlingRequestProcessor() { return slingRequestProcessor; } public HttpServletRequest getServletRequest() { return servletRequest; } public HttpServletResponse getServletResponse() { return servletResponse; } public SlingHttpServletRequest getSlingRequest() { return slingRequest; } public SlingHttpServletResponse getSlingResponse() { return slingResponse; } // ---------- Request Helper /** * Unwraps the ServletRequest to a SlingHttpServletRequest. * * @throws IllegalArgumentException If the <code>request</code> is not a * <code>SlingHttpServletRequest</code> and not a * <code>ServletRequestWrapper</code> wrapping a * <code>SlingHttpServletRequest</code>. */ public static SlingHttpServletRequest unwrap(ServletRequest request) { // early check for most cases if (request instanceof SlingHttpServletRequest) { return (SlingHttpServletRequest) request; } // unwrap wrappers while (request instanceof ServletRequestWrapper) { request = ((ServletRequestWrapper) request).getRequest(); // immediate termination if we found one if (request instanceof SlingHttpServletRequest) { return (SlingHttpServletRequest) request; } } // if we unwrapped everything and did not find a // SlingHttpServletRequest, we lost throw new IllegalArgumentException( "ServletRequest not wrapping SlingHttpServletRequest"); } /** * Unwraps the SlingHttpServletRequest to a SlingHttpServletRequestImpl * * @param request * @throws IllegalArgumentException If <code>request</code> is not a * <code>SlingHttpServletRequestImpl</code> and not * <code>SlingHttpServletRequestWrapper</code> wrapping a * <code>SlingHttpServletRequestImpl</code>. */ public static SlingHttpServletRequestImpl unwrap( SlingHttpServletRequest request) { while (request instanceof SlingHttpServletRequestWrapper) { request = ((SlingHttpServletRequestWrapper) request).getSlingRequest(); } if (request instanceof SlingHttpServletRequestImpl) { return (SlingHttpServletRequestImpl) request; } throw new IllegalArgumentException( "SlingHttpServletRequest not of correct type"); } /** * Unwraps the ServletRequest to a SlingHttpServletRequest. * * @throws IllegalArgumentException If the <code>response</code> is not a * <code>SlingHttpServletResponse</code> and not a * <code>ServletResponseWrapper</code> wrapping a * <code>SlingHttpServletResponse</code>. */ public static SlingHttpServletResponse unwrap(ServletResponse response) { // early check for most cases if (response instanceof SlingHttpServletResponse) { return (SlingHttpServletResponse) response; } // unwrap wrappers while (response instanceof ServletResponseWrapper) { response = ((ServletResponseWrapper) response).getResponse(); // immediate termination if we found one if (response instanceof SlingHttpServletResponse) { return (SlingHttpServletResponse) response; } } // if we unwrapped everything and did not find a // SlingHttpServletResponse, we lost throw new IllegalArgumentException( "ServletResponse not wrapping SlingHttpServletResponse"); } /** * Unwraps a SlingHttpServletResponse to a SlingHttpServletResponseImpl * * @param response * @throws IllegalArgumentException If <code>response</code> is not a * <code>SlingHttpServletResponseImpl</code> and not * <code>SlingHttpServletResponseWrapper</code> wrapping a * <code>SlingHttpServletResponseImpl</code>. */ public static SlingHttpServletResponseImpl unwrap( SlingHttpServletResponse response) { while (response instanceof SlingHttpServletResponseWrapper) { response = ((SlingHttpServletResponseWrapper) response).getSlingResponse(); } if (response instanceof SlingHttpServletResponseImpl) { return (SlingHttpServletResponseImpl) response; } throw new IllegalArgumentException( "SlingHttpServletResponse not of correct type"); } /** * @param request * @throws IllegalArgumentException If the <code>request</code> is not a * <code>SlingHttpServletRequest</code> and not a * <code>ServletRequestWrapper</code> wrapping a * <code>SlingHttpServletRequest</code>. */ public static RequestData getRequestData(ServletRequest request) { return unwrap(unwrap(request)).getRequestData(); } /** * @param request * @throws IllegalArgumentException If <code>request</code> is not a * <code>SlingHttpServletRequestImpl</code> and not * <code>SlingHttpServletRequestWrapper</code> wrapping a * <code>SlingHttpServletRequestImpl</code>. */ public static RequestData getRequestData(SlingHttpServletRequest request) { return unwrap(request).getRequestData(); } /** * @param request * @throws IllegalArgumentException if <code>request</code> is not a * <code>HttpServletRequest</code> of if <code>request</code> * is not backed by <code>SlingHttpServletRequestImpl</code>. */ public static SlingHttpServletRequest toSlingHttpServletRequest( ServletRequest request) { // unwrap to SlingHttpServletRequest, may throw if no // SlingHttpServletRequest is wrapped in request SlingHttpServletRequest cRequest = unwrap(request); // ensure the SlingHttpServletRequest is backed by // SlingHttpServletRequestImpl RequestData.unwrap(cRequest); // if the request is not wrapper at all, we are done if (cRequest == request) { return cRequest; } // ensure the request is a HTTP request if (!(request instanceof HttpServletRequest)) { throw new IllegalArgumentException("Request is not an HTTP request"); } // otherwise, we create a new response wrapping the servlet response // and unwrapped component response return new SlingServletRequestAdapter(cRequest, (HttpServletRequest) request); } /** * @param response * @throws IllegalArgumentException if <code>response</code> is not a * <code>HttpServletResponse</code> of if * <code>response</code> is not backed by * <code>SlingHttpServletResponseImpl</code>. */ public static SlingHttpServletResponse toSlingHttpServletResponse( ServletResponse response) { // unwrap to SlingHttpServletResponse SlingHttpServletResponse cResponse = unwrap(response); // check type of response, don't care actually for the response itself RequestData.unwrap(cResponse); // if the servlet response is actually the SlingHttpServletResponse, we // are done if (cResponse == response) { return cResponse; } // ensure the response is a HTTP response if (!(response instanceof HttpServletResponse)) { throw new IllegalArgumentException( "Response is not an HTTP response"); } // otherwise, we create a new response wrapping the servlet response // and unwrapped component response return new SlingServletResponseAdapter(cResponse, (HttpServletResponse) response); } /** * Helper method to call the servlet for the current content data. If the * current content data has no servlet, <em>NOT_FOUND</em> (404) error is * sent and the method terminates. * <p> * If the the servlet exists, the * {@link org.apache.sling.api.SlingConstants#SLING_CURRENT_SERVLET_NAME} request attribute is set * to the name of that servlet and that servlet name is also set as the * {@link #setActiveServletName(String) currently active servlet}. After * the termination of the servlet (normal or throwing a Throwable) the * request attribute is reset to the previous value. The name of the * currently active servlet is only reset to the previous value if the * servlet terminates normally. In case of a Throwable, the active servlet * name is not reset and indicates which servlet caused the potential abort * of the request. * * @param request The request object for the service method * @param response The response object for the service method * @throws IOException May be thrown by the servlet's service method * @throws ServletException May be thrown by the servlet's service method */ public static void service(SlingHttpServletRequest request, SlingHttpServletResponse response) throws IOException, ServletException { RequestData requestData = RequestData.getRequestData(request); Servlet servlet = requestData.getContentData().getServlet(); if (servlet == null) { response.sendError(HttpServletResponse.SC_NOT_FOUND, "No Servlet to handle request"); } else { String name = RequestUtil.getServletName(servlet); // verify the number of service calls in this request if (requestData.hasServletMaxCallCount(request)) { throw new TooManyCallsException(name); } // replace the current servlet name in the request Object oldValue = request.getAttribute(SLING_CURRENT_SERVLET_NAME); request.setAttribute(SLING_CURRENT_SERVLET_NAME, name); // setup the tracker for this service call String timerName = name + "#" + requestData.servletCallCounter; requestData.servletCallCounter++; requestData.getRequestProgressTracker().startTimer(timerName); try { String callerServlet = requestData.setActiveServletName(name); servlet.service(request, response); requestData.setActiveServletName(callerServlet); } finally { request.setAttribute(SLING_CURRENT_SERVLET_NAME, oldValue); requestData.getRequestProgressTracker().logTimer(timerName); } } } // ---------- Content inclusion stacking ----------------------------------- public ContentData setContent(final Resource resource, final RequestPathInfo requestPathInfo) { if ( this.recursionDepth >= maxInclusionCounter) { throw new RecursionTooDeepException(requestPathInfo.getResourcePath()); } this.recursionDepth++; if (this.recursionDepth > this.peakRecusionDepth) { this.peakRecusionDepth = this.recursionDepth; } currentContentData = new ContentData(resource, requestPathInfo); return currentContentData; } public void resetContent(final ContentData data) { this.recursionDepth--; currentContentData = data; } public ContentData getContentData() { return currentContentData; } public ResourceResolver getResourceResolver() { return resourceResolver; } public RequestProgressTracker getRequestProgressTracker() { return requestProgressTracker; } public int getPeakRecusionDepth() { return peakRecusionDepth; } public int getServletCallCount() { return servletCallCounter; } /** * Returns {@code true} if the number of {@code RequestDispatcher.include} * calls has been reached within the given request. That maximum number may * either be defined by the {@link #REQUEST_MAX_CALL_OVERRIDE} request * attribute or the {@link SlingMainServlet#PROP_MAX_CALL_COUNTER} * configuration of the {@link SlingMainServlet}. * * @param request The request to check * @return {@code true} if the maximum number of calls has been reached (or * surpassed) */ private boolean hasServletMaxCallCount(final ServletRequest request) { // verify the number of service calls in this request log.debug("Servlet call counter : {}", getServletCallCount()); // max number of calls can be overriden with a request attribute (-1 for // infinite or integer value) int maxCallCounter = RequestData.getMaxCallCounter(); Object reqMaxOverride = request.getAttribute(REQUEST_MAX_CALL_OVERRIDE); if (reqMaxOverride instanceof Number) { maxCallCounter = ((Number) reqMaxOverride).intValue(); } return (maxCallCounter >= 0) && getServletCallCount() >= maxCallCounter; } public long getElapsedTimeMsec() { return System.currentTimeMillis() - startTimestamp; } /** * Sets the name of the currently active servlet and returns the name of the * previously active servlet. */ public String setActiveServletName(String servletName) { String old = activeServletName; activeServletName = servletName; return old; } /** * Returns the name of the currently active servlet. If this name is not * <code>null</code> at the end of request processing, more precisly in * the case of an uncaught <code>Throwable</code> at the end of request * processing, this is the name of the servlet causing the uncaught * <code>Throwable</code>. */ public String getActiveServletName() { return activeServletName; } public <Type> Type adaptTo(Object object, Class<Type> type) { return SLING_MAIN_SERVLET.adaptTo(object, type); } public String getMimeType(String fileName) { return SLING_MAIN_SERVLET.getMimeType(fileName); } // ---------- Parameter support ------------------------------------------- public ServletInputStream getInputStream() throws IOException { if (parameterSupport != null && parameterSupport.requestDataUsed()) { throw new IllegalStateException( "Request Data has already been read"); } // may throw IllegalStateException if the reader has already been // acquired return getServletRequest().getInputStream(); } public BufferedReader getReader() throws UnsupportedEncodingException, IOException { if (parameterSupport != null && parameterSupport.requestDataUsed()) { throw new IllegalStateException( "Request Data has already been read"); } // may throw IllegalStateException if the input stream has already been // acquired return getServletRequest().getReader(); } public ParameterSupport getParameterSupport() { if (parameterSupport == null) { parameterSupport = ParameterSupport.getInstance(getServletRequest()); } return parameterSupport; } // SlingHttpServletRequest instance factory private static SlingHttpServletRequestFactory getSlingHttpServletRequestFactory() { SlingHttpServletRequestFactory factory = RequestData.REQUEST_FACTORY; if (factory == null) { factory = new SlingHttpServletRequestFactory() { @Override public SlingHttpServletRequest createRequest(RequestData requestData, HttpServletRequest request) { return new SlingHttpServletRequestImpl(requestData, request); } }; RequestData.REQUEST_FACTORY = factory; } return factory; } private static interface SlingHttpServletRequestFactory { SlingHttpServletRequest createRequest(RequestData requestData, HttpServletRequest request); } }