001/* 002 * (C) Copyright 2006-2008 Nuxeo SAS (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * bstefanescu 016 * 017 * $Id$ 018 */ 019 020package org.nuxeo.runtime.jetty; 021 022import java.io.File; 023import java.io.IOException; 024import java.net.MalformedURLException; 025import java.net.URL; 026 027import org.apache.commons.logging.Log; 028import org.apache.commons.logging.LogFactory; 029import org.mortbay.jetty.NCSARequestLog; 030import org.mortbay.jetty.Server; 031import org.mortbay.jetty.handler.ContextHandlerCollection; 032import org.mortbay.jetty.handler.HandlerCollection; 033import org.mortbay.jetty.handler.RequestLogHandler; 034import org.mortbay.jetty.webapp.Configuration; 035import org.mortbay.jetty.webapp.WebAppContext; 036import org.mortbay.jetty.webapp.WebInfConfiguration; 037import org.mortbay.jetty.webapp.WebXmlConfiguration; 038import org.mortbay.xml.XmlConfiguration; 039import org.nuxeo.common.Environment; 040import org.nuxeo.common.server.WebApplication; 041import org.nuxeo.common.utils.ExceptionUtils; 042import org.nuxeo.runtime.api.Framework; 043import org.nuxeo.runtime.model.ComponentContext; 044import org.nuxeo.runtime.model.ComponentInstance; 045import org.nuxeo.runtime.model.ComponentName; 046import org.nuxeo.runtime.model.DefaultComponent; 047import org.xml.sax.SAXException; 048 049/** 050 * This component registers and configures an embedded Jetty server. 051 * <p> 052 * Contexts are registered like this: 053 * <p> 054 * First, if there is a {@code jetty.xml} config file, the contexts defined there are registered first; if there is no 055 * {@code jetty.xml}, a log context will be create programatically and registered first. 056 * <p> 057 * Second an empty collection context is registered. Here will be registered all regular war contexts. 058 * <p> 059 * Third, the root collection is registered. This way all requests not handled by regular wars are directed to the root 060 * war, which usually is the webengine war in a nxserver application. 061 * 062 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 063 */ 064public class JettyComponent extends DefaultComponent { 065 066 public static final ComponentName NAME = new ComponentName("org.nuxeo.runtime.server"); 067 068 public static final String XP_WEB_APP = "webapp"; 069 070 public static final String XP_SERVLET = "servlet"; 071 072 public static final String XP_FILTER = "filter"; 073 074 public static final String XP_LISTENERS = "listeners"; 075 076 public static final String P_SCAN_WEBDIR = "org.nuxeo.runtime.jetty.scanWebDir"; 077 078 protected Server server; 079 080 protected ContextManager ctxMgr; 081 082 // here we are putting all regular war contexts 083 // the root context will be appended after this context collection to be 084 // sure the regular contexts are checked first 085 // This is because the root context is bound to / so if it is checked first 086 // it will consume 087 // all requests even if there is a context that is the target of the request 088 protected ContextHandlerCollection warContexts; 089 090 protected File config; 091 092 protected File log; 093 094 private static final Log logger = LogFactory.getLog(JettyComponent.class); 095 096 public Server getServer() { 097 return server; 098 } 099 100 @Override 101 public void activate(ComponentContext context) { 102 103 // apply bundled configuration 104 URL cfg = null; 105 106 String cfgName = Framework.getProperty("org.nuxeo.jetty.config"); 107 if (cfgName != null) { 108 if (cfgName.contains(":/")) { 109 try { 110 cfg = new URL(cfgName); 111 } catch (MalformedURLException e) { 112 throw new RuntimeException(e); 113 } 114 } else { // assume a file 115 File file = new File(cfgName); 116 if (file.isFile()) { 117 try { 118 cfg = file.toURI().toURL(); 119 } catch (MalformedURLException e) { 120 throw new RuntimeException(e); 121 } 122 } 123 } 124 } else { 125 File file = new File(Environment.getDefault().getConfig(), "jetty.xml"); 126 if (file.isFile()) { 127 try { 128 cfg = file.toURI().toURL(); 129 } catch (MalformedURLException e) { 130 throw new RuntimeException(e); 131 } 132 } 133 } 134 boolean hasConfigFile = false; 135 if (cfg != null) { 136 hasConfigFile = true; 137 XmlConfiguration configuration; 138 try { 139 configuration = new XmlConfiguration(cfg); 140 } catch (SAXException | IOException e) { 141 throw new RuntimeException(e); 142 } 143 try { 144 server = (Server) configuration.configure(); 145 } catch (Exception e) { // stupid Jetty API throws Exception 146 throw ExceptionUtils.runtimeException(e); 147 } 148 } else { 149 int p = 8080; 150 String port = Environment.getDefault().getProperty("http_port"); 151 if (port != null) { 152 try { 153 p = Integer.parseInt(port); 154 } catch (NumberFormatException e) { 155 // do noting 156 } 157 } 158 server = new Server(p); 159 160 } 161 162 // if a jetty.xml is present we don't configure logging - this should be 163 // done in that file. 164 if (!hasConfigFile) { 165 RequestLogHandler requestLogHandler = new RequestLogHandler(); 166 File logDir = Environment.getDefault().getLog(); 167 logDir.mkdirs(); 168 File logFile = new File(logDir, "jetty.log"); 169 NCSARequestLog requestLog = new NCSARequestLog(logFile.getAbsolutePath()); 170 requestLogHandler.setRequestLog(requestLog); 171 // handlers = new Handler[] {contexts, new DefaultHandler(), 172 // requestLogHandler}; 173 server.addHandler(requestLogHandler); 174 server.setSendServerVersion(true); 175 server.setStopAtShutdown(true); 176 177 } 178 179 // create the war context handler if needed 180 HandlerCollection hc = (HandlerCollection) server.getHandler(); 181 warContexts = (ContextHandlerCollection) hc.getChildHandlerByClass(ContextHandlerCollection.class); 182 if (warContexts == null) { 183 // create the war context 184 warContexts = new ContextHandlerCollection(); 185 server.addHandler(warContexts); 186 } 187 188 // scan for WAR files 189 // deploy any war found in web directory 190 String scanWebDir = Framework.getProperty(P_SCAN_WEBDIR); 191 if (scanWebDir != null && scanWebDir.equals("true")) { 192 logger.info("Scanning for WARs in web directory"); 193 File web = Environment.getDefault().getWeb(); 194 scanForWars(web); 195 } 196 197 ctxMgr = new ContextManager(server); 198 199 // start the server 200 // server.start(); -> server will be start after frameworks starts to be 201 // sure that all services 202 // used by web.xml filters are registered. 203 } 204 205 @Override 206 public void deactivate(ComponentContext context) { 207 ctxMgr = null; 208 try { 209 server.stop(); 210 } catch (Exception e) { // stupid Jetty API throws Exception 211 throw ExceptionUtils.runtimeException(e); 212 } 213 server = null; 214 } 215 216 @Override 217 public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 218 if (XP_WEB_APP.equals(extensionPoint)) { 219 File home = Environment.getDefault().getHome(); 220 WebApplication app = (WebApplication) contribution; 221 // TODO preprocessing was removed from this component - 222 // preprocessing should be done in another bundle 223 // if still required (on equinox distribution) 224 // if (app.needsWarPreprocessing()) { 225 // logger.info("Starting deployment preprocessing"); 226 // DeploymentPreprocessor dp = new DeploymentPreprocessor(home); 227 // dp.init(); 228 // dp.predeploy(); 229 // logger.info("Deployment preprocessing terminated"); 230 // } 231 232 WebAppContext ctx = new WebAppContext(); 233 ctx.setContextPath(app.getContextPath()); 234 String root = app.getWebRoot(); 235 if (root != null) { 236 File file = new File(home, root); 237 ctx.setWar(file.getAbsolutePath()); 238 } 239 String webXml = app.getConfigurationFile(); 240 if (webXml != null) { 241 File file = new File(home, root); 242 ctx.setDescriptor(file.getAbsolutePath()); 243 } 244 File defWebXml = new File(Environment.getDefault().getConfig(), "default-web.xml"); 245 if (defWebXml.isFile()) { 246 ctx.setDefaultsDescriptor(defWebXml.getAbsolutePath()); 247 } 248 if ("/".equals(app.getContextPath())) { // the root context must be 249 // put at the end 250 server.addHandler(ctx); 251 } else { 252 warContexts.addHandler(ctx); 253 } 254 org.mortbay.log.Log.setLog(new Log4JLogger(logger)); 255 // ctx.start(); 256 // HandlerWrapper wrapper = (HandlerWrapper)ctx.getHandler(); 257 // wrapper = (HandlerWrapper)wrapper.getHandler(); 258 // wrapper.setHandler(new NuxeoServletHandler()); 259 260 if (ctx.isFailed()) { 261 logger.error("Error in war deployment"); 262 } 263 264 } else if (XP_FILTER.equals(extensionPoint)) { 265 ctxMgr.addFilter((FilterDescriptor) contribution); 266 } else if (XP_SERVLET.equals(extensionPoint)) { 267 ctxMgr.addServlet((ServletDescriptor) contribution); 268 } else if (XP_LISTENERS.equals(extensionPoint)) { 269 ctxMgr.addLifecycleListener((ServletContextListenerDescriptor) contribution); 270 } 271 } 272 273 public ContextManager getContextManager() { 274 return ctxMgr; 275 } 276 277 @Override 278 public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 279 if (XP_WEB_APP.equals(extensionPoint)) { 280 281 } else if (XP_FILTER.equals(extensionPoint)) { 282 ctxMgr.removeFilter((FilterDescriptor) contribution); 283 } else if (XP_SERVLET.equals(extensionPoint)) { 284 ctxMgr.removeServlet((ServletDescriptor) contribution); 285 } else if (XP_LISTENERS.equals(extensionPoint)) { 286 ctxMgr.removeLifecycleListener((ServletContextListenerDescriptor) contribution); 287 } 288 } 289 290 @Override 291 public <T> T getAdapter(Class<T> adapter) { 292 if (adapter == org.mortbay.jetty.Server.class) { 293 return adapter.cast(server); 294 } 295 return null; 296 } 297 298 // let's nuxeo runtime get access to the JNDI context 299 // injected through the JettyTransactionalListener 300 protected ClassLoader nuxeoCL; 301 302 public void setNuxeoClassLoader(ClassLoader cl) { 303 nuxeoCL = cl; 304 } 305 306 protected ClassLoader getClassLoader(ClassLoader cl) { 307 if (!Boolean.valueOf(System.getProperty("org.nuxeo.jetty.propagateNaming"))) { 308 return cl; 309 } 310 if (nuxeoCL == null) { 311 return cl; 312 } 313 return nuxeoCL; 314 } 315 316 @Override 317 public int getApplicationStartedOrder() { 318 return -100; 319 } 320 321 @Override 322 public void applicationStarted(ComponentContext context) { 323 if (server == null) { 324 return; 325 } 326 ctxMgr.applyLifecycleListeners(); 327 Thread t = Thread.currentThread(); 328 ClassLoader oldcl = t.getContextClassLoader(); 329 t.setContextClassLoader(getClass().getClassLoader()); 330 try { 331 server.start(); 332 } catch (Exception e) { // stupid Jetty API throws Exception 333 throw ExceptionUtils.runtimeException(e); 334 } finally { 335 t.setContextClassLoader(getClassLoader(oldcl)); 336 } 337 } 338 339 private void scanForWars(File dir) { 340 scanForWars(dir, ""); 341 } 342 343 private void scanForWars(File dir, String basePath) { 344 File[] roots = dir.listFiles(); 345 if (roots != null) { 346 for (File root : roots) { 347 String name = root.getName(); 348 if (name.endsWith(".war")) { 349 logger.info("Found war: " + name); 350 name = name.substring(0, name.length() - 4); 351 boolean isRoot = "root".equals(name); 352 String ctxPath = isRoot ? "/" : basePath + "/" + name; 353 WebAppContext ctx = new WebAppContext(root.getAbsolutePath(), ctxPath); 354 ctx.setConfigurations(new Configuration[] { new WebInfConfiguration(), new WebXmlConfiguration() }); 355 if (isRoot) { 356 server.addHandler(ctx); 357 } else { 358 warContexts.addHandler(ctx); 359 } 360 } else if (root.isDirectory()) { 361 scanForWars(root, basePath + "/" + name); 362 } 363 } 364 } 365 } 366 367}