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