| /* |
| * 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 java.io.IOException; |
| |
| 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.HttpServletResponse; |
| |
| import org.apache.sling.api.SlingConstants; |
| import org.apache.sling.api.SlingHttpServletRequest; |
| import org.apache.sling.api.request.RequestDispatcherOptions; |
| import org.apache.sling.api.request.RequestProgressTracker; |
| import org.apache.sling.api.resource.Resource; |
| import org.apache.sling.api.resource.ResourceWrapper; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| public class SlingRequestDispatcher implements RequestDispatcher { |
| |
| /** default log */ |
| private final Logger log = LoggerFactory.getLogger(getClass()); |
| |
| private Resource resource; |
| |
| private RequestDispatcherOptions options; |
| |
| private String path; |
| |
| public SlingRequestDispatcher(String path, RequestDispatcherOptions options) { |
| this.path = path; |
| this.options = options; |
| this.resource = null; |
| } |
| |
| public SlingRequestDispatcher(Resource resource, |
| RequestDispatcherOptions options) { |
| |
| this.resource = resource; |
| this.options = options; |
| this.path = resource.getPath(); |
| } |
| |
| @Override |
| public void include(ServletRequest request, ServletResponse sResponse) |
| throws ServletException, IOException { |
| |
| // guard access to the request and content data: If the request is |
| // not (wrapping) a SlingHttpServletRequest, accessing the request Data |
| // throws an IllegalArgumentException and we cannot continue |
| final ContentData cd; |
| try { |
| cd = RequestData.getRequestData(request).getContentData(); |
| } catch (IllegalArgumentException iae) { |
| throw new ServletException(iae.getMessage()); |
| } |
| |
| // ClassCastException is not expected here because we operate in |
| // HTTP requests only (it cannot be excluded though if some client |
| // code uses a ServletRequestWrapper rather than an |
| // HttpServletRequestWrapper ...) |
| final HttpServletRequest hRequest = (HttpServletRequest) request; |
| |
| // set the inclusion request attributes from the current request |
| final Object v1 = setAttribute(request, |
| SlingConstants.ATTR_REQUEST_CONTENT, cd.getResource()); |
| final Object v2 = setAttribute(request, |
| SlingConstants.ATTR_REQUEST_SERVLET, cd.getServlet()); |
| final Object v3 = setAttribute(request, |
| SlingConstants.ATTR_REQUEST_PATH_INFO, cd.getRequestPathInfo()); |
| final Object v4 = setAttribute(request, |
| SlingConstants.ATTR_INCLUDE_CONTEXT_PATH, hRequest.getContextPath()); |
| final Object v5 = setAttribute(request, |
| SlingConstants.ATTR_INCLUDE_PATH_INFO, hRequest.getPathInfo()); |
| final Object v6 = setAttribute(request, |
| SlingConstants.ATTR_INCLUDE_QUERY_STRING, hRequest.getQueryString()); |
| final Object v7 = setAttribute(request, |
| SlingConstants.ATTR_INCLUDE_REQUEST_URI, hRequest.getRequestURI()); |
| final Object v8 = setAttribute(request, |
| SlingConstants.ATTR_INCLUDE_SERVLET_PATH, hRequest.getServletPath()); |
| |
| try { |
| |
| dispatch(request, sResponse, true); |
| |
| } finally { |
| |
| // reset inclusion request attributes to previous values |
| request.setAttribute(SlingConstants.ATTR_REQUEST_CONTENT, v1); |
| request.setAttribute(SlingConstants.ATTR_REQUEST_SERVLET, v2); |
| request.setAttribute(SlingConstants.ATTR_REQUEST_PATH_INFO, v3); |
| request.setAttribute(SlingConstants.ATTR_INCLUDE_CONTEXT_PATH, v4); |
| request.setAttribute(SlingConstants.ATTR_INCLUDE_PATH_INFO, v5); |
| request.setAttribute(SlingConstants.ATTR_INCLUDE_QUERY_STRING, v6); |
| request.setAttribute(SlingConstants.ATTR_INCLUDE_REQUEST_URI, v7); |
| request.setAttribute(SlingConstants.ATTR_INCLUDE_SERVLET_PATH, v8); |
| |
| } |
| } |
| |
| @Override |
| public void forward(ServletRequest request, ServletResponse response) |
| throws ServletException, IOException { |
| |
| // fail forwarding if the response has already been committed |
| if (response.isCommitted()) { |
| throw new IllegalStateException("Response already committed"); |
| } |
| |
| // reset the response, will throw an IllegalStateException |
| // if already committed, which will not be the case because |
| // we already tested for this condition |
| response.resetBuffer(); |
| |
| // ensure inclusion information attributes are not set |
| request.removeAttribute(SlingConstants.ATTR_REQUEST_CONTENT); |
| request.removeAttribute(SlingConstants.ATTR_REQUEST_SERVLET); |
| request.removeAttribute(SlingConstants.ATTR_REQUEST_PATH_INFO); |
| request.removeAttribute(SlingConstants.ATTR_INCLUDE_CONTEXT_PATH); |
| request.removeAttribute(SlingConstants.ATTR_INCLUDE_PATH_INFO); |
| request.removeAttribute(SlingConstants.ATTR_INCLUDE_QUERY_STRING); |
| request.removeAttribute(SlingConstants.ATTR_INCLUDE_REQUEST_URI); |
| request.removeAttribute(SlingConstants.ATTR_INCLUDE_SERVLET_PATH); |
| |
| // now just include as normal |
| dispatch(request, response, false); |
| |
| // finally, we would have to ensure the response is committed |
| // and closed. Let's just flush the buffer and thus commit the |
| // response for now |
| response.flushBuffer(); |
| } |
| |
| private String getAbsolutePath(SlingHttpServletRequest request, String path) { |
| // path is already absolute |
| if (path.startsWith("/")) { |
| return path; |
| } |
| |
| // get parent of current request |
| String uri = request.getResource().getPath(); |
| int lastSlash = uri.lastIndexOf('/'); |
| if (lastSlash >= 0) { |
| uri = uri.substring(0, lastSlash); |
| } |
| |
| // append relative path to parent |
| return uri + '/' + path; |
| } |
| |
| private void dispatch(ServletRequest request, ServletResponse sResponse, |
| boolean include) throws ServletException, IOException { |
| SlingHttpServletRequest cRequest = RequestData.unwrap(request); |
| RequestData rd = RequestData.getRequestData(cRequest); |
| String absPath = getAbsolutePath(cRequest, path); |
| RequestProgressTracker requestProgressTracker = cRequest.getRequestProgressTracker(); |
| |
| // if the response is not an HttpServletResponse, fail gracefully not |
| // doing anything |
| if (!(sResponse instanceof HttpServletResponse)) { |
| log.error("include: Failed to include {}, response has wrong type", |
| absPath); |
| return; |
| } |
| |
| if (resource == null) { |
| String timerName = "resolveIncludedResource(" + absPath + ")"; |
| requestProgressTracker.startTimer(timerName); |
| |
| // resolve the absolute path in the resource resolver, using |
| // only those parts of the path as if it would be request path |
| resource = cRequest.getResourceResolver().resolve(absPath); |
| |
| // if the resource could not be resolved, fail gracefully |
| if (resource == null) { |
| log.error( |
| "include: Could not resolve {} to a resource, not including", |
| absPath); |
| return; |
| } |
| |
| requestProgressTracker.logTimer(timerName, |
| "path={0} resolves to Resource={1}", |
| absPath, resource); |
| } |
| |
| // ensure request path info and optional merges |
| SlingRequestPathInfo info = getMergedRequestPathInfo(cRequest); |
| requestProgressTracker.log( |
| "Including resource {0} ({1})", resource, info); |
| rd.getSlingRequestProcessor().dispatchRequest(request, sResponse, resource, |
| info, include); |
| } |
| |
| /** |
| * Returns a {@link SlingRequestPathInfo} object to use to select the |
| * servlet or script to call to render the resource to be dispatched to. |
| * <p> |
| * <b>Note:</b> If this request dispatcher has been created with resource |
| * type overwriting request dispatcher options, the resource to dispatch to |
| * may be wrapped with a {@link TypeOverwritingResourceWrapper} as a result |
| * of calling this method. |
| */ |
| private SlingRequestPathInfo getMergedRequestPathInfo( |
| final SlingHttpServletRequest cRequest) { |
| SlingRequestPathInfo info = new SlingRequestPathInfo(resource); |
| info = info.merge(cRequest.getRequestPathInfo()); |
| |
| // merge request dispatcher options and resource type overwrite |
| if (options != null) { |
| info = info.merge(options); |
| |
| // ensure overwritten resource type |
| String rtOverwrite = options.getForceResourceType(); |
| if (rtOverwrite != null |
| && !rtOverwrite.equals(resource.getResourceType())) { |
| resource = new TypeOverwritingResourceWrapper(resource, |
| rtOverwrite); |
| } |
| } |
| |
| return info; |
| } |
| |
| private Object setAttribute(final ServletRequest request, |
| final String name, final Object value) { |
| final Object oldValue = request.getAttribute(name); |
| request.setAttribute(name, value); |
| return oldValue; |
| } |
| |
| private static class TypeOverwritingResourceWrapper extends ResourceWrapper { |
| |
| private final String resourceType; |
| |
| TypeOverwritingResourceWrapper(Resource delegatee, String resourceType) { |
| super(delegatee); |
| this.resourceType = resourceType; |
| } |
| |
| @Override |
| public String getResourceType() { |
| return resourceType; |
| } |
| |
| /** |
| * Overwrite this here because the wrapped resource will return null as |
| * a super type instead of the resource type overwritten here |
| */ |
| @Override |
| public String getResourceSuperType() { |
| return null; |
| } |
| |
| @Override |
| public boolean isResourceType(final String resourceType) { |
| return this.getResourceResolver().isResourceType(this, resourceType); |
| } |
| |
| } |
| } |