/*
* (C) Copyright 2006-2008 Nuxeo SAS (http://nuxeo.com/) and contributors.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser General Public License
* (LGPL) version 2.1 which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/lgpl.html
*
* This library 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.
*
* Contributors:
* bstefanescu
*
* $Id$
*/
package org.nuxeo.runtime.jetty;
import java.io.File;
import java.net.URL;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.mortbay.jetty.NCSARequestLog;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.handler.ContextHandlerCollection;
import org.mortbay.jetty.handler.RequestLogHandler;
import org.mortbay.jetty.webapp.Configuration;
import org.mortbay.jetty.webapp.WebAppContext;
import org.mortbay.jetty.webapp.WebInfConfiguration;
import org.mortbay.jetty.webapp.WebXmlConfiguration;
import org.mortbay.xml.XmlConfiguration;
import org.nuxeo.common.Environment;
import org.nuxeo.common.server.WebApplication;
import org.nuxeo.runtime.api.Framework;
import org.nuxeo.runtime.deployment.preprocessor.DeploymentPreprocessor;
import org.nuxeo.runtime.model.ComponentContext;
import org.nuxeo.runtime.model.ComponentInstance;
import org.nuxeo.runtime.model.ComponentName;
import org.nuxeo.runtime.model.DefaultComponent;
/**
* This component registers and configures an embedded Jetty server.
* <p>
* Contexts are registered like this:
* <p>
* First, if there is a {@code jetty.xml} config file, the contexts defined
* there are registered first; if there is no {@code jetty.xml}, a log context
* will be create programatically and registered first.
* <p>
* Second an empty collection context is registered. Here will be registered all
* regular war contexts.
* <p>
* Third, the root collection is registered. This way all requests not handled
* by regular wars are directed to the root war, which usually is the webengine
* war in a nxserver application.
*
* @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
*/
public class JettyComponent extends DefaultComponent {
public static final ComponentName NAME = new ComponentName(
"org.nuxeo.runtime.server");
public static final String XP_WEB_APP = "webapp";
public static final String XP_SERVLET = "servlet";
public static final String XP_FILTER = "filter";
public static final String P_SCAN_WEBDIR = "org.nuxeo.runtime.jetty.scanWebDir";
protected Server server;
protected ContextManager ctxMgr;
// here we are putting all regular war contexts
// the root context will be appended after this context collection to be
// sure the regular contexts are checked first
// This is because the root context is bound to / so if it is checked first
// it will consume
// all requests even if there is a context that is the target of the request
protected ContextHandlerCollection warContexts;
protected File config;
protected File log;
private static final Log logger = LogFactory.getLog(JettyComponent.class);
public Server getServer() {
return server;
}
@Override
public void activate(ComponentContext context) throws Exception {
// apply bundled configuration
URL cfg = null;
String cfgName = Framework.getProperty("org.nuxeo.jetty.config");
if (cfgName != null) {
if (cfgName.contains(":/")) {
cfg = new URL(cfgName);
} else { // assume a file
File file = new File(cfgName);
if (file.isFile()) {
cfg = file.toURI().toURL();
}
}
} else {
File file = new File(Environment.getDefault().getConfig(),
"jetty.xml");
if (file.isFile()) {
cfg = file.toURI().toURL();
}
}
boolean hasConfigFile = false;
if (cfg != null) {
hasConfigFile = true;
XmlConfiguration configuration = new XmlConfiguration(cfg);
server = (Server) configuration.configure();
} else {
int p = 8080;
String port = Environment.getDefault().getProperty("http_port");
if (port != null) {
try {
p = Integer.parseInt(port);
} catch (NumberFormatException e) {
// do noting
}
}
server = new Server(p);
}
// if a jetty.xml is present we don't configure logging - this should be
// done in that file.
if (!hasConfigFile) {
RequestLogHandler requestLogHandler = new RequestLogHandler();
File logDir = Environment.getDefault().getLog();
logDir.mkdirs();
File logFile = new File(logDir, "jetty.log");
NCSARequestLog requestLog = new NCSARequestLog(
logFile.getAbsolutePath());
requestLogHandler.setRequestLog(requestLog);
// handlers = new Handler[] {contexts, new DefaultHandler(),
// requestLogHandler};
server.addHandler(requestLogHandler);
server.setSendServerVersion(true);
server.setStopAtShutdown(true);
}
// create the war context
warContexts = new ContextHandlerCollection();
server.addHandler(warContexts);
// scan for WAR files
// deploy any war found in web directory
String scanWebDir = Framework.getProperty(P_SCAN_WEBDIR);
if (scanWebDir != null && scanWebDir.equals("true")) {
logger.info("Scanning for WARs in web directory");
File web = Environment.getDefault().getWeb();
scanForWars(web);
}
ctxMgr = new ContextManager(server);
// start the server
// server.start(); -> server will be start after frameworks starts to be
// sure that all services
// used by web.xml filters are registered.
}
@Override
public void deactivate(ComponentContext context) throws Exception {
ctxMgr = null;
server.stop();
server = null;
}
@Override
public void registerContribution(Object contribution,
String extensionPoint, ComponentInstance contributor)
throws Exception {
if (XP_WEB_APP.equals(extensionPoint)) {
File home = Environment.getDefault().getHome();
WebApplication app = (WebApplication) contribution;
if (app.needsWarPreprocessing()) {
logger.info("Starting deployment preprocessing");
DeploymentPreprocessor dp = new DeploymentPreprocessor(home);
dp.init();
dp.predeploy();
logger.info("Deployment preprocessing terminated");
}
WebAppContext ctx = new WebAppContext();
ctx.setContextPath(app.getContextPath());
String root = app.getWebRoot();
if (root != null) {
File file = new File(home, root);
ctx.setWar(file.getAbsolutePath());
}
String webXml = app.getConfigurationFile();
if (webXml != null) {
File file = new File(home, root);
ctx.setDescriptor(file.getAbsolutePath());
}
File defWebXml = new File(Environment.getDefault().getConfig(),
"default-web.xml");
if (defWebXml.isFile()) {
ctx.setDefaultsDescriptor(defWebXml.getAbsolutePath());
}
if ("/".equals(app.getContextPath())) { // the root context must be
// put at the end
server.addHandler(ctx);
} else {
warContexts.addHandler(ctx);
}
org.mortbay.log.Log.setLog(new Log4JLogger(logger));
// ctx.start();
// HandlerWrapper wrapper = (HandlerWrapper)ctx.getHandler();
// wrapper = (HandlerWrapper)wrapper.getHandler();
// wrapper.setHandler(new NuxeoServletHandler());
if (ctx.isFailed()) {
logger.error("Error in war deployment");
}
} else if (XP_FILTER.equals(extensionPoint)) {
ctxMgr.addFilter((FilterDescriptor)contribution);
} else if (XP_SERVLET.equals(extensionPoint)) {
ctxMgr.addServlet((ServletDescriptor)contribution);
}
}
public ContextManager getContextManager() {
return ctxMgr;
}
@Override
public void unregisterContribution(Object contribution,
String extensionPoint, ComponentInstance contributor)
throws Exception {
if (XP_WEB_APP.equals(extensionPoint)) {
} else if (XP_FILTER.equals(extensionPoint)) {
ctxMgr.removeFilter((FilterDescriptor)contribution);
} else if (XP_SERVLET.equals(extensionPoint)) {
ctxMgr.removeServlet((ServletDescriptor)contribution);
}
}
@Override
public <T> T getAdapter(Class<T> adapter) {
if (adapter == org.mortbay.jetty.Server.class) {
return adapter.cast(server);
}
return null;
}
@Override
public void applicationStarted(ComponentContext context) throws Exception {
if (server != null) {
try {
Thread t = Thread.currentThread();
ClassLoader oldcl = t.getContextClassLoader();
t.setContextClassLoader(getClass().getClassLoader());
try {
server.start();
} finally {
t.setContextClassLoader(oldcl);
}
} catch (Exception e) {
logger.error("Failed to start Jetty server", e);
}
}
}
private void scanForWars(File dir) {
scanForWars(dir, "");
}
private void scanForWars(File dir, String basePath) {
File[] roots = dir.listFiles();
if (roots != null) {
for (File root : roots) {
String name = root.getName();
if (name.endsWith(".war")) {
logger.info("Found war: " + name);
name = name.substring(0, name.length() - 4);
boolean isRoot = "root".equals(name);
String ctxPath = isRoot ? "/" : basePath + "/" + name;
WebAppContext ctx = new WebAppContext(
root.getAbsolutePath(), ctxPath);
ctx.setConfigurations(new Configuration[] {
new WebInfConfiguration(),
new WebXmlConfiguration() });
if (isRoot) {
server.addHandler(ctx);
} else {
warContexts.addHandler(ctx);
}
} else if (root.isDirectory()) {
scanForWars(root, basePath + "/" + name);
}
}
}
}
}