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.ecm.webengine; 021 022import java.io.File; 023import java.io.IOException; 024import java.io.InputStream; 025import java.net.MalformedURLException; 026import java.net.URL; 027import java.util.HashMap; 028import java.util.Map; 029import java.util.Properties; 030 031import javax.servlet.GenericServlet; 032import javax.servlet.http.HttpServletRequest; 033import javax.servlet.http.HttpServletResponse; 034 035import org.apache.commons.io.IOUtils; 036import org.apache.commons.logging.Log; 037import org.apache.commons.logging.LogFactory; 038import org.nuxeo.ecm.platform.rendering.api.RenderingEngine; 039import org.nuxeo.ecm.platform.rendering.api.ResourceLocator; 040import org.nuxeo.ecm.platform.rendering.fm.FreemarkerEngine; 041import org.nuxeo.ecm.webengine.app.WebEngineModule; 042import org.nuxeo.ecm.webengine.jaxrs.context.RequestContext; 043import org.nuxeo.ecm.webengine.loader.WebLoader; 044import org.nuxeo.ecm.webengine.model.Module; 045import org.nuxeo.ecm.webengine.model.Resource; 046import org.nuxeo.ecm.webengine.model.WebContext; 047import org.nuxeo.ecm.webengine.model.impl.ModuleConfiguration; 048import org.nuxeo.ecm.webengine.model.impl.ModuleManager; 049import org.nuxeo.ecm.webengine.scripting.ScriptFile; 050import org.nuxeo.ecm.webengine.scripting.Scripting; 051import org.nuxeo.runtime.annotations.AnnotationManager; 052import org.nuxeo.runtime.api.Framework; 053 054import freemarker.ext.jsp.TaglibFactory; 055import freemarker.ext.servlet.HttpRequestHashModel; 056import freemarker.ext.servlet.HttpRequestParametersHashModel; 057import freemarker.ext.servlet.ServletContextHashModel; 058 059/** 060 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 061 */ 062public class WebEngine implements ResourceLocator { 063 064 public static final String SKIN_PATH_PREFIX_KEY = "org.nuxeo.ecm.webengine.skinPathPrefix"; 065 066 protected static final Map<Object, Object> mimeTypes = loadMimeTypes(); 067 068 private static final Log log = LogFactory.getLog(WebEngine.class); 069 070 static Map<Object, Object> loadMimeTypes() { 071 Map<Object, Object> mimeTypes = new HashMap<Object, Object>(); 072 Properties p = new Properties(); 073 URL url = WebEngine.class.getClassLoader().getResource("OSGI-INF/mime.properties"); 074 InputStream in = null; 075 try { 076 in = url.openStream(); 077 p.load(in); 078 mimeTypes.putAll(p); 079 } catch (IOException e) { 080 throw new RuntimeException("Failed to load mime types", e); 081 } finally { 082 IOUtils.closeQuietly(in); 083 } 084 return mimeTypes; 085 } 086 087 public static WebContext getActiveContext() { 088 RequestContext ctx = RequestContext.getActiveContext(); 089 if (ctx != null) { 090 return (WebContext) ctx.getRequest().getAttribute(WebContext.class.getName()); 091 } 092 return null; 093 } 094 095 protected final File root; 096 097 protected HashMap<String, WebEngineModule> apps; 098 099 /** 100 * moduleMgr use double-check idiom and needs to be volatile. See 101 * http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html 102 */ 103 protected volatile ModuleManager moduleMgr; 104 105 protected final Scripting scripting; 106 107 protected final RenderingEngine rendering; 108 109 protected final Map<String, Object> env; 110 111 protected boolean devMode; 112 113 protected final AnnotationManager annoMgr; 114 115 protected final ResourceRegistry registry; 116 117 protected String skinPathPrefix; 118 119 protected final WebLoader webLoader; 120 121 protected RequestConfiguration requestConfig; 122 123 protected volatile boolean isDirty; 124 125 public WebEngine(File root) { 126 this(new EmptyRegistry(), root); 127 } 128 129 public WebEngine(ResourceRegistry registry, File root) { 130 this.registry = registry; 131 this.root = root; 132 webLoader = new WebLoader(this); 133 apps = new HashMap<String, WebEngineModule>(); 134 scripting = new Scripting(webLoader); 135 annoMgr = new AnnotationManager(); 136 137 skinPathPrefix = Framework.getProperty(SKIN_PATH_PREFIX_KEY); 138 if (skinPathPrefix == null) { 139 // TODO: should put this in web.xml and not use jboss.home.dir to 140 // test if on jboss 141 skinPathPrefix = System.getProperty("jboss.home.dir") != null ? "/nuxeo/site/skin" : "/skin"; 142 } 143 144 env = new HashMap<String, Object>(); 145 env.put("installDir", root); 146 env.put("engine", "Nuxeo Web Engine"); 147 // TODO this should be put in the MANIFEST 148 env.put("version", "1.0.0.rc"); 149 150 rendering = new FreemarkerEngine(); 151 rendering.setResourceLocator(this); 152 rendering.setSharedVariable("env", getEnvironment()); 153 154 requestConfig = new RequestConfiguration(); 155 156 } 157 158 public RequestConfiguration getRequestConfiguration() { 159 return requestConfig; 160 } 161 162 /** 163 * JSP taglib support 164 */ 165 public void loadJspTaglib(GenericServlet servlet) { 166 if (rendering instanceof FreemarkerEngine) { 167 FreemarkerEngine fm = (FreemarkerEngine) rendering; 168 ServletContextHashModel servletContextModel = new ServletContextHashModel(servlet, fm.getObjectWrapper()); 169 fm.setSharedVariable("Application", servletContextModel); 170 fm.setSharedVariable("__FreeMarkerServlet.Application__", servletContextModel); 171 fm.setSharedVariable("Application", servletContextModel); 172 fm.setSharedVariable("__FreeMarkerServlet.Application__", servletContextModel); 173 fm.setSharedVariable("JspTaglibs", new TaglibFactory(servlet.getServletContext())); 174 } 175 } 176 177 public void initJspRequestSupport(GenericServlet servlet, HttpServletRequest request, HttpServletResponse response) { 178 if (rendering instanceof FreemarkerEngine) { 179 FreemarkerEngine fm = (FreemarkerEngine) rendering; 180 HttpRequestHashModel requestModel = new HttpRequestHashModel(request, response, fm.getObjectWrapper()); 181 fm.setSharedVariable("__FreeMarkerServlet.Request__", requestModel); 182 fm.setSharedVariable("Request", requestModel); 183 fm.setSharedVariable("RequestParameters", new HttpRequestParametersHashModel(request)); 184 185 // HttpSessionHashModel sessionModel = null; 186 // HttpSession session = request.getSession(false); 187 // if(session != null) { 188 // sessionModel = (HttpSessionHashModel) 189 // session.getAttribute(ATTR_SESSION_MODEL); 190 // if (sessionModel == null || sessionModel.isZombie()) { 191 // sessionModel = new HttpSessionHashModel(session, wrapper); 192 // session.setAttribute(ATTR_SESSION_MODEL, sessionModel); 193 // if(!sessionModel.isZombie()) { 194 // initializeSession(request, response); 195 // } 196 // } 197 // } 198 // else { 199 // sessionModel = new HttpSessionHashModel(servlet, request, 200 // response, fm.getObjectWrapper()); 201 // } 202 // sessionModel = new HttpSessionHashModel(request, response, 203 // fm.getObjectWrapper()); 204 // fm.setSharedVariable("Session", sessionModel); 205 } 206 } 207 208 public WebLoader getWebLoader() { 209 return webLoader; 210 } 211 212 public void setSkinPathPrefix(String skinPathPrefix) { 213 this.skinPathPrefix = skinPathPrefix; 214 } 215 216 public String getSkinPathPrefix() { 217 return skinPathPrefix; 218 } 219 220 @Deprecated 221 public ResourceRegistry getRegistry() { 222 return registry; 223 } 224 225 public Class<?> loadClass(String className) throws ClassNotFoundException { 226 return webLoader.loadClass(className); 227 } 228 229 public String getMimeType(String ext) { 230 return (String) mimeTypes.get(ext); 231 } 232 233 public AnnotationManager getAnnotationManager() { 234 return annoMgr; 235 } 236 237 public void registerRenderingExtension(String id, Object obj) { 238 rendering.setSharedVariable(id, obj); 239 } 240 241 public void unregisterRenderingExtension(String id) { 242 rendering.setSharedVariable(id, null); 243 } 244 245 public Map<String, Object> getEnvironment() { 246 return env; 247 } 248 249 public Scripting getScripting() { 250 return scripting; 251 } 252 253 public synchronized WebEngineModule[] getApplications() { 254 return apps.values().toArray(new WebEngineModule[apps.size()]); 255 } 256 257 public synchronized void addApplication(WebEngineModule app) { 258 flushCache(); 259 apps.put(app.getId(), app); 260 } 261 262 public ModuleManager getModuleManager() { 263 if (moduleMgr == null) { // avoid synchronizing if not needed 264 synchronized (this) { 265 /** 266 * the duplicate if is used avoid synchronizing when no needed. note that the this.moduleMgr member must 267 * be set at the end of the synchronized block after the module manager is completely initialized 268 */ 269 if (moduleMgr == null) { 270 ModuleManager moduleMgr = new ModuleManager(this); 271 File deployRoot = getDeploymentDirectory(); 272 if (deployRoot.isDirectory()) { 273 // load modules present in deploy directory 274 for (String name : deployRoot.list()) { 275 String path = name + "/module.xml"; 276 File file = new File(deployRoot, path); 277 if (file.isFile()) { 278 webLoader.addClassPathElement(file.getParentFile()); 279 moduleMgr.loadModule(file); 280 } 281 } 282 } 283 for (WebEngineModule app : getApplications()) { 284 ModuleConfiguration mc = app.getConfiguration(); 285 moduleMgr.loadModule(mc); 286 } 287 // set member at the end to be sure moduleMgr is completely 288 // initialized 289 this.moduleMgr = moduleMgr; 290 } 291 } 292 } 293 return moduleMgr; 294 } 295 296 public Module getModule(String name) { 297 ModuleConfiguration md = getModuleManager().getModule(name); 298 if (md != null) { 299 return md.get(); 300 } 301 return null; 302 } 303 304 public File getRootDirectory() { 305 return root; 306 } 307 308 public File getDeploymentDirectory() { 309 return new File(root, "deploy"); 310 } 311 312 public File getModulesDirectory() { 313 return new File(root, "modules"); 314 } 315 316 public RenderingEngine getRendering() { 317 return rendering; 318 } 319 320 /** 321 * Manage jax-rs root resource bindings 322 * 323 * @deprecated resources are deprecated - you should use a jax-rs application to declare more resources. 324 */ 325 @Deprecated 326 public void addResourceBinding(ResourceBinding binding) { 327 try { 328 binding.resolve(this); 329 registry.addBinding(binding); 330 } catch (ClassNotFoundException e) { 331 throw WebException.wrap("Failed o register binding: " + binding, e); 332 } 333 } 334 335 /** 336 * @deprecated resources are deprecated - you should use a jax-rs application to declare more resources. 337 */ 338 @Deprecated 339 public void removeResourceBinding(ResourceBinding binding) { 340 registry.removeBinding(binding); 341 } 342 343 /** 344 * @deprecated resources are deprecated - you should use a jax-rs application to declare more resources. 345 */ 346 @Deprecated 347 public ResourceBinding[] getBindings() { 348 return registry.getBindings(); 349 } 350 351 public synchronized void setDirty(boolean dirty) { 352 isDirty = dirty; 353 } 354 355 public boolean tryReload() { 356 if (isDirty) { 357 synchronized (this) { 358 if (isDirty) { 359 reload(); 360 return true; 361 } 362 } 363 } 364 return false; 365 } 366 367 public synchronized boolean isDirty() { 368 return isDirty; 369 } 370 371 public synchronized void flushCache() { 372 isDirty = false; 373 if (moduleMgr != null) { 374 webLoader.flushCache(); 375 moduleMgr = null; 376 } 377 } 378 379 /** 380 * Reloads configuration. 381 */ 382 public synchronized void reload() { 383 log.info("Reloading WebEngine"); 384 isDirty = false; 385 webLoader.flushCache(); 386 apps = new HashMap<String, WebEngineModule>(); 387 if (moduleMgr != null) { // avoid synchronizing if not needed 388 for (ModuleConfiguration mc : moduleMgr.getModules()) { 389 if (mc.isLoaded()) { 390 // remove module level caches 391 mc.get().flushCache(); 392 } 393 } 394 moduleMgr = null; 395 } 396 } 397 398 public synchronized void reloadModules() { 399 if (moduleMgr != null) { 400 moduleMgr.reloadModules(); 401 } 402 } 403 404 public void start() { 405 } 406 407 public void stop() { 408 registry.clear(); 409 } 410 411 protected ModuleConfiguration getModuleFromPath(String rootPath, String path) { 412 path = path.substring(rootPath.length() + 1); 413 int p = path.indexOf('/'); 414 String moduleName = path; 415 if (p > -1) { 416 moduleName = path.substring(0, p); 417 } 418 return moduleMgr.getModule(moduleName); 419 } 420 421 /* ResourceLocator API */ 422 423 @Override 424 public URL getResourceURL(String key) { 425 try { 426 return new URL(key); 427 } catch (MalformedURLException e) { 428 return null; 429 } 430 } 431 432 @Override 433 public File getResourceFile(String key) { 434 WebContext ctx = getActiveContext(); 435 if (key.startsWith("@")) { 436 Resource rs = ctx.getTargetObject(); 437 if (rs != null) { 438 return rs.getView(key.substring(1)).script().getFile(); 439 } 440 } else { 441 ScriptFile file = ctx.getFile(key); 442 if (file != null) { 443 return file.getFile(); 444 } 445 } 446 return null; 447 } 448 449}