blob: 7601020e0c73d031eb217c219c5b9f3765fe29a0 [file] [log] [blame]
/*
* 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 javax.servlet.DispatcherType;
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 java.io.IOException;
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 final RequestDispatcherOptions options;
private final String path;
private final boolean protectHeadersOnInclude;
private final boolean checkContentTypeOnInclude;
public SlingRequestDispatcher(
String path,
RequestDispatcherOptions options,
boolean protectHeadersOnInclude,
boolean checkContentTypeOnInclude) {
this.path = path;
this.options = options;
this.resource = null;
this.protectHeadersOnInclude = protectHeadersOnInclude;
this.checkContentTypeOnInclude = checkContentTypeOnInclude;
}
public SlingRequestDispatcher(
Resource resource,
RequestDispatcherOptions options,
boolean protectHeadersOnInclude,
boolean checkContentTypeOnInclude) {
this.resource = resource;
this.options = options;
this.path = resource.getPath();
this.protectHeadersOnInclude = protectHeadersOnInclude;
this.checkContentTypeOnInclude = checkContentTypeOnInclude;
}
@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;
final DispatchingInfo info = new DispatchingInfo(DispatcherType.INCLUDE);
info.setRequestContent(cd.getResource());
info.setRequestServlet(cd.getServlet());
info.setRequestPathInfo(cd.getRequestPathInfo());
info.setContextPath(hRequest.getContextPath());
info.setPathInfo(hRequest.getPathInfo());
info.setQueryString(hRequest.getQueryString());
info.setRequestUri(hRequest.getRequestURI());
info.setServletPath(hRequest.getServletPath());
dispatch(request, sResponse, info);
}
@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
final DispatchingInfo info = new DispatchingInfo(DispatcherType.FORWARD);
// now just include as normal
dispatch(request, response, info);
// finally, we would have to ensure the response is committed
// and closed
if (!response.isCommitted()) {
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;
}
/**
* Dispatch the request
* @param request The request
* @param response The response
* @param dispatchingInfo Is this an include (or forward)
*/
private void dispatch(
final ServletRequest request, final ServletResponse response, final DispatchingInfo dispatchingInfo)
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 (!(response 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);
if (dispatchingInfo.getType() == DispatcherType.INCLUDE) {
final boolean protectHeaders = this.options != null
? Boolean.parseBoolean(this.options.getOrDefault(
RequestDispatcherOptions.OPT_PROTECT_HEADERS_ON_INCLUDE,
String.valueOf(this.protectHeadersOnInclude)))
: this.protectHeadersOnInclude;
dispatchingInfo.setProtectHeadersOnInclude(protectHeaders);
dispatchingInfo.setCheckContentTypeOnInclude(this.checkContentTypeOnInclude);
}
rd.getSlingRequestProcessor().dispatchRequest(request, response, resource, info, dispatchingInfo);
}
/**
* 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 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);
}
}
}