/*
* (C) Copyright 2006-2008 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed 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.
*
* Contributors:
* bstefanescu
*
* $Id$
*/
package org.nuxeo.runtime.jetty;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
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.HandlerCollection;
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.common.utils.ExceptionUtils;
import org.nuxeo.runtime.api.Framework;
import org.nuxeo.runtime.model.ComponentContext;
import org.nuxeo.runtime.model.ComponentInstance;
import org.nuxeo.runtime.model.ComponentName;
import org.nuxeo.runtime.model.DefaultComponent;
import org.xml.sax.SAXException;
/**
* 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 XP_LISTENERS = "listeners";
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) {
// apply bundled configuration
URL cfg = null;
String cfgName = Framework.getProperty("org.nuxeo.jetty.config");
if (cfgName != null) {
if (cfgName.contains(":/")) {
try {
cfg = new URL(cfgName);
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
} else { // assume a file
File file = new File(cfgName);
if (file.isFile()) {
try {
cfg = file.toURI().toURL();
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
}
} else {
File file = new File(Environment.getDefault().getConfig(), "jetty.xml");
if (file.isFile()) {
try {
cfg = file.toURI().toURL();
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
}
boolean hasConfigFile = false;
if (cfg != null) {
hasConfigFile = true;
XmlConfiguration configuration;
try {
configuration = new XmlConfiguration(cfg);
} catch (SAXException | IOException e) {
throw new RuntimeException(e);
}
try {
server = (Server) configuration.configure();
} catch (Exception e) { // stupid Jetty API throws Exception
throw ExceptionUtils.runtimeException(e);
}
} 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 handler if needed
HandlerCollection hc = (HandlerCollection) server.getHandler();
warContexts = (ContextHandlerCollection) hc.getChildHandlerByClass(ContextHandlerCollection.class);
if (warContexts == null) {
// 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) {
ctxMgr = null;
try {
server.stop();
} catch (Exception e) { // stupid Jetty API throws Exception
throw ExceptionUtils.runtimeException(e);
}
server = null;
}
@Override
public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
if (XP_WEB_APP.equals(extensionPoint)) {
File home = Environment.getDefault().getHome();
WebApplication app = (WebApplication) contribution;
// TODO preprocessing was removed from this component -
// preprocessing should be done in another bundle
// if still required (on equinox distribution)
// 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);
} else if (XP_LISTENERS.equals(extensionPoint)) {
ctxMgr.addLifecycleListener((ServletContextListenerDescriptor) contribution);
}
}
public ContextManager getContextManager() {
return ctxMgr;
}
@Override
public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
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);
} else if (XP_LISTENERS.equals(extensionPoint)) {
ctxMgr.removeLifecycleListener((ServletContextListenerDescriptor) contribution);
}
}
@Override
public <T> T getAdapter(Class<T> adapter) {
if (adapter == org.mortbay.jetty.Server.class) {
return adapter.cast(server);
}
return null;
}
// let's nuxeo runtime get access to the JNDI context
// injected through the JettyTransactionalListener
protected ClassLoader nuxeoCL;
public void setNuxeoClassLoader(ClassLoader cl) {
nuxeoCL = cl;
}
protected ClassLoader getClassLoader(ClassLoader cl) {
if (!Boolean.valueOf(System.getProperty("org.nuxeo.jetty.propagateNaming"))) {
return cl;
}
if (nuxeoCL == null) {
return cl;
}
return nuxeoCL;
}
@Override
public int getApplicationStartedOrder() {
return -100;
}
@Override
public void applicationStarted(ComponentContext context) {
if (server == null) {
return;
}
ctxMgr.applyLifecycleListeners();
Thread t = Thread.currentThread();
ClassLoader oldcl = t.getContextClassLoader();
t.setContextClassLoader(getClass().getClassLoader());
try {
server.start();
} catch (Exception e) { // stupid Jetty API throws Exception
throw ExceptionUtils.runtimeException(e);
} finally {
t.setContextClassLoader(getClassLoader(oldcl));
}
}
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);
}
}
}
}
}