/*
* Weblounge: Web Content Management System
* Copyright (c) 2003 - 2011 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.kernel.site;
import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import ch.entwine.weblounge.common.content.Renderer.RendererType;
import ch.entwine.weblounge.common.content.page.Page;
import ch.entwine.weblounge.common.content.page.PageTemplate;
import ch.entwine.weblounge.common.content.page.Pagelet;
import ch.entwine.weblounge.common.content.page.PageletRenderer;
import ch.entwine.weblounge.common.content.page.PageletURI;
import ch.entwine.weblounge.common.impl.content.page.MockPageImpl;
import ch.entwine.weblounge.common.impl.content.page.PageletImpl;
import ch.entwine.weblounge.common.impl.content.page.PageletURIImpl;
import ch.entwine.weblounge.common.impl.security.Guest;
import ch.entwine.weblounge.common.impl.site.PrecompileHttpServletRequest;
import ch.entwine.weblounge.common.impl.testing.MockHttpServletResponse;
import ch.entwine.weblounge.common.request.WebloungeRequest;
import ch.entwine.weblounge.common.security.SecurityService;
import ch.entwine.weblounge.common.site.Environment;
import ch.entwine.weblounge.common.site.Module;
import ch.entwine.weblounge.common.site.Site;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
/**
* This precompiler searches an OSGi bundle for Java Server Pages (JSP) and
* sends a request to <code>JspC</code>, the java server page compiler provided
* by Jasper in order to get the compilation work done before a user request
* hits the jsp.
*/
public class Precompiler {
/** Logging facility */
protected static final Logger logger = LoggerFactory.getLogger(SiteDispatcherServiceImpl.class);
/** The site servlet */
protected SiteServlet servlet = null;
/** The worker */
protected PrecompileWorker worker = null;
/** Flag to indicate whether to keep working or not */
protected boolean keepGoing = true;
/** Running flag */
protected boolean isRunning = true;
/** Switch for precompiler error logging */
protected boolean logErrors = true;
/** The default environment */
protected Environment environment = Environment.Any;
/** The security service */
protected SecurityService security = null;
/** The key used to identify this compilation process */
protected String compilerKey = null;
/**
* Creates a new precompiler for the site identified by the servlet.
*
* @param key
* the compiler key
* @param servlet
* the site servlet
* @param environment
* the environment to use
* @param security
* the security service
* @param logErrors
* <code>true</code> to log precompilation errors
*/
public Precompiler(String key, SiteServlet servlet, Environment environment,
SecurityService security, boolean logErrors) {
this.compilerKey = key;
this.servlet = servlet;
this.environment = environment;
this.security = security;
this.logErrors = logErrors;
}
/**
* Precompiles all of the bundle's server pages intoto the output directory as
* specified in the <code>scratchDir</code> setting of the compiler
* configuration.
*
* @param outputDir
* the path to the output directory
*/
public void precompile() {
worker = new PrecompileWorker(servlet);
Thread workerThread = new Thread(worker);
workerThread.setPriority(Thread.MIN_PRIORITY);
workerThread.setDaemon(true);
workerThread.start();
}
/**
* Returns <code>true</code> if the precompiler is still running.
*
* @return <code>true</code> if the compiler is still running
*/
public boolean isRunning() {
return isRunning;
}
/**
* Returns the key that is used to identify this compilation process.
*
* @return the compiler key
*/
public String getCompilerKey() {
return compilerKey;
}
/**
* Stops the current precompilation work.
*/
public void stop() {
logger.debug("Asking precompiler for '{}' to stop", servlet.getSite());
keepGoing = false;
}
class PrecompileWorker implements Runnable {
/** The servlet to use */
private SiteServlet servlet = null;
/**
* Creates a new jsp precompilation worker.
*
* @param servlet
* the servlet
*/
public PrecompileWorker(SiteServlet servlet) {
this.servlet = servlet;
}
/**
* {@inheritDoc}
*
* @see java.lang.Runnable#run()
*/
public void run() {
Site site = servlet.getSite();
// Prepare the mock request and response objects
PrecompileHttpServletRequest request = new PrecompileHttpServletRequest();
request.setServerName(site.getHostname(environment).getURL().getHost());
request.setServerPort(site.getHostname(environment).getURL().getPort());
request.setMethod(site.getHostname(environment).getURL().getProtocol());
request.setServletPath("");
// Prepare a fake page in order to prevent erratic behavior during
// precompilation
Page page = new MockPageImpl(site);
Pagelet pagelet = null;
for (Module m : site.getModules()) {
if (m.getRenderers().length > 0) {
PageletRenderer r = m.getRenderers()[0];
PageletURI pageletURI = new PageletURIImpl(page.getURI(), PageTemplate.DEFAULT_STAGE, 0);
pagelet = new PageletImpl(pageletURI, m.getIdentifier(), r.getIdentifier());
}
}
// Collect all renderers from modules and ask for precompilation
List<URL> rendererUrls = new ArrayList<URL>();
for (Module m : site.getModules()) {
if (!m.isEnabled())
break;
for (PageletRenderer p : m.getRenderers()) {
if (p.getRenderer() != null)
rendererUrls.add(p.getRenderer());
if (p.getRenderer(RendererType.Feed.name()) != null)
rendererUrls.add(p.getRenderer(RendererType.Feed.name()));
if (p.getRenderer(RendererType.Search.name()) != null)
rendererUrls.add(p.getRenderer(RendererType.Search.name()));
if (p.getEditor() != null)
rendererUrls.add(p.getEditor());
}
}
// Collect all site templates and ask for precompilation
for (PageTemplate t : site.getTemplates()) {
if (t.getRenderer() != null)
rendererUrls.add(t.getRenderer());
}
if (rendererUrls.size() < 1) {
logger.debug("No java server pages found to precompile for {}", site);
return;
}
// Make sure there is a user
security.setUser(new Guest(site.getIdentifier()));
security.setSite(site);
logger.info("Precompiling java server pages for '{}'", site);
int errorCount = 0;
Iterator<URL> rendererIterator = rendererUrls.iterator();
while (keepGoing && rendererIterator.hasNext()) {
MockHttpServletResponse response = new MockHttpServletResponse();
URL entry = rendererIterator.next();
String path = entry.getPath();
String pathInfo = path.substring(path.indexOf(site.getIdentifier()) + site.getIdentifier().length());
request.setPathInfo(pathInfo);
request.setRequestURI(pathInfo);
request.setAttribute(WebloungeRequest.PAGE, page);
request.setAttribute(WebloungeRequest.COMPOSER, page.getComposer(PageTemplate.DEFAULT_STAGE));
if (pagelet != null)
request.setAttribute(WebloungeRequest.PAGELET, pagelet);
try {
logger.debug("Precompiling {}:/{}", site, pathInfo);
servlet.service(request, response);
switch (response.getStatus()) {
case HttpServletResponse.SC_OK:
logger.debug("Precompilation of {}:/{} finished", site, pathInfo);
break;
case SC_NOT_FOUND:
logger.error("Precompilation failed: {}:/{}: not found", site, pathInfo);
break;
default:
logger.debug("Precompilation of {}:/{} failed with status {}", new Object[] {
site,
pathInfo,
response.getStatus() });
break;
}
} catch (Throwable t) {
while (t != t.getCause() && t.getCause() != null)
t = t.getCause();
if (logErrors)
logger.warn("Error precompiling {}:/{}: {}", new Object[] {
site,
pathInfo,
t.getMessage() });
errorCount++;
}
}
isRunning = false;
security.setUser(null);
security.setSite(null);
// Log the precompilation results
if (!keepGoing) {
logger.info("Precompilation for '{}' canceled", site);
} else if (errorCount > 0) {
String compilationResult = "finished";
compilationResult += " with " + errorCount + " errors";
logger.warn("Precompilation for '{}' {}", site, compilationResult);
if (!logErrors)
logger.info("Precompilation error logging can be enabled in the site dispatcher service");
} else {
logger.info("Precompilation for '{}' finished", site);
}
}
}
}