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