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