blob: 2bb2fa10cdfdc005b11b647a8f89f76b837186b0 [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;
import javax.servlet.GenericServlet;
import javax.servlet.Servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.Objects;
import org.apache.sling.api.request.SlingRequestEvent;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.auth.core.AuthenticationSupport;
import org.apache.sling.engine.impl.helper.ClientAbortException;
import org.apache.sling.engine.impl.helper.RequestListenerManager;
import org.apache.sling.engine.impl.helper.SlingServletContext;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.osgi.service.component.annotations.ReferencePolicyOption;
import org.osgi.service.component.propertytypes.ServiceDescription;
import org.osgi.service.component.propertytypes.ServiceVendor;
import org.osgi.service.http.whiteboard.HttpWhiteboardConstants;
import org.osgi.service.metatype.annotations.Designate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The <code>SlingMainServlet</code>
*/
@SuppressWarnings("serial")
@Component(configurationPid = Config.PID)
@ServiceDescription("Apache Sling Main Servlet")
@ServiceVendor("The Apache Software Foundation")
@Designate(ocd = Config.class)
public class SlingMainServlet extends GenericServlet {
@Reference(
cardinality = ReferenceCardinality.OPTIONAL,
policy = ReferencePolicy.DYNAMIC,
policyOption = ReferencePolicyOption.GREEDY)
private volatile RequestListenerManager requestListenerManager;
private BundleContext bundleContext;
/** default log */
private final Logger log = LoggerFactory.getLogger(SlingMainServlet.class);
/**
* The product info provider
*/
@Reference
private ProductInfoProvider productInfoProvider;
/**
* The Sling http context
*/
@Reference(target = SlingServletContext.TARGET)
private volatile ServletContext slingServletContext;
@Reference
private volatile SlingRequestProcessorImpl requestProcessorImpl;
private volatile boolean allowTrace;
private volatile ServiceRegistration<Servlet> servletRegistration;
// ---------- Servlet API -------------------------------------------------
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException {
if (req instanceof HttpServletRequest && res instanceof HttpServletResponse) {
HttpServletRequest request = (HttpServletRequest) req;
// set the thread name according to the request
String threadName = setThreadName(request);
final RequestListenerManager localRLM = requestListenerManager;
if (localRLM != null) {
localRLM.sendEvent(request, SlingRequestEvent.EventType.EVENT_INIT);
}
ResourceResolver resolver = null;
try {
if (!allowTrace && "TRACE".equals(request.getMethod())) {
HttpServletResponse response = (HttpServletResponse) res;
response.setStatus(405);
response.setHeader("Allow", "GET, HEAD, POST, PUT, DELETE, OPTIONS");
return;
}
// get ResourceResolver (set by AuthenticationSupport)
Object resolverObject = request.getAttribute(AuthenticationSupport.REQUEST_ATTRIBUTE_RESOLVER);
resolver = (resolverObject instanceof ResourceResolver) ? (ResourceResolver) resolverObject : null;
// real request handling for HTTP requests
// we don't check for null of requestProcessorImpl as we would throw an exception anyway in that case
requestProcessorImpl.doProcessRequest(request, (HttpServletResponse) res, resolver);
} catch (ClientAbortException cae) {
log.debug(
"service: ClientAbortException, probable cause is client aborted request or network problem",
cae);
} catch (Throwable t) {
// some failure while handling the request, log the issue
// and terminate. We do not call error handling here, because
// we assume the real request handling would have done this.
// So here we just log
log.error("service: Uncaught Problem handling the request", t);
} finally {
// close the resource resolver (not relying on servlet request
// listener to do this for now; see SLING-1270)
if (resolver != null) {
resolver.close();
}
if (localRLM != null) {
localRLM.sendEvent(request, SlingRequestEvent.EventType.EVENT_DESTROY);
}
// reset the thread name
if (threadName != null) {
Thread.currentThread().setName(threadName);
}
}
} else {
throw new ServletException("Apache Sling must be run in an HTTP servlet environment.");
}
}
// ---------- Property Setter for SCR --------------------------------------
@Modified
protected void modified(final Config config) {
setup(config);
}
private Dictionary<String, Object> getServletContextRegistrationProps(final String servletName) {
final Dictionary<String, Object> servletConfig = new Hashtable<>();
servletConfig.put(
HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT,
"(" + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_NAME + "=" + SlingHttpContext.SERVLET_CONTEXT_NAME
+ ")");
servletConfig.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN, "/");
if (servletName != null) {
servletConfig.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_NAME, servletName);
}
servletConfig.put(Constants.SERVICE_DESCRIPTION, "Apache Sling Engine Main Servlet");
servletConfig.put(Constants.SERVICE_VENDOR, "The Apache Software Foundation");
return servletConfig;
}
protected void setup(final Config config) {
// configure method filter
this.allowTrace = config.sling_trace_allow();
String servletName = config.servlet_name();
if (servletName == null || servletName.isEmpty()) {
servletName = this.productInfoProvider.getProductInfo();
}
if (this.servletRegistration == null) {
this.servletRegistration =
bundleContext.registerService(Servlet.class, this, getServletContextRegistrationProps(servletName));
} else {
// check if the servlet name has changed and update properties
if (!Objects.equals(
servletName,
this.servletRegistration
.getReference()
.getProperty(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_NAME))) {
this.servletRegistration.setProperties(getServletContextRegistrationProps(servletName));
}
}
}
@Activate
protected void activate(final BundleContext bundleContext, final Config config) {
this.bundleContext = bundleContext;
this.setup(config);
}
@Override
public void init() throws ServletException {
final ServletContext localContext = this.slingServletContext;
log.info("{} ready to serve requests", localContext != null ? localContext.getServerInfo() : "Sling");
}
@Deactivate
protected void deactivate() {
if (this.servletRegistration != null) {
this.servletRegistration.unregister();
this.servletRegistration = null;
}
this.bundleContext = null;
final ServletContext localContext = this.slingServletContext;
log.info("{} shut down", localContext != null ? localContext.getServerInfo() : "Sling");
}
/**
* Sets the name of the current thread to the IP address of the remote
* client with the current system time and the first request line consisting
* of the method, path and protocol.
*
* @param request The request to extract the remote IP address, method,
* request URL and protocol from.
* @return The name of the current thread before setting the new name.
*/
private String setThreadName(HttpServletRequest request) {
// get the name of the current thread (to be returned)
Thread thread = Thread.currentThread();
String oldThreadName = thread.getName();
// construct and set the new thread name of the form:
// 127.0.0.1 [1224156108055] GET /system/console/config HTTP/1.1
final StringBuilder buf = new StringBuilder();
buf.append(request.getRemoteAddr());
buf.append(" [").append(System.currentTimeMillis()).append("] ");
buf.append(request.getMethod()).append(' ');
buf.append(request.getRequestURI()).append(' ');
buf.append(request.getProtocol());
thread.setName(buf.toString());
// return the previous thread name
return oldThreadName;
}
}