/*
* 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;
import static org.apache.sling.api.SlingConstants.ERROR_SERVLET_NAME;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Writer;
import java.security.AccessControlException;
import javax.servlet.FilterChain;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.UnavailableException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.sling.api.SlingException;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.SlingServletException;
import org.apache.sling.api.request.RequestPathInfo;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceNotFoundException;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.servlets.ServletResolver;
import org.apache.sling.api.wrappers.SlingHttpServletResponseWrapper;
import org.apache.sling.engine.SlingRequestProcessor;
import org.apache.sling.engine.impl.filter.AbstractSlingFilterChain;
import org.apache.sling.engine.impl.filter.FilterHandle;
import org.apache.sling.engine.impl.filter.RequestSlingFilterChain;
import org.apache.sling.engine.impl.filter.ServletFilterManager;
import org.apache.sling.engine.impl.filter.ServletFilterManager.FilterChainType;
import org.apache.sling.engine.impl.filter.SlingComponentFilterChain;
import org.apache.sling.engine.impl.parameters.ParameterSupport;
import org.apache.sling.engine.impl.request.ContentData;
import org.apache.sling.engine.impl.request.RequestData;
import org.apache.sling.engine.impl.request.RequestHistoryConsolePlugin;
import org.apache.sling.engine.servlets.ErrorHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SlingRequestProcessorImpl implements SlingRequestProcessor {
/** default log */
private final Logger log = LoggerFactory.getLogger(SlingRequestProcessorImpl.class);
// used fields ....
private final DefaultErrorHandler errorHandler = new DefaultErrorHandler();
private ServletResolver servletResolver;
private ServletFilterManager filterManager;
private RequestProcessorMBeanImpl mbean;
// ---------- helper setters
void setServerInfo(final String serverInfo) {
errorHandler.setServerInfo(serverInfo);
}
void setErrorHandler(final ErrorHandler eh) {
errorHandler.setDelegate(eh);
}
void unsetErrorHandler(final ErrorHandler eh) {
if (errorHandler.getDelegate() == eh) {
errorHandler.setDelegate(null);
}
}
void setServletResolver(final ServletResolver servletResolver) {
this.servletResolver = servletResolver;
}
void unsetServletResolver(final ServletResolver servletResolver) {
if (this.servletResolver == servletResolver) {
this.servletResolver = null;
}
}
void setFilterManager(final ServletFilterManager filterManager) {
this.filterManager = filterManager;
}
void setMBean(final RequestProcessorMBeanImpl mbean) {
this.mbean = mbean;
}
/**
* This method is directly called by the Sling main servlet.
*/
public void doProcessRequest(final HttpServletRequest servletRequest,
final HttpServletResponse servletResponse,
final ResourceResolver resourceResolver) throws IOException {
// setting the Sling request and response
final RequestData requestData = new RequestData(this, servletRequest,
servletResponse);
final SlingHttpServletRequest request = requestData.getSlingRequest();
final SlingHttpServletResponse response = requestData.getSlingResponse();
// record the request for the web console display
RequestHistoryConsolePlugin.recordRequest(request);
try {
final ServletResolver sr = this.servletResolver;
// check that we have all required services
if (resourceResolver == null) {
throw new UnavailableException("ResourceResolver");
} else if (sr == null) {
throw new UnavailableException("ServletResolver");
}
// initialize the request data - resolve resource and servlet
Resource resource = requestData.initResource(resourceResolver);
requestData.initServlet(resource, sr);
FilterHandle[] filters = filterManager.getFilters(FilterChainType.REQUEST);
if (filters != null) {
FilterChain processor = new RequestSlingFilterChain(this,
filters);
request.getRequestProgressTracker().log(
"Applying " + FilterChainType.REQUEST + "filters");
processor.doFilter(request, response);
} else {
// no filters, directly call resource level filters and servlet
processComponent(request, response, FilterChainType.COMPONENT);
}
} catch ( final SlingHttpServletResponseImpl.WriterAlreadyClosedException wace ) {
log.error("Writer has already been closed.", wace);
} catch (ResourceNotFoundException rnfe) {
// send this exception as a 404 status
log.info("service: Resource {} not found", rnfe.getResource());
handleError(HttpServletResponse.SC_NOT_FOUND, rnfe.getMessage(),
request, response);
} catch (final SlingException se) {
// if we have request data and a non-null active servlet name
// we assume, that this is the name of the causing servlet
if (requestData.getActiveServletName() != null) {
request.setAttribute(ERROR_SERVLET_NAME,
requestData.getActiveServletName());
}
// send this exception as is (albeit unwrapping and wrapped
// exception.
Throwable t = se;
while ( t instanceof SlingException && t.getCause() != null ) {
t = t.getCause();
}
log.error("service: Uncaught SlingException", t);
handleError(t, request, response);
} catch (AccessControlException ace) {
// SLING-319 if anything goes wrong, send 403/FORBIDDEN
log.info(
"service: Authenticated user {} does not have enough rights to executed requested action",
request.getRemoteUser());
handleError(HttpServletResponse.SC_FORBIDDEN, null, request,
response);
} catch (UnavailableException ue) {
// exception is thrown before the SlingHttpServletRequest/Response
// is properly set up due to missing dependencies. In this case
// we must not use the Sling error handling infrastructure but
// just return a 503 status response handled by the servlet
// container environment
final int status = HttpServletResponse.SC_SERVICE_UNAVAILABLE;
final String errorMessage = ue.getMessage()
+ " service missing, cannot service requests";
log.error("{} , sending status {}", errorMessage, status);
servletResponse.sendError(status, errorMessage);
} catch (IOException ioe) {
// forward IOException up the call chain to properly handle it
throw ioe;
} catch (Throwable t) {
// if we have request data and a non-null active servlet name
// we assume, that this is the name of the causing servlet
if (requestData.getActiveServletName() != null) {
request.setAttribute(ERROR_SERVLET_NAME,
requestData.getActiveServletName());
}
log.error("service: Uncaught Throwable", t);
handleError(t, request, response);
} finally {
if (mbean != null) {
mbean.addRequestData(requestData);
}
}
}
// ---------- SlingRequestProcessor interface
/**
* @see org.apache.sling.engine.SlingRequestProcessor#processRequest(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, org.apache.sling.api.resource.ResourceResolver)
*/
public void processRequest(final HttpServletRequest servletRequest,
final HttpServletResponse servletResponse,
final ResourceResolver resourceResolver) throws IOException {
// set the marker for the parameter support
final Object oldValue = servletRequest.getAttribute(ParameterSupport.MARKER_IS_SERVICE_PROCESSING);
servletRequest.setAttribute(ParameterSupport.MARKER_IS_SERVICE_PROCESSING, Boolean.TRUE);
try {
this.doProcessRequest(servletRequest, servletResponse, resourceResolver);
} finally {
// restore the old value
if ( oldValue != null ) {
servletRequest.setAttribute(ParameterSupport.MARKER_IS_SERVICE_PROCESSING, oldValue);
} else {
servletRequest.removeAttribute(ParameterSupport.MARKER_IS_SERVICE_PROCESSING);
}
}
}
/**
* Renders the component defined by the RequestData's current ComponentData
* instance after calling all filters of the given
* {@link org.apache.sling.engine.impl.filter.ServletFilterManager.FilterChainType
* filterChainType}.
*
* @param request
* @param response
* @param filterChainType
* @throws IOException
* @throws ServletException
*/
public void processComponent(SlingHttpServletRequest request,
SlingHttpServletResponse response,
final FilterChainType filterChainType) throws IOException,
ServletException {
FilterHandle filters[] = filterManager.getFilters(filterChainType);
if (filters != null) {
FilterChain processor = new SlingComponentFilterChain(filters);
request.getRequestProgressTracker().log(
"Applying " + filterChainType + "filters");
processor.doFilter(request, response);
} else {
log.debug("service: No Resource level filters, calling servlet");
RequestData.service(request, response);
}
}
// ---------- Generic Content Request processor ----------------------------
/**
* Dispatches the request on behalf of the
* {@link org.apache.sling.engine.impl.request.SlingRequestDispatcher}.
*/
public void dispatchRequest(ServletRequest request,
ServletResponse response, Resource resource,
RequestPathInfo resolvedURL, boolean include) throws IOException,
ServletException {
// we need a SlingHttpServletRequest/SlingHttpServletResponse tupel
// to continue
SlingHttpServletRequest cRequest = RequestData.toSlingHttpServletRequest(request);
SlingHttpServletResponse cResponse = RequestData.toSlingHttpServletResponse(response);
// get the request data (and btw check the correct type)
final RequestData requestData = RequestData.getRequestData(cRequest);
final ContentData oldContentData = requestData.getContentData();
final ContentData contentData = requestData.setContent(resource, resolvedURL);
try {
// resolve the servlet
Servlet servlet = servletResolver.resolveServlet(cRequest);
contentData.setServlet(servlet);
FilterChainType type = include
? FilterChainType.INCLUDE
: FilterChainType.FORWARD;
processComponent(cRequest, cResponse, type);
} finally {
requestData.resetContent(oldContentData);
}
}
// ---------- Error Handling with Filters
void handleError(final int status, final String message,
final SlingHttpServletRequest request,
SlingHttpServletResponse response) throws IOException {
// wrap the response ensuring getWriter will fall back to wrapping
// the response output stream if reset does not reset this
response = new ErrorResponseWrapper(response);
FilterHandle[] filters = filterManager.getFilters(FilterChainType.ERROR);
if (filters != null && filters.length > 0) {
FilterChain processor = new AbstractSlingFilterChain(filters) {
@Override
protected void render(SlingHttpServletRequest request,
SlingHttpServletResponse response) throws IOException {
errorHandler.handleError(status, message, request, response);
}
};
request.getRequestProgressTracker().log(
"Applying " + FilterChainType.ERROR + " filters");
try {
processor.doFilter(request, response);
} catch (ServletException se) {
throw new SlingServletException(se);
}
} else {
errorHandler.handleError(status, message, request, response);
}
}
// just rethrow the exception as explained in the class comment
private void handleError(final Throwable throwable,
final SlingHttpServletRequest request,
SlingHttpServletResponse response) throws IOException {
// wrap the response ensuring getWriter will fall back to wrapping
// the response output stream if reset does not reset this
response = new ErrorResponseWrapper(response);
FilterHandle[] filters = filterManager.getFilters(FilterChainType.ERROR);
if (filters != null && filters.length > 0) {
FilterChain processor = new AbstractSlingFilterChain(filters) {
@Override
protected void render(SlingHttpServletRequest request,
SlingHttpServletResponse response) throws IOException {
errorHandler.handleError(throwable, request, response);
}
};
request.getRequestProgressTracker().log(
"Applying " + FilterChainType.ERROR + " filters");
try {
processor.doFilter(request, response);
} catch (ServletException se) {
throw new SlingServletException(se);
}
} else {
errorHandler.handleError(throwable, request, response);
}
}
private static class ErrorResponseWrapper extends
SlingHttpServletResponseWrapper {
private PrintWriter writer;
public ErrorResponseWrapper(SlingHttpServletResponse wrappedResponse) {
super(wrappedResponse);
}
@Override
public PrintWriter getWriter() throws IOException {
if (writer == null) {
try {
writer = super.getWriter();
} catch (IllegalStateException ise) {
// resetting the response did not reset the output channel
// status and we have to create a writer based on the output
// stream using the character encoding already set on the
// response, defaulting to ISO-8859-1
OutputStream out = getOutputStream();
String encoding = getCharacterEncoding();
if (encoding == null) {
encoding = "ISO-8859-1";
setCharacterEncoding(encoding);
}
Writer w = new OutputStreamWriter(out, encoding);
writer = new PrintWriter(w);
}
}
return writer;
}
/**
* Flush the writer if the {@link #getWriter()} method was called
* to potentially wrap an OuputStream still existing in the response.
*/
@Override
public void flushBuffer() throws IOException {
if (writer != null) {
writer.flush();
}
super.flushBuffer();
}
}
}