/*
* Weblounge: Web Content Management System Copyright (c) 2009 The Weblounge
* Team http://entwinemedia.com/weblounge
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package ch.entwine.weblounge.dispatcher.impl;
import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
import ch.entwine.weblounge.cache.CacheService;
import ch.entwine.weblounge.common.impl.request.RequestUtils;
import ch.entwine.weblounge.common.impl.request.WebloungeRequestImpl;
import ch.entwine.weblounge.common.impl.request.WebloungeResponseImpl;
import ch.entwine.weblounge.common.request.RequestListener;
import ch.entwine.weblounge.common.request.ResponseCache;
import ch.entwine.weblounge.common.request.WebloungeRequest;
import ch.entwine.weblounge.common.request.WebloungeResponse;
import ch.entwine.weblounge.common.security.SecurityService;
import ch.entwine.weblounge.common.site.Environment;
import ch.entwine.weblounge.common.site.Site;
import ch.entwine.weblounge.dispatcher.DispatchListener;
import ch.entwine.weblounge.dispatcher.RequestHandler;
import ch.entwine.weblounge.dispatcher.SiteDispatcherService;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* This is the main dispatcher for weblounge, every request starts and ends
* here. Using the <code>HttpServiceTracker</code>, the servlet is registered
* with an instance of the OSGi web service.
* <p>
* The servlet is also where you enable and disable response caching by calling
* <code>setResponseCache()</code> with the appropriate implementation
* reference.
*/
public final class WebloungeDispatcherServlet extends HttpServlet {
/** Serial version uid */
private static final long serialVersionUID = 8939686825567275614L;
/** Logger */
private static final Logger logger = LoggerFactory.getLogger(WebloungeDispatcherServlet.class);
/** Value of the X-Powered-By header */
private static final String POWERED_BY = "Weblounge Content Management System";
/** Response default encoding */
private static final String DEFAULT_RESPONSE_ENCODING = "UTF-8";
/** The environment */
private Environment environment = Environment.Production;
/** The sites that are online */
private transient SiteDispatcherService sites = null;
/** The security service */
private SecurityService securityService = null;
/** List of request listeners */
private List<RequestListener> requestListeners = null;
/** List of dispatcher listeners */
private List<DispatchListener> dispatcher = null;
/** List of dispatcher listeners */
private Set<RequestHandler> requestHandler = null;
/** List with well known urls and files */
private static List<String> wellknownFiles = new ArrayList<String>();
/** List of offline sites that have already issued a warning once */
private final Set<String> missingRepositoryWarnings = new TreeSet<String>();
/** List of missing sites that have already issued a warning once */
private final Set<String> missingSiteWarnings = new TreeSet<String>();
/** The response caches */
private Map<String, ResponseCache> caches = null;
/** Name of this Weblounge instance */
private String instanceName = null;
static {
wellknownFiles.add("/favicon.ico");
wellknownFiles.add("/robots.txt");
}
/**
* Creates a new instance of the weblounge dispatcher servlet.
*
* @param environment
* the environment
*/
WebloungeDispatcherServlet(Environment environment) {
this.environment = environment;
requestListeners = new CopyOnWriteArrayList<RequestListener>();
dispatcher = new CopyOnWriteArrayList<DispatchListener>();
requestHandler = new TreeSet<RequestHandler>(new Comparator<RequestHandler>() {
public int compare(RequestHandler handler1, RequestHandler handler2) {
int compare = Integer.valueOf(handler2.getPriority()).compareTo(Integer.valueOf(handler1.getPriority()));
// FIXME if 0 is returned the request handler will not be added?!
if (compare == 0)
return 1;
return compare;
}
});
caches = new HashMap<String, ResponseCache>();
}
/**
* Sets the name of this Weblounge instance or <code>null</code> if no
* instance name was configured.
*
* @param instanceName
* name of the instance
*/
public void setName(String instanceName) {
this.instanceName = instanceName;
}
/**
* {@inheritDoc}
*
* @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest,
* javax.servlet.http.HttpServletResponse)
*/
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
super.doGet(request, response);
}
/**
* {@inheritDoc}
*
* @see javax.servlet.http.HttpServlet#doDelete(javax.servlet.http.HttpServletRequest,
* javax.servlet.http.HttpServletResponse)
*/
@Override
protected void doDelete(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
super.doDelete(request, response);
}
/**
* {@inheritDoc}
*
* @see javax.servlet.http.HttpServlet#doHead(javax.servlet.http.HttpServletRequest,
* javax.servlet.http.HttpServletResponse)
*/
@Override
protected void doHead(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
super.doHead(request, response);
}
/**
* {@inheritDoc}
*
* @see javax.servlet.http.HttpServlet#doOptions(javax.servlet.http.HttpServletRequest,
* javax.servlet.http.HttpServletResponse)
*/
@Override
protected void doOptions(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
super.doOptions(request, response);
}
/**
* {@inheritDoc}
*
* @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest,
* javax.servlet.http.HttpServletResponse)
*/
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
super.doPost(request, response);
}
/**
* {@inheritDoc}
*
* @see javax.servlet.http.HttpServlet#doPut(javax.servlet.http.HttpServletRequest,
* javax.servlet.http.HttpServletResponse)
*/
@Override
protected void doPut(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
super.doPut(request, response);
}
/**
* {@inheritDoc}
*
* @see javax.servlet.http.HttpServlet#doTrace(javax.servlet.http.HttpServletRequest,
* javax.servlet.http.HttpServletResponse)
*/
@Override
protected void doTrace(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
super.doTrace(request, response);
}
/**
* {@inheritDoc}
*
* @see javax.servlet.http.HttpServlet#getLastModified(javax.servlet.http.HttpServletRequest)
*/
@Override
protected long getLastModified(HttpServletRequest req) {
return super.getLastModified(req);
}
/**
* {@inheritDoc}
*
* @see javax.servlet.http.HttpServlet#service(javax.servlet.http.HttpServletRequest,
* javax.servlet.http.HttpServletResponse)
*/
@Override
protected void service(HttpServletRequest httpRequest,
HttpServletResponse httpResponse) throws ServletException, IOException {
// Return the instance name if available
if (instanceName != null) {
httpResponse.addHeader("X-Weblounge-Instance", instanceName);
}
if (sites == null) {
httpResponse.sendError(SC_SERVICE_UNAVAILABLE);
return;
}
logger.debug("Serving {}", httpRequest.getRequestURI());
// Get the site dispatcher
Site site = securityService.getSite();
if (site == null) {
site = getSiteByRequest(httpRequest);
securityService.setSite(site);
}
boolean isSpecialRequest = StringUtils.isNotBlank(httpRequest.getHeader("X-Weblounge-Special"));
// See if a site dispatcher was found, and if so, if it's enabled
if (site == null) {
String serverName = httpRequest.getScheme() + "://" + httpRequest.getServerName();
if (httpRequest.getServerPort() != 80)
serverName += ":" + httpRequest.getServerPort();
if (!wellknownFiles.contains(httpRequest.getRequestURI()) && !missingSiteWarnings.contains(serverName)) {
missingSiteWarnings.add(serverName);
logger.warn("No site found to handle {}", serverName);
}
httpResponse.sendError(SC_NOT_FOUND);
return;
} else if (!site.isOnline() && !isSpecialRequest) {
if (site.getContentRepository() == null) {
if (!missingRepositoryWarnings.contains(site.getIdentifier())) {
logger.warn("No content repository connected to site '{}'", site);
missingRepositoryWarnings.add(site.getIdentifier());
} else {
logger.debug("No content repository connected to site '{}'", site);
}
} else {
logger.debug("Ignoring request for disabled site '{}'", site);
}
httpResponse.sendError(SC_SERVICE_UNAVAILABLE);
return;
}
// Make sure the response is buffered
httpResponse = new BufferedHttpServletResponse(httpResponse);
// Get the servlet that is responsible for the site's content
Servlet siteServlet = sites.getSiteServlet(site);
// Get the response cache, if available
ResponseCache cache = caches.get(site.getIdentifier());
// Wrap for caching
if (cache != null) {
httpResponse = cache.createCacheableResponse(httpRequest, httpResponse);
}
// Wrap request and response
WebloungeRequestImpl request = new WebloungeRequestImpl(httpRequest, siteServlet, environment);
WebloungeResponseImpl response = new WebloungeResponseImpl(httpResponse);
// Configure request and response objects
request.init(site);
response.setRequest(request);
response.setResponseCache(cache);
response.setCharacterEncoding(DEFAULT_RESPONSE_ENCODING);
response.setHeader("X-Powered-By", POWERED_BY);
response.setDateHeader("Date", Calendar.getInstance().getTimeInMillis());
// Notify listeners about starting request
fireRequestStarted(request, response, site);
boolean requestServed = false;
// Ask the registered request handler if they are willing to handle
// the request.
try {
securityService.setSite(site);
request.setUser(securityService.getUser());
for (RequestHandler handler : requestHandler) {
try {
logger.trace("Asking {} to serve {}", handler, request);
if (handler.service(request, response)) {
requestServed = true;
logger.debug("{} served request {}", handler, request);
if (response.hasError()) {
logger.debug("Request processing failed on {}", request);
fireRequestFailed(request, response, site);
} else {
fireRequestDelivered(request, response, site);
}
return;
}
} catch (Throwable t) {
response.invalidate();
String params = RequestUtils.dumpParameters(request);
if (t.getCause() != null) {
t = t.getCause();
}
logger.error("Request handler '{}' failed to handle {} {}", new Object[] {
handler,
request,
params });
logger.error(t.getMessage(), t);
DispatchUtils.sendInternalError(t.getMessage(), request, response);
break;
}
}
} finally {
securityService.setSite(null);
if (requestServed) {
response.endResponse();
response.flushBuffer();
logger.debug("Finished processing of {}", httpRequest.getRequestURI());
} else {
logger.debug("No handler found for {}", request);
DispatchUtils.sendNotFound(request, response);
if (cache != null)
cache.invalidate(response);
fireRequestFailed(request, response, site);
}
}
}
/**
* Returns the site that is being targeted by <code>request</code>.
*
* @param request
* the http request
* @return the target site or <code>null</code>
*/
private Site getSiteByRequest(HttpServletRequest request) {
if (sites == null)
return null;
Site site = sites.findSiteByRequest(request);
return site;
}
/**
* Adds <code>listener</code> to the list of request listeners if it has not
* already been registered. The listener is called a few times during the life
* cycle of a request.
*
* @param listener
* the lister
*/
void addRequestListener(RequestListener listener) {
if (!requestListeners.contains(listener)) {
requestListeners.add(listener);
}
}
/**
* Removes the listener from the list of request listeners.
*
* @param listener
* the listener to remove
*/
void removeRequestListener(RequestListener listener) {
requestListeners.remove(listener);
}
/**
* Adds <code>listener</code> to the list of dispatch listeners if it has not
* already been registered. The listener is called every time the current
* request is internally being included or forwarded using the
* <code>include</code> or <code>forward</code> method of this class.
*
* @param listener
* the lister
*/
void addDispatchListener(DispatchListener listener) {
if (!dispatcher.contains(listener)) {
dispatcher.add(listener);
}
}
/**
* Removes the listener from the list of request listeners.
*
* @param listener
* the listener to remove
*/
void removeDispatchListener(DispatchListener listener) {
dispatcher.remove(listener);
}
/**
* Adds <code>handler</code> to the list of request handler if it has not
* already been registered. The handler is called every time a request enters
* the system.
*
* @param handler
* the request handler
*/
void addRequestHandler(RequestHandler handler) {
requestHandler.add(handler);
requestHandler.size();
}
/**
* Removes the request handler from the list of handlers.
*
* @param handler
* the request handler to remove
*/
void removeRequestHandler(RequestHandler handler) {
requestHandler.remove(handler);
}
/**
* Registers the response cache with the main dispatcher servlet.
*
* @param cache
* the response cache
*/
void addResponseCache(CacheService cache) {
caches.put(cache.getIdentifier(), cache);
logger.info("Response caching activated for site '{}'", cache.getIdentifier());
}
/**
* Removes the response cache from the main dispatcher servlet.
*
* @param cache
* the response cache
*/
void removeResponseCache(CacheService cache) {
caches.remove(cache.getIdentifier());
logger.info("Response caching deactivated for site '{}'", cache.getIdentifier());
}
/**
* Method to fire a <code>startRequest()</code> message to all registered
* <code>RequestListeners</code>.
*
* @param request
* the starting request
* @param response
* the servlet response
* @param site
* the target site
*/
protected void fireRequestStarted(WebloungeRequest request,
WebloungeResponse response, Site site) {
for (int i = 0; i < requestListeners.size(); i++) {
RequestListener listener = requestListeners.get(i);
listener.requestStarted(request, response);
}
}
/**
* Method to fire a <code>requestDelivered()</code> message to all registered
* <code>RequestListeners</code>.
*
* @param request
* the delivered request
* @param response
* the servlet response
* @param site
* the target site
*/
protected void fireRequestDelivered(WebloungeRequest request,
WebloungeResponse response, Site site) {
for (int i = 0; i < requestListeners.size(); i++) {
RequestListener listener = requestListeners.get(i);
listener.requestDelivered(request, response);
}
}
/**
* Method to fire a <code>requestFailed()</code> message to all registered
* <code>RequestListeners</code>.
*
* @param request
* the failing request
* @param response
* the servlet response
* @param site
* the target site
*/
protected void fireRequestFailed(WebloungeRequest request,
WebloungeResponse response, Site site) {
WebloungeResponseImpl resp = ((WebloungeResponseImpl) response);
for (int i = 0; i < requestListeners.size(); i++) {
RequestListener listener = requestListeners.get(i);
listener.requestFailed(request, response, resp.getStatus());
}
if (site != null)
site.requestFailed(request, response, resp.getStatus());
}
/**
* Sets the environment that is used to determine the correct context for
* requests.
*
* @param environment
* the environment
*/
public void setEnvironment(Environment environment) {
this.environment = environment;
}
/**
* Sets the site locator.
*
* @param siteDispatcher
* the site locator
*/
void setSiteDispatcher(SiteDispatcherService siteDispatcher) {
this.sites = siteDispatcher;
}
/**
* Removes the site locator.
*
* @param siteDispatcher
* the site locator
*/
void removeSiteDispatcher(SiteDispatcherService siteDispatcher) {
this.sites = null;
}
/**
* Sets the security service.
*
* @param securityService
* the security service
*/
void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
/**
* {@inheritDoc}
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "Weblounge dispatcher servlet";
}
}